mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2026-04-20 15:22:42 +00:00
Compare commits
781 Commits
0.2.1_alph
...
1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eb3ef0500 | ||
|
|
ffc20f5fee | ||
|
|
6144f3a3f9 | ||
|
|
a3bda1b8fd | ||
|
|
2a6c742a51 | ||
|
|
604a559b54 | ||
|
|
d9a0243905 | ||
|
|
99096885f5 | ||
|
|
f16c296f38 | ||
|
|
85eb08e422 | ||
|
|
ee5b89c4aa | ||
|
|
c13eb950b2 | ||
|
|
011fdce237 | ||
|
|
79e79e78ac | ||
|
|
6114cf0f58 | ||
|
|
3350697875 | ||
|
|
7f4557527e | ||
|
|
1aa2c064f7 | ||
|
|
1f3dcc1beb | ||
|
|
e9cdf162fa | ||
|
|
a487c0aaea | ||
|
|
d84273a0db | ||
|
|
5d08f1018d | ||
|
|
b7a0f849cf | ||
|
|
7079ddb74e | ||
|
|
e744520d50 | ||
|
|
0741f265d7 | ||
|
|
2f61b190ca | ||
|
|
fc30287bed | ||
|
|
003ff37ab8 | ||
|
|
8290284586 | ||
|
|
b9e35e6558 | ||
|
|
679fb49743 | ||
|
|
5974839515 | ||
|
|
666b89c4c7 | ||
|
|
d21a61aefd | ||
|
|
5f29350dd1 | ||
|
|
8f43110c72 | ||
|
|
a4240ab8a1 | ||
|
|
d73a18ddcc | ||
|
|
6dc24a7fc7 | ||
|
|
cd74313bc8 | ||
|
|
14fae89a0b | ||
|
|
0498a48b93 | ||
|
|
22e9d25bed | ||
|
|
4092874f5c | ||
|
|
e27702c166 | ||
|
|
21f4c40e7f | ||
|
|
bd744d07ba | ||
|
|
8a4055920d | ||
|
|
6c05c11a62 | ||
|
|
e62351c206 | ||
|
|
ffed602246 | ||
|
|
03a2f41c04 | ||
|
|
df51dc7104 | ||
|
|
8bea72beb5 | ||
|
|
14813aa962 | ||
|
|
ce448d6852 | ||
|
|
7506e45d3b | ||
|
|
034ada1ed7 | ||
|
|
646fe4fd02 | ||
|
|
fa9347f2ee | ||
|
|
85de72a859 | ||
|
|
b327bfbe5d | ||
|
|
da8614438c | ||
|
|
5248688241 | ||
|
|
26dedec561 | ||
|
|
d8d45ec7af | ||
|
|
a888065624 | ||
|
|
8454b40d54 | ||
|
|
175e361ccd | ||
|
|
2baf607b8c | ||
|
|
a974658c98 | ||
|
|
465dc9e5dc | ||
|
|
8d3f646aec | ||
|
|
4418baba3f | ||
|
|
3d5047338c | ||
|
|
fb32b4d55a | ||
|
|
5f1a94c267 | ||
|
|
0d7f1265da | ||
|
|
09e332a8d1 | ||
|
|
e877844768 | ||
|
|
c9f1ec0a8a | ||
|
|
4b43da095e | ||
|
|
3103d2d168 | ||
|
|
027297933b | ||
|
|
1adcdea6d1 | ||
|
|
ebd74d1d1a | ||
|
|
0d993937b3 | ||
|
|
79e2747aed | ||
|
|
8d3557268f | ||
|
|
336d69c043 | ||
|
|
2ddb1b93c4 | ||
|
|
63ae56cb9b | ||
|
|
45e4c21870 | ||
|
|
ead7ee153a | ||
|
|
9cb6d96f8f | ||
|
|
0b7a7ca193 | ||
|
|
f6e0e2f39d | ||
|
|
def6036b30 | ||
|
|
943d23a7ce | ||
|
|
218844ed47 | ||
|
|
794c486352 | ||
|
|
e6293b6435 | ||
|
|
6ef58f2e7c | ||
|
|
c0f3babc49 | ||
|
|
0ff287cbfb | ||
|
|
0c40a2954d | ||
|
|
4735fd238a | ||
|
|
ace0f4a316 | ||
|
|
6583104a96 | ||
|
|
4a5a29a59a | ||
|
|
ff38cefe11 | ||
|
|
168226c634 | ||
|
|
3d7cfffe13 | ||
|
|
6f409b59c8 | ||
|
|
ff030397a4 | ||
|
|
1f1b0cd45e | ||
|
|
4aaf71f5cc | ||
|
|
5971d3d3b3 | ||
|
|
2151d1e6cc | ||
|
|
b2d8e19504 | ||
|
|
939197df6b | ||
|
|
eb48dd70fb | ||
|
|
6cca4c654f | ||
|
|
f86df07c36 | ||
|
|
73bbd69e3f | ||
|
|
fd9f4ebdc3 | ||
|
|
ac04432453 | ||
|
|
91c6823e0c | ||
|
|
cf3c976651 | ||
|
|
29ec14d3f0 | ||
|
|
ba208bf8b3 | ||
|
|
7ebfddc03c | ||
|
|
61f56b6e56 | ||
|
|
132f591288 | ||
|
|
7e6b9d8487 | ||
|
|
4c8b810bd6 | ||
|
|
f9ad86e312 | ||
|
|
7219e3a4de | ||
|
|
1bc49426e2 | ||
|
|
41a307fd35 | ||
|
|
d4849af171 | ||
|
|
ab376ea1aa | ||
|
|
6db8251e46 | ||
|
|
4dc0df74cf | ||
|
|
5b9bd56cf2 | ||
|
|
ecbb451763 | ||
|
|
78f079ca84 | ||
|
|
a408084237 | ||
|
|
9c0602f406 | ||
|
|
3f6b8dbe6c | ||
|
|
5a21b07269 | ||
|
|
b96206765a | ||
|
|
f0b2d80ba7 | ||
|
|
a19e47bd54 | ||
|
|
4a2774367f | ||
|
|
7d720e4d6f | ||
|
|
b87ec8f2cc | ||
|
|
2c4e221d1c | ||
|
|
0dbd89db19 | ||
|
|
8f4942bbe9 | ||
|
|
24892c854e | ||
|
|
5f84ecc4de | ||
|
|
659b9b1e8c | ||
|
|
8a1df1d712 | ||
|
|
aaa15315ce | ||
|
|
70cf463881 | ||
|
|
dff9ec2d37 | ||
|
|
192e86064c | ||
|
|
bfdeb77f7d | ||
|
|
da96ecaaba | ||
|
|
72a794df6f | ||
|
|
c39b9609be | ||
|
|
d31ed762c1 | ||
|
|
dad41e1574 | ||
|
|
a6aee1d9a4 | ||
|
|
64ed5058bf | ||
|
|
26079dba0a | ||
|
|
94fae2135d | ||
|
|
1e71a52727 | ||
|
|
da2f4fcf3a | ||
|
|
b5d38c71ce | ||
|
|
8a18bec55c | ||
|
|
45ff5dd0cc | ||
|
|
55017f876d | ||
|
|
c59b83e564 | ||
|
|
c0244e819e | ||
|
|
3467031bf4 | ||
|
|
51ef8b1891 | ||
|
|
bbff0036dc | ||
|
|
e6ab6f3cc9 | ||
|
|
99d14f7abb | ||
|
|
215c17ae20 | ||
|
|
b4e1eef8c9 | ||
|
|
0b276bed1d | ||
|
|
1b27916c24 | ||
|
|
85b9649a9a | ||
|
|
aa9ab8e1e8 | ||
|
|
1eca58605c | ||
|
|
9a1850bd61 | ||
|
|
c23b2bdc55 | ||
|
|
754a9ac406 | ||
|
|
3225f15419 | ||
|
|
b1bb863a7e | ||
|
|
b030c22c56 | ||
|
|
e1086e2d41 | ||
|
|
4634c8187f | ||
|
|
9913124a5c | ||
|
|
0bc1bd8549 | ||
|
|
c6c15a446b | ||
|
|
6e4f502454 | ||
|
|
66150922c7 | ||
|
|
d1acfeb496 | ||
|
|
233d5ed838 | ||
|
|
860afb2fbd | ||
|
|
d8c0c8649c | ||
|
|
55b2b050c8 | ||
|
|
96f83ee55c | ||
|
|
3e79d4dfad | ||
|
|
d3276a1546 | ||
|
|
ce8b4ceb44 | ||
|
|
bed0712be1 | ||
|
|
f483de1f7e | ||
|
|
a6df90785a | ||
|
|
ab4cde9bb8 | ||
|
|
1738706c59 | ||
|
|
c801b6547f | ||
|
|
490243e346 | ||
|
|
17db61c302 | ||
|
|
bef4a6efc1 | ||
|
|
26605b8d90 | ||
|
|
8b25d74dde | ||
|
|
8c8acf6955 | ||
|
|
6aade531a2 | ||
|
|
53f2caa9cc | ||
|
|
ac474902a4 | ||
|
|
20c47ae8f2 | ||
|
|
7d4fdad6f6 | ||
|
|
de3ee34fce | ||
|
|
7481f0432b | ||
|
|
41709ef916 | ||
|
|
935534905f | ||
|
|
b9a41c83bf | ||
|
|
54165c64ec | ||
|
|
0300be1c4b | ||
|
|
48932a5230 | ||
|
|
624817618c | ||
|
|
48399d341f | ||
|
|
48ae57ad2d | ||
|
|
128e52e33a | ||
|
|
27697bb638 | ||
|
|
ed29f9dcc5 | ||
|
|
78c7ef0242 | ||
|
|
95f7171256 | ||
|
|
f968725469 | ||
|
|
3d65e515ad | ||
|
|
1ba3756be0 | ||
|
|
061cb91c48 | ||
|
|
5feba74b9b | ||
|
|
6117635a8d | ||
|
|
8c70d816a0 | ||
|
|
c862882499 | ||
|
|
b9642f1e62 | ||
|
|
481d5a6b14 | ||
|
|
6deae14870 | ||
|
|
102eea134c | ||
|
|
1894e191d5 | ||
|
|
36f2b157e5 | ||
|
|
e4b4787cbb | ||
|
|
78e40f1f76 | ||
|
|
8596c26f7e | ||
|
|
a4ce0c8868 | ||
|
|
86702040a4 | ||
|
|
5322a4632c | ||
|
|
670b5aefce | ||
|
|
f316856682 | ||
|
|
1c18310f37 | ||
|
|
8c428be885 | ||
|
|
c9a247b64d | ||
|
|
95d2d2d7c8 | ||
|
|
8eb1067da5 | ||
|
|
d08611ee91 | ||
|
|
289eba7855 | ||
|
|
31e7867915 | ||
|
|
5fd1509c96 | ||
|
|
e2b897f1f2 | ||
|
|
2628700ea8 | ||
|
|
10d368444b | ||
|
|
75b54fb9b4 | ||
|
|
f0250f0cd1 | ||
|
|
db1ab774f0 | ||
|
|
28d189e35a | ||
|
|
5c487b73ab | ||
|
|
71eb2735f6 | ||
|
|
53ba9eddf7 | ||
|
|
3efdd51fa6 | ||
|
|
e115161da8 | ||
|
|
dec16eeb87 | ||
|
|
2a22a125bb | ||
|
|
8305750016 | ||
|
|
77ac94ff99 | ||
|
|
35122708be | ||
|
|
6152403faf | ||
|
|
cd4f64bc80 | ||
|
|
7a95229cc2 | ||
|
|
df42830d38 | ||
|
|
2df1869824 | ||
|
|
05440ed2fc | ||
|
|
7a9371c062 | ||
|
|
fc350871e4 | ||
|
|
88fe31fead | ||
|
|
fc7ecab5f2 | ||
|
|
0382b8aed8 | ||
|
|
a35d0252e7 | ||
|
|
dd5490cac6 | ||
|
|
75568a7bf7 | ||
|
|
b8347fd254 | ||
|
|
d43f501819 | ||
|
|
b8e4a79188 | ||
|
|
1dbdf48e9a | ||
|
|
ed83abaeef | ||
|
|
89e805b1a0 | ||
|
|
a4c25280b3 | ||
|
|
72cbf741b3 | ||
|
|
2c83d79836 | ||
|
|
9efdb6b150 | ||
|
|
20ce4efebb | ||
|
|
d3ad4a5035 | ||
|
|
5edb838f31 | ||
|
|
89dfeeb247 | ||
|
|
956969f679 | ||
|
|
7ee86a5d40 | ||
|
|
f8be28dcee | ||
|
|
bc11ef7e9c | ||
|
|
eb8cd09e65 | ||
|
|
77dada07da | ||
|
|
e236c42068 | ||
|
|
38c9e2c894 | ||
|
|
a4fa7d2ff6 | ||
|
|
61ebff209b | ||
|
|
4849d9cf09 | ||
|
|
a0316e57c5 | ||
|
|
6332b33f3e | ||
|
|
7d6ecf923b | ||
|
|
8e194ba5a9 | ||
|
|
99ec2a12f1 | ||
|
|
dd0ec72fb7 | ||
|
|
271e4a6f46 | ||
|
|
a9d1d9b9e7 | ||
|
|
9776191e83 | ||
|
|
a0f955e907 | ||
|
|
18eb29fabd | ||
|
|
f23faa72ec | ||
|
|
70c7060eaf | ||
|
|
6420553ae9 | ||
|
|
dc65ff3a05 | ||
|
|
c9a44d1ecf | ||
|
|
13949e0dde | ||
|
|
35d50c91c7 | ||
|
|
3a142b0d85 | ||
|
|
f119af5e6f | ||
|
|
a91ed266d5 | ||
|
|
bdce551a50 | ||
|
|
89599d0bf8 | ||
|
|
685a14a21e | ||
|
|
24e6ac8013 | ||
|
|
2b4cc46a53 | ||
|
|
30e8b3b60e | ||
|
|
026e5f9bcb | ||
|
|
3c9b8db090 | ||
|
|
234e1618c8 | ||
|
|
bc605f1351 | ||
|
|
06b524213f | ||
|
|
21cea65fbe | ||
|
|
000429c3b5 | ||
|
|
85d79f25d9 | ||
|
|
31b7d97097 | ||
|
|
16098ba717 | ||
|
|
dfd29bfa04 | ||
|
|
ba25eee09a | ||
|
|
cf4cfb50fc | ||
|
|
91176c9291 | ||
|
|
5aa9359236 | ||
|
|
f760aba7dd | ||
|
|
b0409ad033 | ||
|
|
d91934955b | ||
|
|
d1e553f05a | ||
|
|
3bf4f0ce01 | ||
|
|
892f957729 | ||
|
|
d903daa046 | ||
|
|
b16ab3f0c0 | ||
|
|
c2bccc9b04 | ||
|
|
0b9d5c2b69 | ||
|
|
098f09844b | ||
|
|
2c334c08ac | ||
|
|
11766a2c41 | ||
|
|
ce389dfd79 | ||
|
|
48bba00cb3 | ||
|
|
b74043e2ee | ||
|
|
64436f1034 | ||
|
|
58864b79e4 | ||
|
|
cb8bbd7ccc | ||
|
|
37ad6365e3 | ||
|
|
6c4af86b29 | ||
|
|
f492c6fc61 | ||
|
|
d41ae73e0d | ||
|
|
26e623bec4 | ||
|
|
aeab33127d | ||
|
|
7b6d0c1acd | ||
|
|
b16c6f50a6 | ||
|
|
edf3743f49 | ||
|
|
1675ee99a4 | ||
|
|
f88e2312b8 | ||
|
|
28aaeef2b6 | ||
|
|
669288385e | ||
|
|
4b0516966d | ||
|
|
a9cb6bfe79 | ||
|
|
bcc1744a76 | ||
|
|
ce56d03c3e | ||
|
|
38c1949538 | ||
|
|
27394a091f | ||
|
|
b72246d769 | ||
|
|
a6e58e7b1d | ||
|
|
ad579d514b | ||
|
|
3b657484b2 | ||
|
|
eff9cd4b71 | ||
|
|
5e9486ef4c | ||
|
|
f29d683918 | ||
|
|
f55d591cba | ||
|
|
20ee982e3d | ||
|
|
c21db4b6d1 | ||
|
|
f0ef239e22 | ||
|
|
a714370eb2 | ||
|
|
abf5ad2eec | ||
|
|
68fdd7e3f0 | ||
|
|
49e671802a | ||
|
|
da8fa813c5 | ||
|
|
c823f0759d | ||
|
|
c71f7898ed | ||
|
|
a24972843e | ||
|
|
9f0262fc05 | ||
|
|
4fc55f75a8 | ||
|
|
fcb1d94946 | ||
|
|
38acf81a76 | ||
|
|
eaae856a37 | ||
|
|
79df475c9d | ||
|
|
a6ce43ba71 | ||
|
|
9e0fee27ab | ||
|
|
d3a6ee6a94 | ||
|
|
a4ec53c90a | ||
|
|
0e7a8301f7 | ||
|
|
5b4c5bd5c1 | ||
|
|
b4ee6420c5 | ||
|
|
e4c062c9c2 | ||
|
|
72bd3e9cc1 | ||
|
|
49cf3af769 | ||
|
|
127d6bf0a7 | ||
|
|
044f8cbffd | ||
|
|
89d0f6b761 | ||
|
|
d0bea51cd4 | ||
|
|
d325dab035 | ||
|
|
4174554260 | ||
|
|
3a81bb9725 | ||
|
|
f96c31deab | ||
|
|
aae6f535f5 | ||
|
|
23dcc16829 | ||
|
|
8f9fdd8b70 | ||
|
|
b4d06697b4 | ||
|
|
cc9da905a6 | ||
|
|
e79fa4145b | ||
|
|
0769b61dd2 | ||
|
|
3a8a29402d | ||
|
|
e21b876104 | ||
|
|
36b5af45c6 | ||
|
|
d0c14efbd1 | ||
|
|
3e9e6de16d | ||
|
|
a754becb46 | ||
|
|
fe9af9ced6 | ||
|
|
5c9c8c2670 | ||
|
|
0a4c72c571 | ||
|
|
f765a07c3b | ||
|
|
3e81a07563 | ||
|
|
c2e376879c | ||
|
|
548b56cf16 | ||
|
|
8333912b24 | ||
|
|
f879a775f8 | ||
|
|
51573146f0 | ||
|
|
835ec716a0 | ||
|
|
b86cf9681c | ||
|
|
3b4cc26210 | ||
|
|
3422998bd1 | ||
|
|
c90772666e | ||
|
|
096c5edbd4 | ||
|
|
3541b8a0dd | ||
|
|
9e410e3856 | ||
|
|
35d079beb1 | ||
|
|
c846e0f400 | ||
|
|
7ff8f3f7b9 | ||
|
|
27eb2571a4 | ||
|
|
ff2ab3b27e | ||
|
|
9df90e5e75 | ||
|
|
49ec3d68d2 | ||
|
|
9def1843b2 | ||
|
|
c26855d07e | ||
|
|
a3f147a827 | ||
|
|
a2d93915e8 | ||
|
|
29e9db184f | ||
|
|
2f93c7ae58 | ||
|
|
4abfe407da | ||
|
|
9b27e81091 | ||
|
|
39787743fd | ||
|
|
22e47807b8 | ||
|
|
898525a6d8 | ||
|
|
1ebcfe7d80 | ||
|
|
1dbc39b970 | ||
|
|
80f5f6c288 | ||
|
|
b18acd469f | ||
|
|
cefcd18269 | ||
|
|
4de3ac176d | ||
|
|
afd5699ff1 | ||
|
|
b79461e3ce | ||
|
|
de6ab8ecdf | ||
|
|
d0180d42a8 | ||
|
|
2e504b40f6 | ||
|
|
9b00304c29 | ||
|
|
979928ded8 | ||
|
|
7c4e442432 | ||
|
|
0dd445f101 | ||
|
|
f217804838 | ||
|
|
9a630fff06 | ||
|
|
db508214d7 | ||
|
|
8e764f48ae | ||
|
|
2583063f5f | ||
|
|
dd4ec22b39 | ||
|
|
42dbcec93f | ||
|
|
9bbf634f5d | ||
|
|
d6b09759de | ||
|
|
5bb8a943ad | ||
|
|
b370eda0d5 | ||
|
|
69bcbf6f27 | ||
|
|
04823abb83 | ||
|
|
7269a0ea12 | ||
|
|
153b58fbbd | ||
|
|
149af55e61 | ||
|
|
9cac95fd82 | ||
|
|
09498f3b18 | ||
|
|
bb919d0f32 | ||
|
|
6d0abd73a5 | ||
|
|
717f2a822b | ||
|
|
8946b4b4b6 | ||
|
|
db279d2b36 | ||
|
|
bb7965b3c4 | ||
|
|
a33fe5a4cc | ||
|
|
4a03f0870c | ||
|
|
bfe15aff19 | ||
|
|
42bc2d01f7 | ||
|
|
0cb9fc0df8 | ||
|
|
450896b122 | ||
|
|
cc0b89dbe2 | ||
|
|
c887b96a77 | ||
|
|
22541ae0f4 | ||
|
|
d83da38d79 | ||
|
|
b21f8abbd6 | ||
|
|
e9aade4d0d | ||
|
|
2bf2fff3d6 | ||
|
|
463a22fdfb | ||
|
|
3175022b31 | ||
|
|
504d910226 | ||
|
|
c2769e1a72 | ||
|
|
7577253dbf | ||
|
|
e4c5b2dbd1 | ||
|
|
fafd76ff94 | ||
|
|
e354d11820 | ||
|
|
a3374c7eca | ||
|
|
552b886cea | ||
|
|
ff9a19381b | ||
|
|
77aacc2e5d | ||
|
|
6a1fa2c13b | ||
|
|
c3bb64bf6e | ||
|
|
da68ab4ed0 | ||
|
|
1aedf92bcd | ||
|
|
abcf484506 | ||
|
|
a93681a980 | ||
|
|
a08758ea54 | ||
|
|
0a0f5b8e8c | ||
|
|
84f67a3ac1 | ||
|
|
22d18a9e58 | ||
|
|
d1a8425d43 | ||
|
|
65d94f03e4 | ||
|
|
3a49041f27 | ||
|
|
eec2f7c4a0 | ||
|
|
720df5ce89 | ||
|
|
d7cea16d4a | ||
|
|
d5c0fdd525 | ||
|
|
4a86d6073c | ||
|
|
98b6e580b4 | ||
|
|
c96c69c112 | ||
|
|
bd545feb2c | ||
|
|
e90b6656c3 | ||
|
|
5099c16a12 | ||
|
|
d9dcfa4a88 | ||
|
|
1fcd783dd9 | ||
|
|
2c84123158 | ||
|
|
c3d39029b8 | ||
|
|
20b703f8bf | ||
|
|
89c579880c | ||
|
|
db389372ad | ||
|
|
3a6eaf6526 | ||
|
|
3e27af472b | ||
|
|
7bea6058fe | ||
|
|
b02b6c30b5 | ||
|
|
46e9266752 | ||
|
|
e3db19b16a | ||
|
|
774663d70d | ||
|
|
2c729bf646 | ||
|
|
9b1c9e9e29 | ||
|
|
80badebb37 | ||
|
|
fc9e155481 | ||
|
|
16d8a31c12 | ||
|
|
7ba6081cb3 | ||
|
|
c3a8865dd3 | ||
|
|
929ca50b06 | ||
|
|
e5123dd8bf | ||
|
|
fe1de4bed9 | ||
|
|
c612620ca5 | ||
|
|
ca9d2c01af | ||
|
|
51d90c1898 | ||
|
|
a6a4193fbb | ||
|
|
f4f8c77ffa | ||
|
|
9b8c1a3072 | ||
|
|
92b77904f6 | ||
|
|
9805e4a395 | ||
|
|
6a01c9d426 | ||
|
|
48df92c8a5 | ||
|
|
5bb2f9bf05 | ||
|
|
e5dbac4345 | ||
|
|
618d4ac4cc | ||
|
|
19e516f206 | ||
|
|
afadb71d64 | ||
|
|
b3d1eabbad | ||
|
|
eac0a7a13f | ||
|
|
e06ed84330 | ||
|
|
4eae7c3ba5 | ||
|
|
61a612cf30 | ||
|
|
f1084157a3 | ||
|
|
de3b056133 | ||
|
|
02ae50905d | ||
|
|
0a5fd5c271 | ||
|
|
ef968ac1fb | ||
|
|
3156236745 | ||
|
|
5d320fdd53 | ||
|
|
cee6af1e14 | ||
|
|
35c7f0e3cf | ||
|
|
fc9bc496cb | ||
|
|
75f8a45119 | ||
|
|
50a73a380d | ||
|
|
e62042d26a | ||
|
|
c109de3949 | ||
|
|
39c87782db | ||
|
|
b6566dde14 | ||
|
|
ef36283370 | ||
|
|
922a226028 | ||
|
|
ba81f25933 | ||
|
|
da9528576a | ||
|
|
3fdd2477e5 | ||
|
|
62368e35a7 | ||
|
|
82d3431f1d | ||
|
|
edbc0c149d | ||
|
|
b8987e6d2d | ||
|
|
6296b8865b | ||
|
|
2df185e340 | ||
|
|
6262c64daa | ||
|
|
0fe5af9816 | ||
|
|
e94888d533 | ||
|
|
6130428989 | ||
|
|
5400a4e18a | ||
|
|
0d45217dfd | ||
|
|
fa1e647235 | ||
|
|
d637cb9e75 | ||
|
|
406f18bf11 | ||
|
|
72611b5fa7 | ||
|
|
313b786d88 | ||
|
|
801a56787f | ||
|
|
62868b2533 | ||
|
|
4bf88739b5 | ||
|
|
087380c966 | ||
|
|
fbd7321b48 | ||
|
|
f6cfe83d45 | ||
|
|
71f6be8d08 | ||
|
|
6e5450ed24 | ||
|
|
027054b57e | ||
|
|
3b6a3ff94d | ||
|
|
46d5b8a750 | ||
|
|
ac068036b8 | ||
|
|
ace1fe1e5e | ||
|
|
14a8e81662 | ||
|
|
eff7bbdd5a | ||
|
|
60342de9c0 | ||
|
|
47b04ffef4 | ||
|
|
1507e6ec31 | ||
|
|
524f20bc2f | ||
|
|
2c4d7cbf09 | ||
|
|
5fedda08d7 | ||
|
|
2056eae139 | ||
|
|
48a8b04eaa | ||
|
|
51ee02f9da | ||
|
|
ab8ce4c53f | ||
|
|
2aaf254565 | ||
|
|
109696c65a | ||
|
|
35ef99c6e9 | ||
|
|
91d382ca0c | ||
|
|
ec234e99a1 | ||
|
|
9de585190f | ||
|
|
d6b9e1d86a | ||
|
|
1ef31f0f8b | ||
|
|
c1052b1b28 | ||
|
|
e497122c06 | ||
|
|
407fcaadc6 | ||
|
|
7e6f24d203 | ||
|
|
c0825dbeeb | ||
|
|
39a65b51fb | ||
|
|
acf3fe0297 | ||
|
|
70c2ef36f5 | ||
|
|
7190acfe9e | ||
|
|
bf6210721d | ||
|
|
27731f376a | ||
|
|
d82260d4d4 | ||
|
|
78086a79f4 | ||
|
|
aa2caa67ad | ||
|
|
709627a738 | ||
|
|
649be86cb6 | ||
|
|
b56aab8f74 | ||
|
|
53ec38766a | ||
|
|
dbe811b47a | ||
|
|
f08515420e | ||
|
|
11913ab683 | ||
|
|
1cecc78c0c | ||
|
|
6717c43fc2 | ||
|
|
e44d20bdbc | ||
|
|
e50ed1b960 | ||
|
|
cff5987329 | ||
|
|
b2191c5d2c | ||
|
|
03dc5d2042 | ||
|
|
19e07eb767 | ||
|
|
c4f49203a1 | ||
|
|
9830337103 | ||
|
|
eadaf3ce6b | ||
|
|
31a95031e4 | ||
|
|
cdea80f8c5 | ||
|
|
b65bddc1b3 | ||
|
|
7759de96da | ||
|
|
9d2b60b88e | ||
|
|
a9c82ecc81 | ||
|
|
3aa8ce80db | ||
|
|
294337aa63 | ||
|
|
908abf0743 | ||
|
|
509e4aa7f7 | ||
|
|
13fbd6a800 | ||
|
|
b430f88245 | ||
|
|
e24e3cbc2a | ||
|
|
08292c279e | ||
|
|
10f1b992dd | ||
|
|
022898c61d | ||
|
|
cd7e5cf1bc | ||
|
|
f5d6e609d7 | ||
|
|
ed49ad6f7f | ||
|
|
f8c9aa1be4 | ||
|
|
e364ebac6d | ||
|
|
1a42421d92 | ||
|
|
e8ae20232a | ||
|
|
3216daec2b | ||
|
|
0dc14c663a | ||
|
|
07d3fa2034 | ||
|
|
f1fd6fce7a | ||
|
|
355a348ba0 | ||
|
|
d85fed00a0 | ||
|
|
d47679c5cf | ||
|
|
379d6f8101 | ||
|
|
43f6802199 | ||
|
|
6bfe15b14e |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
patreon: ryzerth
|
||||
247
.github/workflows/build_all.yml
vendored
Normal file
247
.github/workflows/build_all.yml
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
name: Build Binaries
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build_windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Download PothosSDR
|
||||
run: Invoke-WebRequest -Uri "https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2020.01.26-vc14-x64.exe" -OutFile ${{runner.workspace}}/pothos.exe
|
||||
|
||||
- name: Install PothosSDR
|
||||
run: mkdir "C:/Program Files/PothosSDR" ; 7z x ${{runner.workspace}}/pothos.exe -o"C:/Program Files/PothosSDR/"
|
||||
|
||||
- name: Download libusb
|
||||
run: Invoke-WebRequest -Uri "https://github.com/libusb/libusb/releases/download/v1.0.23/libusb-1.0.23.7z" -OutFile ${{runner.workspace}}/libusb.7z
|
||||
|
||||
- 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/"
|
||||
|
||||
- name: Download SDRPlay API
|
||||
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
|
||||
|
||||
- name: Install SDRPlay API
|
||||
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
|
||||
|
||||
- name: Install vcpkg dependencies
|
||||
run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows portaudio:x64-windows
|
||||
|
||||
- name: Install rtaudio
|
||||
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; mkdir build ; cd build ; 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
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake --build . --config Release
|
||||
|
||||
- name: Create Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_windows_x64
|
||||
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
|
||||
|
||||
build_macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install fftw glew glfw volk airspy portaudio hackrf rtl-sdr libbladerf
|
||||
|
||||
- name: Prepare CMake
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_AIRSPYHF_SOURCE=OFF -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
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: make -j3
|
||||
|
||||
- name: Create Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: sh $GITHUB_WORKSPACE/make_macos_package.sh ${{runner.workspace}}/build
|
||||
|
||||
- name: Save Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_macos_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_macos_amd64.pkg
|
||||
|
||||
build_debian_buster:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_debian_buster_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_bullseye:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_debian_bullseye_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_debian_sid:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_debian_sid_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_focal:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
|
||||
|
||||
- name: Run Container
|
||||
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
|
||||
|
||||
- name: Recover Deb Archive
|
||||
working-directory: ${{runner.workspace}}
|
||||
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
|
||||
|
||||
- name: Save Deb Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_ubuntu_focal_amd64
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
build_ubuntu_groovy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create Docker Image
|
||||
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_groovy && 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_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
|
||||
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
|
||||
|
||||
create_full_archive:
|
||||
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_groovy', 'build_ubuntu_hirsute']
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download All Builds
|
||||
uses: actions/download-artifact@v2
|
||||
|
||||
- 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_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_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
|
||||
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
|
||||
mv sdrpp_ubuntu_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
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: sdrpp_all
|
||||
path: sdrpp_all/
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,3 +1,14 @@
|
||||
build/
|
||||
.vscode/
|
||||
*.old
|
||||
.vs/
|
||||
.idea/
|
||||
*.old
|
||||
*.dll
|
||||
*.exe
|
||||
*.zip
|
||||
*.wav
|
||||
.DS_Store
|
||||
root_dev/
|
||||
Folder.DotSettings.user
|
||||
CMakeSettings.json
|
||||
poggers_decoder
|
||||
236
CMakeLists.txt
236
CMakeLists.txt
@@ -1,48 +1,208 @@
|
||||
cmake_minimum_required(VERSION 3.9)
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp)
|
||||
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CMAKE_INSTALL_PREFIX "/usr/local")
|
||||
else()
|
||||
set(CMAKE_INSTALL_PREFIX "/usr")
|
||||
endif()
|
||||
|
||||
# Compiler config
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
|
||||
# Sources
|
||||
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Depedencies: libairspy)" ON)
|
||||
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Depedencies: libairspyhf)" ON)
|
||||
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Depedencies: libbladeRF)" OFF)
|
||||
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
|
||||
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
|
||||
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Depedencies: liblimesuite)" OFF)
|
||||
option(OPT_BUILD_SDDC_SOURCE "Build SDDC Source Module (Depedencies: libusb-1.0)" OFF)
|
||||
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Depedencies: librtlsdr)" ON)
|
||||
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Depedencies: libsdrplay)" OFF)
|
||||
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Depedencies: soapysdr)" ON)
|
||||
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
|
||||
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON)
|
||||
|
||||
# PothosSDR
|
||||
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
|
||||
# Sinks
|
||||
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON)
|
||||
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Depedencies: 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 (Depedencies: portaudio)" OFF)
|
||||
|
||||
# Volk
|
||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
|
||||
link_libraries(volk)
|
||||
# Decoders
|
||||
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
|
||||
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator 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)
|
||||
|
||||
# SoapySDR
|
||||
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
|
||||
link_libraries(SoapySDR)
|
||||
# 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_RECORDER "Audio and baseband recorder" ON)
|
||||
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
|
||||
|
||||
# Main code
|
||||
include_directories(sdrpp "src/")
|
||||
include_directories(sdrpp "src/imgui")
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
file(GLOB IMGUI "src/imgui/*.cpp")
|
||||
add_executable(sdrpp ${SRC} ${IMGUI})
|
||||
# Compiler arguments for each platform
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
|
||||
# Glew
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE GLEW::GLEW)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE glfw)
|
||||
|
||||
# FFTW3
|
||||
find_package(FFTW3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3)
|
||||
find_package(FFTW3f CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f)
|
||||
find_package(FFTW3l CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l)
|
||||
|
||||
# PortAudio
|
||||
find_package(portaudio CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
|
||||
# Core of SDR++
|
||||
add_subdirectory("core")
|
||||
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
|
||||
# Source modules
|
||||
if (OPT_BUILD_AIRSPY_SOURCE)
|
||||
add_subdirectory("airspy_source")
|
||||
endif (OPT_BUILD_AIRSPY_SOURCE)
|
||||
|
||||
if (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
add_subdirectory("airspyhf_source")
|
||||
endif (OPT_BUILD_AIRSPYHF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_BLADERF_SOURCE)
|
||||
add_subdirectory("bladerf_source")
|
||||
endif (OPT_BUILD_BLADERF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_FILE_SOURCE)
|
||||
add_subdirectory("file_source")
|
||||
endif (OPT_BUILD_FILE_SOURCE)
|
||||
|
||||
if (OPT_BUILD_HACKRF_SOURCE)
|
||||
add_subdirectory("hackrf_source")
|
||||
endif (OPT_BUILD_HACKRF_SOURCE)
|
||||
|
||||
if (OPT_BUILD_LIMESDR_SOURCE)
|
||||
add_subdirectory("limesdr_source")
|
||||
endif (OPT_BUILD_LIMESDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDDC_SOURCE)
|
||||
add_subdirectory("sddc_source")
|
||||
endif (OPT_BUILD_SDDC_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RTL_SDR_SOURCE)
|
||||
add_subdirectory("rtl_sdr_source")
|
||||
endif (OPT_BUILD_RTL_SDR_SOURCE)
|
||||
|
||||
if (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
add_subdirectory("rtl_tcp_source")
|
||||
endif (OPT_BUILD_RTL_TCP_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
add_subdirectory("sdrplay_source")
|
||||
endif (OPT_BUILD_SDRPLAY_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SOAPY_SOURCE)
|
||||
add_subdirectory("soapy_source")
|
||||
endif (OPT_BUILD_SOAPY_SOURCE)
|
||||
|
||||
if (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
add_subdirectory("spyserver_source")
|
||||
endif (OPT_BUILD_SPYSERVER_SOURCE)
|
||||
|
||||
if (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
add_subdirectory("plutosdr_source")
|
||||
endif (OPT_BUILD_PLUTOSDR_SOURCE)
|
||||
|
||||
|
||||
# Sink modules
|
||||
if (OPT_BUILD_AUDIO_SINK)
|
||||
add_subdirectory("audio_sink")
|
||||
endif (OPT_BUILD_AUDIO_SINK)
|
||||
|
||||
if (OPT_BUILD_PORTAUDIO_SINK)
|
||||
add_subdirectory("portaudio_sink")
|
||||
endif (OPT_BUILD_PORTAUDIO_SINK)
|
||||
|
||||
if (OPT_BUILD_NETWORK_SINK)
|
||||
add_subdirectory("network_sink")
|
||||
endif (OPT_BUILD_NETWORK_SINK)
|
||||
|
||||
if (OPT_BUILD_NEW_PORTAUDIO_SINK)
|
||||
add_subdirectory("new_portaudio_sink")
|
||||
endif (OPT_BUILD_NEW_PORTAUDIO_SINK)
|
||||
|
||||
|
||||
# Decoders
|
||||
if (OPT_BUILD_FALCON9_DECODER)
|
||||
add_subdirectory("falcon9_decoder")
|
||||
endif (OPT_BUILD_FALCON9_DECODER)
|
||||
|
||||
if (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
add_subdirectory("meteor_demodulator")
|
||||
endif (OPT_BUILD_METEOR_DEMODULATOR)
|
||||
|
||||
if (OPT_BUILD_RADIO)
|
||||
add_subdirectory("radio")
|
||||
endif (OPT_BUILD_RADIO)
|
||||
|
||||
if (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
add_subdirectory("weather_sat_decoder")
|
||||
endif (OPT_BUILD_WEATHER_SAT_DECODER)
|
||||
|
||||
|
||||
# Misc
|
||||
if (OPT_BUILD_DISCORD_PRESENCE)
|
||||
add_subdirectory("discord_integration")
|
||||
endif (OPT_BUILD_DISCORD_PRESENCE)
|
||||
|
||||
if (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
add_subdirectory("frequency_manager")
|
||||
endif (OPT_BUILD_FREQUENCY_MANAGER)
|
||||
|
||||
if (OPT_BUILD_RECORDER)
|
||||
add_subdirectory("recorder")
|
||||
endif (OPT_BUILD_RECORDER)
|
||||
|
||||
if (OPT_BUILD_RIGCTL_SERVER)
|
||||
add_subdirectory("rigctl_server")
|
||||
endif (OPT_BUILD_RIGCTL_SERVER)
|
||||
|
||||
|
||||
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
|
||||
target_link_libraries(sdrpp PRIVATE sdrpp_core)
|
||||
|
||||
# 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)
|
||||
endif ()
|
||||
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
target_link_libraries(sdrpp PUBLIC pthread)
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
|
||||
endif ()
|
||||
|
||||
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:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON
|
||||
|
||||
# Install directives
|
||||
install(TARGETS sdrpp DESTINATION bin)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/bandplans DESTINATION share/sdrpp)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/colormaps DESTINATION share/sdrpp)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/fonts DESTINATION share/sdrpp)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/icons DESTINATION share/sdrpp)
|
||||
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)
|
||||
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)
|
||||
|
||||
42
airspy_source/CMakeLists.txt
Normal file
42
airspy_source/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(airspy_source)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
add_library(airspy_source SHARED ${SRC})
|
||||
target_link_libraries(airspy_source PRIVATE sdrpp_core)
|
||||
set_target_properties(airspy_source PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(airspy_source PUBLIC "C:/Program Files/PothosSDR/bin/")
|
||||
|
||||
target_link_libraries(airspy_source PUBLIC airspy)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBAIRSPY REQUIRED libairspy)
|
||||
|
||||
target_include_directories(airspy_source PUBLIC ${LIBAIRSPY_INCLUDE_DIRS})
|
||||
target_link_directories(airspy_source PUBLIC ${LIBAIRSPY_LIBRARY_DIRS})
|
||||
target_link_libraries(airspy_source PUBLIC ${LIBAIRSPY_LIBRARIES})
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(airspy_source PUBLIC "/usr/local/include")
|
||||
endif()
|
||||
|
||||
endif ()
|
||||
|
||||
# Install directives
|
||||
install(TARGETS airspy_source DESTINATION lib/sdrpp/plugins)
|
||||
607
airspy_source/src/main.cpp
Normal file
607
airspy_source/src/main.cpp
Normal file
@@ -0,0 +1,607 @@
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <libairspy/airspy.h>
|
||||
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "airspy_source",
|
||||
/* Description: */ "Airspy source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class AirspySourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AirspySourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
airspy_init();
|
||||
|
||||
sampleRate = 10000000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
if (sampleRateList.size() > 0) {
|
||||
sampleRate = sampleRateList[0];
|
||||
}
|
||||
|
||||
// Select device from config
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
selectByString(devSerial);
|
||||
|
||||
sigpath::sourceManager.registerSource("Airspy", &handler);
|
||||
}
|
||||
|
||||
~AirspySourceModule() {
|
||||
stop(this);
|
||||
sigpath::sourceManager.unregisterSource("Airspy");
|
||||
airspy_exit();
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devList.clear();
|
||||
devListTxt = "";
|
||||
|
||||
uint64_t serials[256];
|
||||
int n = airspy_list_devices(serials, 256);
|
||||
|
||||
char buf[1024];
|
||||
for (int i = 0; i < n; i++) {
|
||||
sprintf(buf, "%016" PRIX64, serials[i]);
|
||||
devList.push_back(serials[i]);
|
||||
devListTxt += buf;
|
||||
devListTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (devList.size() != 0) {
|
||||
selectBySerial(devList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void selectByString(std::string serial) {
|
||||
char buf[1024];
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
sprintf(buf, "%016" PRIX64, devList[i]);
|
||||
std::string str = buf;
|
||||
if (serial == str) {
|
||||
selectBySerial(devList[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectBySerial(uint64_t serial) {
|
||||
airspy_device* dev;
|
||||
try {
|
||||
int err = airspy_open_sn(&dev, serial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
spdlog::error("Could not open Airspy {0}", buf);
|
||||
selectedSerial = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (std::exception e) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
spdlog::error("Could not open Airspy {0}", buf);
|
||||
}
|
||||
selectedSerial = serial;
|
||||
|
||||
uint32_t sampleRates[256];
|
||||
airspy_get_samplerates(dev, sampleRates, 0);
|
||||
int n = sampleRates[0];
|
||||
airspy_get_samplerates(dev, sampleRates, n);
|
||||
sampleRateList.clear();
|
||||
sampleRateListTxt = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
sampleRateList.push_back(sampleRates[i]);
|
||||
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
|
||||
sampleRateListTxt += '\0';
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
selectedSerStr = std::string(buf);
|
||||
|
||||
// Load config here
|
||||
config.acquire();
|
||||
bool created = false;
|
||||
if (!config.conf["devices"].contains(selectedSerStr)) {
|
||||
created = true;
|
||||
config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000;
|
||||
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["linearGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["mixerGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["vgaGain"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lnaAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["mixerAgc"] = false;
|
||||
config.conf["devices"][selectedSerStr]["biasT"] = false;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
srId = 0;
|
||||
sampleRate = sampleRateList[0];
|
||||
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
|
||||
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
|
||||
for (int i = 0; i < sampleRateList.size(); i++) {
|
||||
if (sampleRateList[i] == selectedSr) {
|
||||
srId = i;
|
||||
sampleRate = selectedSr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load gains
|
||||
if (config.conf["devices"][selectedSerStr].contains("gainMode")) {
|
||||
gainMode = config.conf["devices"][selectedSerStr]["gainMode"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) {
|
||||
sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("linearGain")) {
|
||||
linearGain = config.conf["devices"][selectedSerStr]["linearGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaGain")) {
|
||||
lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerGain")) {
|
||||
mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("vgaGain")) {
|
||||
vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) {
|
||||
lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) {
|
||||
mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"];
|
||||
}
|
||||
|
||||
// Load Bias-T
|
||||
if (config.conf["devices"][selectedSerStr].contains("biasT")) {
|
||||
biasT = config.conf["devices"][selectedSerStr]["biasT"];
|
||||
}
|
||||
|
||||
config.release(created);
|
||||
|
||||
airspy_close(dev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
spdlog::info("AirspySourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
spdlog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
if (_this->selectedSerial == 0) {
|
||||
spdlog::error("Tried to start Airspy source with null serial");
|
||||
return;
|
||||
}
|
||||
|
||||
int err = airspy_open_sn(&_this->openDev, _this->selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
|
||||
spdlog::error("Could not open Airspy {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
|
||||
airspy_set_freq(_this->openDev, _this->freq);
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
|
||||
airspy_set_rf_bias(_this->openDev, _this->biasT);
|
||||
|
||||
airspy_start_rx(_this->openDev, callback, _this);
|
||||
|
||||
_this->running = true;
|
||||
spdlog::info("AirspySourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
airspy_close(_this->openDev);
|
||||
_this->stream.clearWriteStop();
|
||||
spdlog::info("AirspySourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
airspy_set_freq(_this->openDev, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
spdlog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)ctx;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (_this->running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectBySerial(_this->devList[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerStr;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
|
||||
_this->sampleRate = _this->sampleRateList[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||
if (ImGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||
_this->refresh();
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectByString(devSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { style::endDisabled(); }
|
||||
|
||||
ImGui::BeginGroup();
|
||||
ImGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false);
|
||||
if (ImGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) {
|
||||
_this->gainMode = 0;
|
||||
if (_this->running) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) {
|
||||
_this->gainMode = 1;
|
||||
if (_this->running) {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::NextColumn();
|
||||
if (ImGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) {
|
||||
_this->gainMode = 2;
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
ImGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false);
|
||||
ImGui::EndGroup();
|
||||
|
||||
// Gain menus
|
||||
|
||||
if (_this->gainMode == 0) {
|
||||
ImGui::Text("Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 1) {
|
||||
ImGui::Text("Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
|
||||
if (_this->running) {
|
||||
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_this->gainMode == 2) {
|
||||
// Calculate position of sliders
|
||||
float pos = ImGui::CalcTextSize("Mixer Gain").x + 10;
|
||||
|
||||
if (_this->lnaAgc) { style::beginDisabled(); }
|
||||
ImGui::Text("LNA Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->lnaAgc) { style::endDisabled(); }
|
||||
|
||||
if (_this->mixerAgc) { style::beginDisabled(); }
|
||||
ImGui::Text("Mixer Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->mixerAgc) { style::endDisabled(); }
|
||||
|
||||
ImGui::Text("VGA Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(pos);
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
|
||||
if (_this->running) {
|
||||
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// AGC Control
|
||||
if (ImGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->lnaAgc) {
|
||||
airspy_set_lna_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_lna_agc(_this->openDev, 0);
|
||||
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (ImGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) {
|
||||
if (_this->running) {
|
||||
if (_this->mixerAgc) {
|
||||
airspy_set_mixer_agc(_this->openDev, 1);
|
||||
}
|
||||
else {
|
||||
airspy_set_mixer_agc(_this->openDev, 0);
|
||||
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bias T
|
||||
|
||||
if (ImGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) {
|
||||
if (_this->running) {
|
||||
airspy_set_rf_bias(_this->openDev, _this->biasT);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
static int callback(airspy_transfer_t* transfer) {
|
||||
AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx;
|
||||
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
|
||||
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
airspy_device* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
uint64_t selectedSerial = 0;
|
||||
std::string selectedSerStr = "";
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
|
||||
bool biasT = false;
|
||||
|
||||
int lnaGain = 0;
|
||||
int vgaGain = 0;
|
||||
int mixerGain = 0;
|
||||
int linearGain = 0;
|
||||
int sensitiveGain = 0;
|
||||
|
||||
int gainMode = 0;
|
||||
|
||||
bool lnaAgc = false;
|
||||
bool mixerAgc = false;
|
||||
|
||||
std::vector<uint64_t> devList;
|
||||
std::string devListTxt;
|
||||
std::vector<uint32_t> sampleRateList;
|
||||
std::string sampleRateListTxt;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(options::opts.root + "/airspy_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new AirspySourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (AirspySourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
42
airspyhf_source/CMakeLists.txt
Normal file
42
airspyhf_source/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(airspyhf_source)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
add_library(airspyhf_source SHARED ${SRC})
|
||||
target_link_libraries(airspyhf_source PRIVATE sdrpp_core)
|
||||
set_target_properties(airspyhf_source PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(airspyhf_source PUBLIC "C:/Program Files/PothosSDR/bin/")
|
||||
|
||||
target_link_libraries(airspyhf_source PUBLIC airspyhf)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(LIBAIRSPYHF REQUIRED libairspyhf)
|
||||
|
||||
target_include_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
|
||||
target_link_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
|
||||
target_link_libraries(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(airspyhf_source PUBLIC "/usr/local/include")
|
||||
endif()
|
||||
|
||||
endif ()
|
||||
|
||||
# Install directives
|
||||
install(TARGETS airspyhf_source DESTINATION lib/sdrpp/plugins)
|
||||
400
airspyhf_source/src/main.cpp
Normal file
400
airspyhf_source/src/main.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <libairspyhf/airspyhf.h>
|
||||
#include <gui/widgets/stepped_slider.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "airspyhf_source",
|
||||
/* Description: */ "Airspy HF+ source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
const char* AGG_MODES_STR = "Off\0Low\0High\0";
|
||||
|
||||
class AirspyHFSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AirspyHFSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 768000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
selectByString(devSerial);
|
||||
|
||||
sigpath::sourceManager.registerSource("Airspy HF+", &handler);
|
||||
}
|
||||
|
||||
~AirspyHFSourceModule() {
|
||||
stop(this);
|
||||
sigpath::sourceManager.unregisterSource("Airspy HF+");
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
enum AGCMode {
|
||||
AGC_MODE_OFF,
|
||||
AGC_MODE_LOW,
|
||||
AGC_MODE_HIGG
|
||||
};
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devList.clear();
|
||||
devListTxt = "";
|
||||
|
||||
uint64_t serials[256];
|
||||
int n = airspyhf_list_devices(serials, 256);
|
||||
|
||||
char buf[1024];
|
||||
for (int i = 0; i < n; i++) {
|
||||
sprintf(buf, "%016" PRIX64, serials[i]);
|
||||
devList.push_back(serials[i]);
|
||||
devListTxt += buf;
|
||||
devListTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (devList.size() != 0) {
|
||||
selectBySerial(devList[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void selectByString(std::string serial) {
|
||||
char buf[1024];
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
sprintf(buf, "%016" PRIX64, devList[i]);
|
||||
std::string str = buf;
|
||||
if (serial == str) {
|
||||
selectBySerial(devList[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectBySerial(uint64_t serial) {
|
||||
airspyhf_device_t* dev;
|
||||
try {
|
||||
int err = airspyhf_open_sn(&dev, selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
selectedSerial = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (std::exception e) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
}
|
||||
|
||||
selectedSerial = serial;
|
||||
|
||||
uint32_t sampleRates[256];
|
||||
airspyhf_get_samplerates(dev, sampleRates, 0);
|
||||
int n = sampleRates[0];
|
||||
airspyhf_get_samplerates(dev, sampleRates, n);
|
||||
sampleRateList.clear();
|
||||
sampleRateListTxt = "";
|
||||
for (int i = 0; i < n; i++) {
|
||||
sampleRateList.push_back(sampleRates[i]);
|
||||
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
|
||||
sampleRateListTxt += '\0';
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, serial);
|
||||
selectedSerStr = std::string(buf);
|
||||
|
||||
// Load config here
|
||||
config.acquire();
|
||||
bool created = false;
|
||||
if (!config.conf["devices"].contains(selectedSerStr)) {
|
||||
created = true;
|
||||
config.conf["devices"][selectedSerStr]["sampleRate"] = 768000;
|
||||
config.conf["devices"][selectedSerStr]["agcMode"] = 0;
|
||||
config.conf["devices"][selectedSerStr]["lna"] = false;
|
||||
config.conf["devices"][selectedSerStr]["attenuation"] = 0;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
srId = 0;
|
||||
sampleRate = sampleRateList[0];
|
||||
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
|
||||
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
|
||||
for (int i = 0; i < sampleRateList.size(); i++) {
|
||||
if (sampleRateList[i] == selectedSr) {
|
||||
srId = i;
|
||||
sampleRate = selectedSr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load Gains
|
||||
if (config.conf["devices"][selectedSerStr].contains("agcMode")) {
|
||||
agcMode = config.conf["devices"][selectedSerStr]["agcMode"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("lna")) {
|
||||
hfLNA = config.conf["devices"][selectedSerStr]["lna"];
|
||||
}
|
||||
if (config.conf["devices"][selectedSerStr].contains("attenuation")) {
|
||||
atten = config.conf["devices"][selectedSerStr]["attenuation"];
|
||||
}
|
||||
|
||||
config.release(created);
|
||||
|
||||
airspyhf_close(dev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
if (_this->selectedSerial == 0) {
|
||||
spdlog::error("Tried to start AirspyHF+ source with null serial");
|
||||
return;
|
||||
}
|
||||
|
||||
int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial);
|
||||
if (err != 0) {
|
||||
char buf[1024];
|
||||
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
|
||||
spdlog::error("Could not open Airspy HF+ {0}", buf);
|
||||
return;
|
||||
}
|
||||
|
||||
airspyhf_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
|
||||
airspyhf_set_freq(_this->openDev, _this->freq);
|
||||
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
|
||||
if (_this->agcMode > 0) {
|
||||
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
|
||||
}
|
||||
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
|
||||
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
|
||||
|
||||
airspyhf_start(_this->openDev, callback, _this);
|
||||
|
||||
_this->running = true;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
airspyhf_close(_this->openDev);
|
||||
_this->stream.clearWriteStop();
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
if (_this->running) {
|
||||
airspyhf_set_freq(_this->openDev, freq);
|
||||
}
|
||||
_this->freq = freq;
|
||||
spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (_this->running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
_this->selectBySerial(_this->devList[_this->devId]);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerStr;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
|
||||
_this->sampleRate = _this->sampleRateList[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||
if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||
_this->refresh();
|
||||
config.acquire();
|
||||
std::string devSerial = config.conf["device"];
|
||||
config.release();
|
||||
_this->selectByString(devSerial);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
if (_this->running) { style::endDisabled(); }
|
||||
|
||||
ImGui::Text("AGC Mode");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_airspyhf_agc_", _this->name), &_this->agcMode, AGG_MODES_STR)) {
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
|
||||
if (_this->agcMode > 0) {
|
||||
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["agcMode"] = _this->agcMode;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("HF LNA");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Checkbox(CONCAT("##_airspyhf_lna_", _this->name), &_this->hfLNA)) {
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["lna"] = _this->hfLNA;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Attenuation");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderFloatWithSteps(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, 6, "%.0f dB")) {
|
||||
if (_this->running) {
|
||||
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
|
||||
}
|
||||
if (_this->selectedSerStr != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerStr]["attenuation"] = _this->atten;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int callback(airspyhf_transfer_t* transfer) {
|
||||
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx;
|
||||
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
|
||||
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
airspyhf_device_t* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
uint64_t selectedSerial = 0;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int agcMode = AGC_MODE_OFF;
|
||||
bool hfLNA = false;
|
||||
float atten = 0.0f;
|
||||
std::string selectedSerStr = "";
|
||||
|
||||
std::vector<uint64_t> devList;
|
||||
std::string devListTxt;
|
||||
std::vector<uint32_t> sampleRateList;
|
||||
std::string sampleRateListTxt;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(options::opts.root + "/airspyhf_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new AirspyHFSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (AirspyHFSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
40
audio_sink/CMakeLists.txt
Normal file
40
audio_sink/CMakeLists.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(audio_sink)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
add_library(audio_sink SHARED ${SRC})
|
||||
target_link_libraries(audio_sink PRIVATE sdrpp_core)
|
||||
set_target_properties(audio_sink PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(audio_sink PUBLIC "C:/Program Files (x86)/RtAudio/lib")
|
||||
|
||||
# Misc headers
|
||||
target_include_directories(audio_sink PUBLIC "C:/Program Files (x86)/RtAudio/include/rtaudio")
|
||||
|
||||
target_link_libraries(audio_sink PUBLIC rtaudio)
|
||||
else (MSVC)
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
|
||||
|
||||
target_include_directories(audio_sink PUBLIC ${RTAUDIO_INCLUDE_DIRS})
|
||||
target_link_directories(audio_sink PUBLIC ${RTAUDIO_LIBRARY_DIRS})
|
||||
target_link_libraries(audio_sink PUBLIC ${RTAUDIO_LIBRARIES})
|
||||
|
||||
endif ()
|
||||
|
||||
# Install directives
|
||||
install(TARGETS audio_sink DESTINATION lib/sdrpp/plugins)
|
||||
299
audio_sink/src/main.cpp
Normal file
299
audio_sink/src/main.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
#include <imgui.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <signal_path/sink.h>
|
||||
#include <dsp/audio.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <RtAudio.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "audio_sink",
|
||||
/* Description: */ "Audio sink module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class AudioSink : SinkManager::Sink {
|
||||
public:
|
||||
AudioSink(SinkManager::Stream* stream, std::string streamName) {
|
||||
_stream = stream;
|
||||
_streamName = streamName;
|
||||
s2m.init(_stream->sinkOut);
|
||||
monoPacker.init(&s2m.out, 512);
|
||||
stereoPacker.init(_stream->sinkOut, 512);
|
||||
|
||||
bool created = false;
|
||||
std::string device = "";
|
||||
config.acquire();
|
||||
if (!config.conf.contains(_streamName)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["device"] = "";
|
||||
config.conf[_streamName]["devices"] = json({});
|
||||
}
|
||||
device = config.conf[_streamName]["device"];
|
||||
config.release(created);
|
||||
|
||||
int count = audio.getDeviceCount();
|
||||
RtAudio::DeviceInfo info;
|
||||
for (int i = 0; i < count; i++) {
|
||||
info = audio.getDeviceInfo(i);
|
||||
if (!info.probed) { continue; }
|
||||
if (info.outputChannels == 0) { continue; }
|
||||
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
|
||||
devList.push_back(info);
|
||||
deviceIds.push_back(i);
|
||||
txtDevList += info.name;
|
||||
txtDevList += '\0';
|
||||
}
|
||||
|
||||
selectByName(device);
|
||||
}
|
||||
|
||||
~AudioSink() {
|
||||
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
doStart();
|
||||
running = true;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
doStop();
|
||||
running = false;
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
selectById(defaultDevId);
|
||||
}
|
||||
|
||||
void selectByName(std::string name) {
|
||||
for (int i = 0; i < devList.size(); i++) {
|
||||
if (devList[i].name == name) {
|
||||
selectById(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectById(int id) {
|
||||
devId = id;
|
||||
bool created = false;
|
||||
config.acquire();
|
||||
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
|
||||
created = true;
|
||||
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
|
||||
}
|
||||
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
|
||||
config.release(created);
|
||||
|
||||
sampleRates = devList[id].sampleRates;
|
||||
sampleRatesTxt = "";
|
||||
char buf[256];
|
||||
bool found = false;
|
||||
unsigned int defaultId = 0;
|
||||
unsigned int defaultSr = devList[id].preferredSampleRate;
|
||||
for (int i = 0; i < sampleRates.size(); i++) {
|
||||
if (sampleRates[i] == sampleRate) {
|
||||
found = true;
|
||||
srId = i;
|
||||
}
|
||||
if (sampleRates[i] == defaultSr) {
|
||||
defaultId = i;
|
||||
}
|
||||
sprintf(buf, "%d", sampleRates[i]);
|
||||
sampleRatesTxt += buf;
|
||||
sampleRatesTxt += '\0';
|
||||
}
|
||||
if (!found) {
|
||||
sampleRate = defaultSr;
|
||||
srId = defaultId;
|
||||
}
|
||||
|
||||
_stream->setSampleRate(sampleRate);
|
||||
|
||||
if (running) { doStop(); }
|
||||
if (running) { doStart(); }
|
||||
}
|
||||
|
||||
void menuHandler() {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devId, txtDevList.c_str())) {
|
||||
selectById(devId);
|
||||
config.acquire();
|
||||
config.conf[_streamName]["device"] = devList[devId].name;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
|
||||
sampleRate = sampleRates[srId];
|
||||
_stream->setSampleRate(sampleRate);
|
||||
if (running) {
|
||||
doStop();
|
||||
doStart();
|
||||
}
|
||||
config.acquire();
|
||||
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void doStart() {
|
||||
RtAudio::StreamParameters parameters;
|
||||
parameters.deviceId = deviceIds[devId];
|
||||
parameters.nChannels = 2;
|
||||
unsigned int bufferFrames = sampleRate / 60;
|
||||
RtAudio::StreamOptions opts;
|
||||
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||
opts.streamName = _streamName;
|
||||
|
||||
try {
|
||||
audio.openStream(¶meters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
|
||||
stereoPacker.setSampleCount(bufferFrames);
|
||||
audio.startStream();
|
||||
stereoPacker.start();
|
||||
}
|
||||
catch ( RtAudioError& e ) {
|
||||
spdlog::error("Could not open audio device");
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::info("RtAudio stream open");
|
||||
}
|
||||
|
||||
void doStop() {
|
||||
s2m.stop();
|
||||
monoPacker.stop();
|
||||
stereoPacker.stop();
|
||||
monoPacker.out.stopReader();
|
||||
stereoPacker.out.stopReader();
|
||||
audio.stopStream();
|
||||
audio.closeStream();
|
||||
monoPacker.out.clearReadStop();
|
||||
stereoPacker.out.clearReadStop();
|
||||
}
|
||||
|
||||
static int callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) {
|
||||
AudioSink* _this = (AudioSink*)userData;
|
||||
int count = _this->stereoPacker.out.read();
|
||||
if (count < 0) { return 0; }
|
||||
|
||||
// For debug purposes only...
|
||||
// if (nBufferFrames != count) { spdlog::warn("Buffer size missmatch, wanted {0}, was asked for {1}", count, nBufferFrames); }
|
||||
// for (int i = 0; i < count; i++) {
|
||||
// if (_this->stereoPacker.out.readBuf[i].l == NAN || _this->stereoPacker.out.readBuf[i].r == NAN) { spdlog::error("NAN in audio data"); }
|
||||
// if (_this->stereoPacker.out.readBuf[i].l == INFINITY || _this->stereoPacker.out.readBuf[i].r == INFINITY) { spdlog::error("INFINITY in audio data"); }
|
||||
// if (_this->stereoPacker.out.readBuf[i].l == -INFINITY || _this->stereoPacker.out.readBuf[i].r == -INFINITY) { spdlog::error("-INFINITY in audio data"); }
|
||||
// }
|
||||
|
||||
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
|
||||
_this->stereoPacker.out.flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
SinkManager::Stream* _stream;
|
||||
dsp::StereoToMono s2m;
|
||||
dsp::Packer<float> monoPacker;
|
||||
dsp::Packer<dsp::stereo_t> stereoPacker;
|
||||
|
||||
std::string _streamName;
|
||||
|
||||
int srId = 0;
|
||||
int devCount;
|
||||
int devId = 0;
|
||||
bool running = false;
|
||||
|
||||
unsigned int defaultDevId = 0;
|
||||
|
||||
std::vector<RtAudio::DeviceInfo> devList;
|
||||
std::vector<unsigned int> deviceIds;
|
||||
std::string txtDevList;
|
||||
|
||||
std::vector<unsigned int> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
unsigned int sampleRate = 48000;
|
||||
|
||||
RtAudio audio;
|
||||
|
||||
};
|
||||
|
||||
class AudioSinkModule : public ModuleManager::Instance {
|
||||
public:
|
||||
AudioSinkModule(std::string name) {
|
||||
this->name = name;
|
||||
provider.create = create_sink;
|
||||
provider.ctx = this;
|
||||
|
||||
sigpath::sinkManager.registerSinkProvider("Audio", provider);
|
||||
}
|
||||
|
||||
~AudioSinkModule() {
|
||||
// Unregister sink, this will automatically stop and delete all instances of the audio sink
|
||||
sigpath::sinkManager.unregisterSinkProvider("Audio");
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
|
||||
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool enabled = true;
|
||||
SinkManager::SinkProvider provider;
|
||||
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
config.setPath(options::opts.root + "/audio_sink_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
|
||||
AudioSinkModule* instance = new AudioSinkModule(name);
|
||||
return instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
||||
delete (AudioSinkModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
36
bladerf_source/CMakeLists.txt
Normal file
36
bladerf_source/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(bladerf_source)
|
||||
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
|
||||
include_directories("src/")
|
||||
|
||||
file(GLOB SRC "src/*.cpp")
|
||||
|
||||
add_library(bladerf_source SHARED ${SRC})
|
||||
target_link_libraries(bladerf_source PRIVATE sdrpp_core)
|
||||
set_target_properties(bladerf_source PROPERTIES PREFIX "")
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(bladerf_source PUBLIC "C:/Program Files/PothosSDR/bin/")
|
||||
|
||||
target_link_libraries(bladerf_source PUBLIC bladeRF)
|
||||
else (MSVC)
|
||||
# Not in pkg-config
|
||||
target_link_libraries(bladerf_source PUBLIC bladeRF)
|
||||
|
||||
# Include it because for some reason pkgconfig doesn't look here?
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
target_include_directories(airspyhf_source PUBLIC "/usr/local/include")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Install directives
|
||||
install(TARGETS bladerf_source DESTINATION lib/sdrpp/plugins)
|
||||
579
bladerf_source/src/main.cpp
Normal file
579
bladerf_source/src/main.cpp
Normal file
@@ -0,0 +1,579 @@
|
||||
#include <imgui.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <module.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <gui/widgets/stepped_slider.h>
|
||||
#include <libbladeRF.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <algorithm>
|
||||
|
||||
#define CONCAT(a, b) ((std::string(a) + b).c_str())
|
||||
|
||||
#define NUM_BUFFERS 128
|
||||
#define NUM_TRANSFERS 1
|
||||
|
||||
SDRPP_MOD_INFO {
|
||||
/* Name: */ "bladerf_source",
|
||||
/* Description: */ "BladeRF source module for SDR++",
|
||||
/* Author: */ "Ryzerth",
|
||||
/* Version: */ 0, 1, 0,
|
||||
/* Max instances */ 1
|
||||
};
|
||||
|
||||
ConfigManager config;
|
||||
|
||||
class BladeRFSourceModule : public ModuleManager::Instance {
|
||||
public:
|
||||
BladeRFSourceModule(std::string name) {
|
||||
this->name = name;
|
||||
|
||||
sampleRate = 1000000.0;
|
||||
|
||||
handler.ctx = this;
|
||||
handler.selectHandler = menuSelected;
|
||||
handler.deselectHandler = menuDeselected;
|
||||
handler.menuHandler = menuHandler;
|
||||
handler.startHandler = start;
|
||||
handler.stopHandler = stop;
|
||||
handler.tuneHandler = tune;
|
||||
handler.stream = &stream;
|
||||
|
||||
refresh();
|
||||
|
||||
// Select device here
|
||||
config.acquire();
|
||||
std::string serial = config.conf["device"];
|
||||
config.release();
|
||||
selectBySerial(serial);
|
||||
|
||||
sigpath::sourceManager.registerSource("BladeRF", &handler);
|
||||
}
|
||||
|
||||
~BladeRFSourceModule() {
|
||||
stop(this);
|
||||
sigpath::sourceManager.unregisterSource("BladeRF");
|
||||
}
|
||||
|
||||
void postInit() {}
|
||||
|
||||
void enable() {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
bool isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
devListTxt = "";
|
||||
|
||||
if (devInfoList != NULL) {
|
||||
bladerf_free_device_list(devInfoList);
|
||||
}
|
||||
|
||||
devCount = bladerf_get_device_list(&devInfoList);
|
||||
if (devCount < 0) {
|
||||
spdlog::error("Could not list devices");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < devCount; i++) {
|
||||
// Keep only the first 32 character of the serial number for display
|
||||
devListTxt += std::string(devInfoList[i].serial).substr(0, 16);
|
||||
devListTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
if (devCount > 0) { selectByInfo(&devInfoList[0]); }
|
||||
else { selectedSerial = ""; }
|
||||
}
|
||||
|
||||
void selectBySerial(std::string serial, bool reloadChannelId = true) {
|
||||
if (serial == "") {
|
||||
selectFirst();
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < devCount; i++) {
|
||||
bladerf_devinfo info = devInfoList[i];
|
||||
if (serial == info.serial) {
|
||||
devId = i;
|
||||
selectByInfo(&info, reloadChannelId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
selectFirst();
|
||||
}
|
||||
|
||||
void selectByInfo(bladerf_devinfo* info, bool reloadChannelId = true) {
|
||||
int ret = bladerf_open_with_devinfo(&openDev, info);
|
||||
if (ret != 0) {
|
||||
spdlog::error("Could not open device {0}", info->serial);
|
||||
selectedSerial = "";
|
||||
return;
|
||||
}
|
||||
|
||||
selectedSerial = info->serial;
|
||||
for (int i = 0; i < devCount; i++) {
|
||||
if (selectedSerial == devInfoList[i].serial) { devId = i; }
|
||||
}
|
||||
|
||||
// Gather info about the BladeRF's ranges
|
||||
channelCount = bladerf_get_channel_count(openDev, BLADERF_RX);
|
||||
|
||||
// Load the channelId if there are more than 1 channel
|
||||
if (reloadChannelId) {
|
||||
config.acquire();
|
||||
if (channelCount > 1 && config.conf["devices"].contains(info->serial)) {
|
||||
if (config.conf["devices"][info->serial].contains("channelId")) {
|
||||
chanId = config.conf["devices"][info->serial]["channelId"];
|
||||
}
|
||||
else { chanId = 0; }
|
||||
}
|
||||
else { chanId = 0; }
|
||||
config.release();
|
||||
}
|
||||
|
||||
chanId = std::clamp<int>(chanId, 0, channelCount - 1);
|
||||
|
||||
bladerf_get_sample_rate_range(openDev, BLADERF_CHANNEL_RX(chanId), &srRange);
|
||||
bladerf_get_bandwidth_range(openDev, BLADERF_CHANNEL_RX(chanId), &bwRange);
|
||||
bladerf_get_gain_range(openDev, BLADERF_CHANNEL_RX(chanId), &gainRange);
|
||||
int gainModeCount = bladerf_get_gain_modes(openDev, BLADERF_CHANNEL_RX(chanId), &gainModes);
|
||||
|
||||
// Generate sampleRate and Bandwidth lists
|
||||
sampleRates.clear();
|
||||
sampleRatesTxt = "";
|
||||
sampleRates.push_back(srRange->min);
|
||||
sampleRatesTxt += getBandwdithScaled(srRange->min);
|
||||
sampleRatesTxt += '\0';
|
||||
for (int i = 2000000; i < srRange->max; i += 2000000) {
|
||||
sampleRates.push_back(i);
|
||||
sampleRatesTxt += getBandwdithScaled(i);
|
||||
sampleRatesTxt += '\0';
|
||||
}
|
||||
sampleRates.push_back(srRange->max);
|
||||
sampleRatesTxt += getBandwdithScaled(srRange->max);
|
||||
sampleRatesTxt += '\0';
|
||||
|
||||
// Generate bandwidth list
|
||||
bandwidths.clear();
|
||||
bandwidthsTxt = "";
|
||||
bandwidths.push_back(bwRange->min);
|
||||
bandwidthsTxt += getBandwdithScaled(bwRange->min);
|
||||
bandwidthsTxt += '\0';
|
||||
for (int i = 2000000; i < bwRange->max; i += 2000000) {
|
||||
bandwidths.push_back(i);
|
||||
bandwidthsTxt += getBandwdithScaled(i);
|
||||
bandwidthsTxt += '\0';
|
||||
}
|
||||
bandwidths.push_back(bwRange->max);
|
||||
bandwidthsTxt += getBandwdithScaled(bwRange->max);
|
||||
bandwidthsTxt += '\0';
|
||||
bandwidthsTxt += "Auto";
|
||||
bandwidthsTxt += '\0';
|
||||
|
||||
// Generate list of channel names
|
||||
channelNamesTxt = "";
|
||||
char buf[32];
|
||||
for (int i = 0; i < channelCount; i++) {
|
||||
sprintf(buf, "RX %d", i+1);
|
||||
channelNamesTxt += buf;
|
||||
channelNamesTxt += '\0';
|
||||
}
|
||||
|
||||
// Generate gain mode list
|
||||
gainModeNames.clear();
|
||||
gainModesTxt = "";
|
||||
for (int i = 0; i < gainModeCount; i++) {
|
||||
std::string gm = gainModes[i].name;
|
||||
gm[0] = gm[0] & (~0x20);
|
||||
gainModeNames.push_back(gm);
|
||||
gainModesTxt += gm;
|
||||
gainModesTxt += '\0';
|
||||
}
|
||||
|
||||
// Load settings here
|
||||
config.acquire();
|
||||
|
||||
if (!config.conf["devices"].contains(selectedSerial)) {
|
||||
config.conf["devices"][info->serial]["channelId"] = 0;
|
||||
config.conf["devices"][selectedSerial]["sampleRate"] = sampleRates[0];
|
||||
config.conf["devices"][selectedSerial]["bandwidth"] = bandwidths.size(); // Auto
|
||||
config.conf["devices"][selectedSerial]["gainMode"] = "Manual";
|
||||
config.conf["devices"][selectedSerial]["overallGain"] = gainRange->min;
|
||||
}
|
||||
|
||||
// Load sample rate
|
||||
if (config.conf["devices"][selectedSerial].contains("sampleRate")) {
|
||||
bool found = false;
|
||||
uint64_t sr = config.conf["devices"][selectedSerial]["sampleRate"];
|
||||
for (int i = 0; i < sampleRates.size(); i++) {
|
||||
if (sr == sampleRates[i]) {
|
||||
srId = i;
|
||||
sampleRate = sampleRates[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
srId = 0;
|
||||
sampleRate = sampleRates[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
srId = 0;
|
||||
sampleRate = sampleRates[0];
|
||||
}
|
||||
|
||||
// Load bandwidth
|
||||
if (config.conf["devices"][selectedSerial].contains("bandwidth")) {
|
||||
bwId = config.conf["devices"][selectedSerial]["bandwidth"];
|
||||
bwId = std::clamp<int>(bwId, 0, bandwidths.size());
|
||||
}
|
||||
else {
|
||||
bwId = 0;
|
||||
}
|
||||
config.release(true);
|
||||
|
||||
// Load gain mode
|
||||
if (config.conf["devices"][selectedSerial].contains("gainMode")) {
|
||||
std::string gm = config.conf["devices"][selectedSerial]["gainMode"];
|
||||
bool found = false;
|
||||
for (int i = 0; i < gainModeNames.size(); i++) {
|
||||
if (gainModeNames[i] == gm) {
|
||||
gainMode = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
for (int i = 0; i < gainModeNames.size(); i++) {
|
||||
if (gainModeNames[i] == "Manual") {
|
||||
gainMode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < gainModeNames.size(); i++) {
|
||||
if (gainModeNames[i] == "Manual") {
|
||||
gainMode = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load gain
|
||||
if (config.conf["devices"][selectedSerial].contains("overallGain")) {
|
||||
overallGain = config.conf["devices"][selectedSerial]["overallGain"];
|
||||
overallGain = std::clamp<int>(overallGain, gainRange->min, gainRange->max);
|
||||
}
|
||||
else {
|
||||
overallGain = gainRange->min;
|
||||
}
|
||||
|
||||
bladerf_close(openDev);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string getBandwdithScaled(double bw) {
|
||||
char buf[1024];
|
||||
if (bw >= 1000000.0) {
|
||||
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
|
||||
}
|
||||
else if (bw >= 1000.0) {
|
||||
sprintf(buf, "%.1lfKHz", bw / 1000.0);
|
||||
}
|
||||
else {
|
||||
sprintf(buf, "%.1lfHz", bw);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static void menuSelected(void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
spdlog::info("BladeRFSourceModule '{0}': Menu Select!", _this->name);
|
||||
}
|
||||
|
||||
static void menuDeselected(void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
spdlog::info("BladeRFSourceModule '{0}': Menu Deselect!", _this->name);
|
||||
}
|
||||
|
||||
static void start(void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
if (_this->running) { return; }
|
||||
if (_this->devCount == 0) { return; }
|
||||
|
||||
// Open device
|
||||
bladerf_devinfo info = _this->devInfoList[_this->devId];
|
||||
int ret = bladerf_open_with_devinfo(&_this->openDev, &info);
|
||||
if (ret != 0) {
|
||||
spdlog::error("Could not open device {0}", info.serial);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate buffer size, must be a multiple of 1024
|
||||
_this->bufferSize = _this->sampleRate / 200.0;
|
||||
_this->bufferSize /= 1024;
|
||||
_this->bufferSize *= 1024;
|
||||
if (_this->bufferSize < 1024) { _this->bufferSize = 1024; }
|
||||
|
||||
// Setup device parameters
|
||||
bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL);
|
||||
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
|
||||
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ?
|
||||
std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
|
||||
bladerf_set_gain_mode(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->gainModes[_this->gainMode].mode);
|
||||
|
||||
// If gain mode is manual, set the gain
|
||||
if (_this->gainModes[_this->gainMode].mode == BLADERF_GAIN_MANUAL) {
|
||||
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
|
||||
}
|
||||
|
||||
_this->streamingEnabled = true;
|
||||
|
||||
// Setup syncronous transfer
|
||||
bladerf_sync_config(_this->openDev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 16, _this->bufferSize, 8, 3500);
|
||||
|
||||
// Enable streaming
|
||||
bladerf_enable_module(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), true);
|
||||
|
||||
_this->running = true;
|
||||
_this->workerThread = std::thread(&BladeRFSourceModule::worker, _this);
|
||||
|
||||
spdlog::info("BladeRFSourceModule '{0}': Start!", _this->name);
|
||||
}
|
||||
|
||||
static void stop(void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
if (!_this->running) { return; }
|
||||
_this->running = false;
|
||||
_this->stream.stopWriter();
|
||||
|
||||
_this->streamingEnabled = false;
|
||||
// Wait for read worker to terminate
|
||||
if (_this->workerThread.joinable()) {
|
||||
_this->workerThread.join();
|
||||
}
|
||||
|
||||
// Disable streaming
|
||||
bladerf_enable_module(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), false);
|
||||
|
||||
// Close device
|
||||
bladerf_close(_this->openDev);
|
||||
|
||||
_this->stream.clearWriteStop();
|
||||
spdlog::info("BladeRFSourceModule '{0}': Stop!", _this->name);
|
||||
}
|
||||
|
||||
static void tune(double freq, void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
_this->freq = freq;
|
||||
if (_this->running) {
|
||||
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
|
||||
}
|
||||
spdlog::info("BladeRFSourceModule '{0}': Tune: {1}!", _this->name, freq);
|
||||
}
|
||||
|
||||
static void menuHandler(void* ctx) {
|
||||
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (_this->running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(menuWidth);
|
||||
if (ImGui::Combo(CONCAT("##_balderf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
|
||||
bladerf_devinfo info = _this->devInfoList[_this->devId];
|
||||
_this->selectByInfo(&info);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
config.acquire();
|
||||
config.conf["device"] = _this->selectedSerial;
|
||||
config.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Combo(CONCAT("##_balderf_sr_sel_", _this->name), &_this->srId, _this->sampleRatesTxt.c_str())) {
|
||||
_this->sampleRate = _this->sampleRates[_this->srId];
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["sampleRate"] = _this->sampleRates[_this->srId];
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh button
|
||||
ImGui::SameLine();
|
||||
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
|
||||
if (ImGui::Button(CONCAT("Refresh##_balderf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
|
||||
_this->refresh();
|
||||
_this->selectBySerial(_this->selectedSerial, false);
|
||||
core::setInputSampleRate(_this->sampleRate);
|
||||
}
|
||||
|
||||
// Channel selection (only show if more than one channel)
|
||||
if (_this->channelCount > 1) {
|
||||
ImGui::Text("RX Channel");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
ImGui::Combo(CONCAT("##_balderf_ch_sel_", _this->name), &_this->chanId, _this->channelNamesTxt.c_str());
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["channelId"] = _this->chanId;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->running) { style::endDisabled(); }
|
||||
|
||||
ImGui::Text("Bandwidth");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_balderf_bw_sel_", _this->name), &_this->bwId, _this->bandwidthsTxt.c_str())) {
|
||||
if (_this->running) {
|
||||
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ?
|
||||
std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
|
||||
}
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["bandwidth"] = _this->bwId;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
// General config BS
|
||||
ImGui::Text("Gain control mode");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo(CONCAT("##_balderf_gm_sel_", _this->name), &_this->gainMode, _this->gainModesTxt.c_str()) && _this->selectedSerial != "") {
|
||||
if (_this->running) {
|
||||
bladerf_set_gain_mode(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->gainModes[_this->gainMode].mode);
|
||||
}
|
||||
// if switched to manual, reset gains
|
||||
if (_this->gainModes[_this->gainMode].mode == BLADERF_GAIN_MANUAL && _this->running) {
|
||||
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
|
||||
}
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["gainMode"] = _this->gainModeNames[_this->gainMode];
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (_this->selectedSerial != "") { if (_this->gainModes[_this->gainMode].mode != BLADERF_GAIN_MANUAL) { style::beginDisabled(); } }
|
||||
ImGui::Text("Gain");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::SliderInt("##_balderf_oag_sel_", &_this->overallGain, (_this->gainRange != NULL) ? _this->gainRange->min : 0, (_this->gainRange != NULL) ? _this->gainRange->max : 60)) {
|
||||
if (_this->running) {
|
||||
spdlog::info("Setting gain to {0}", _this->overallGain);
|
||||
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
|
||||
}
|
||||
if (_this->selectedSerial != "") {
|
||||
config.acquire();
|
||||
config.conf["devices"][_this->selectedSerial]["overallGain"] = _this->overallGain;
|
||||
config.release(true);
|
||||
}
|
||||
}
|
||||
if (_this->selectedSerial != "") { if (_this->gainModes[_this->gainMode].mode != BLADERF_GAIN_MANUAL) { style::endDisabled(); } }
|
||||
|
||||
}
|
||||
|
||||
void worker() {
|
||||
int16_t* buffer = new int16_t[bufferSize * 2];
|
||||
bladerf_metadata meta;
|
||||
|
||||
while (streamingEnabled) {
|
||||
// Receive from the stream and break on error
|
||||
int ret = bladerf_sync_rx(openDev, buffer, bufferSize, &meta, 3500);
|
||||
if (ret != 0) { break; }
|
||||
|
||||
// Convert to complex float and swap buffers
|
||||
volk_16i_s32f_convert_32f((float*)stream.writeBuf, buffer, 32768.0f, bufferSize * 2);
|
||||
if (!stream.swap(bufferSize)) { break; }
|
||||
}
|
||||
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bladerf* openDev;
|
||||
bool enabled = true;
|
||||
dsp::stream<dsp::complex_t> stream;
|
||||
double sampleRate;
|
||||
SourceManager::SourceHandler handler;
|
||||
bool running = false;
|
||||
double freq;
|
||||
int devId = 0;
|
||||
int srId = 0;
|
||||
int bwId = 0;
|
||||
int chanId = 0;
|
||||
int gainMode = 0;
|
||||
bool streamingEnabled = false;
|
||||
|
||||
int channelCount;
|
||||
|
||||
const bladerf_range* srRange = NULL;
|
||||
const bladerf_range* bwRange = NULL;
|
||||
const bladerf_range* gainRange = NULL;
|
||||
|
||||
std::vector<uint64_t> sampleRates;
|
||||
std::string sampleRatesTxt;
|
||||
std::vector<uint64_t> bandwidths;
|
||||
std::string bandwidthsTxt;
|
||||
|
||||
std::string channelNamesTxt;
|
||||
|
||||
int bufferSize;
|
||||
struct bladerf_stream* rxStream;
|
||||
|
||||
int overallGain = 0;
|
||||
|
||||
std::thread workerThread;
|
||||
|
||||
int devCount = 0;
|
||||
bladerf_devinfo* devInfoList = NULL;
|
||||
std::string devListTxt;
|
||||
|
||||
std::string selectedSerial;
|
||||
|
||||
bool isBlade1 = false;
|
||||
|
||||
const bladerf_gain_modes* gainModes;
|
||||
std::vector<std::string> gainModeNames;
|
||||
std::string gainModesTxt;
|
||||
int gainModeCount;
|
||||
};
|
||||
|
||||
MOD_EXPORT void _INIT_() {
|
||||
json def = json({});
|
||||
def["devices"] = json({});
|
||||
def["device"] = "";
|
||||
config.setPath(options::opts.root + "/bladerf_config.json");
|
||||
config.load(def);
|
||||
config.enableAutoSave();
|
||||
}
|
||||
|
||||
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
||||
return new BladeRFSourceModule(name);
|
||||
}
|
||||
|
||||
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
|
||||
delete (BladeRFSourceModule*)instance;
|
||||
}
|
||||
|
||||
MOD_EXPORT void _END_() {
|
||||
config.disableAutoSave();
|
||||
config.save();
|
||||
}
|
||||
32
cmake_uninstall.cmake
Normal file
32
cmake_uninstall.cmake
Normal file
@@ -0,0 +1,32 @@
|
||||
# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F
|
||||
|
||||
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
|
||||
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
|
||||
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
|
||||
|
||||
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
|
||||
STRING(REGEX REPLACE "\n" ";" files "${files}")
|
||||
FOREACH(file ${files})
|
||||
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
|
||||
IF(EXISTS "$ENV{DESTDIR}${file}")
|
||||
EXEC_PROGRAM(
|
||||
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||
OUTPUT_VARIABLE rm_out
|
||||
RETURN_VALUE rm_retval
|
||||
)
|
||||
IF(NOT "${rm_retval}" STREQUAL 0)
|
||||
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
|
||||
ENDIF(NOT "${rm_retval}" STREQUAL 0)
|
||||
ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}")
|
||||
EXEC_PROGRAM(
|
||||
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
|
||||
OUTPUT_VARIABLE rm_out
|
||||
RETURN_VALUE rm_retval
|
||||
)
|
||||
IF(NOT "${rm_retval}" STREQUAL 0)
|
||||
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
|
||||
ENDIF(NOT "${rm_retval}" STREQUAL 0)
|
||||
ELSE(EXISTS "$ENV{DESTDIR}${file}")
|
||||
MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
|
||||
ENDIF(EXISTS "$ENV{DESTDIR}${file}")
|
||||
ENDFOREACH(file)
|
||||
125
contributing.md
Normal file
125
contributing.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 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 folowing guides will show you how to properly format the JSON files for their respective uses.
|
||||
|
||||
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
|
||||
|
||||
## Band Frequency Allocation
|
||||
|
||||
Please follow this guide to properly format the JSON files for custom radio band allocation identifiers.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Short name (has to fit in the menu)",
|
||||
"country_name": "Name of country or area, if applicable (Use '--' otherwise)",
|
||||
"country_code": "Two letter country code, if applicable (Use '--' otherwise)",
|
||||
"author_name": "Name of the original/main creator of the JSON file",
|
||||
"author_url": "URL the author wishes to be associated with the file (personal website, GitHub, Twitter, etc)",
|
||||
"bands": [
|
||||
// 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)",
|
||||
"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)",
|
||||
"start": 526500, //In Hz, must be an integer
|
||||
"end": 1606500 //In Hz, must be an integer
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Color Maps
|
||||
|
||||
Please follow this guide to properly format the JSON files for custom color maps.
|
||||
|
||||
```json
|
||||
{
|
||||
"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 hexidecimal (#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.
|
||||
"#000020",
|
||||
"#000030",
|
||||
"#000050",
|
||||
"#000091",
|
||||
"#1E90FF",
|
||||
"#FFFFFF",
|
||||
"#FFFF00",
|
||||
"#FE6D16",
|
||||
"#FE6D16",
|
||||
"#FF0000",
|
||||
"#FF0000",
|
||||
"#C60000",
|
||||
"#9F0000",
|
||||
"#750000",
|
||||
"#4A0000"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# Best Practices
|
||||
|
||||
* 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
|
||||
101
core/CMakeLists.txt
Normal file
101
core/CMakeLists.txt
Normal file
@@ -0,0 +1,101 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(sdrpp_core)
|
||||
|
||||
# Set compiler options
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
|
||||
endif ()
|
||||
add_definitions(-DSDRPP_IS_CORE)
|
||||
|
||||
# Main code
|
||||
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
|
||||
|
||||
# Add code to dyn lib
|
||||
add_library(sdrpp_core SHARED ${SRC})
|
||||
|
||||
# Set the install prefix
|
||||
target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
# Include core headers
|
||||
target_include_directories(sdrpp_core PUBLIC "src/")
|
||||
target_include_directories(sdrpp_core PUBLIC "src/imgui")
|
||||
|
||||
|
||||
if (MSVC)
|
||||
# Lib path
|
||||
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/lib/")
|
||||
|
||||
# Misc headers
|
||||
target_include_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/include/")
|
||||
|
||||
# Volk
|
||||
target_link_libraries(sdrpp_core PUBLIC volk)
|
||||
|
||||
# Glew
|
||||
find_package(GLEW REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
|
||||
|
||||
# GLFW3
|
||||
find_package(glfw3 CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC glfw)
|
||||
|
||||
# FFTW3
|
||||
find_package(FFTW3f CONFIG REQUIRED)
|
||||
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
|
||||
|
||||
# WinSock2
|
||||
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32)
|
||||
|
||||
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)
|
||||
|
||||
target_include_directories(sdrpp_core PUBLIC
|
||||
${GLEW_INCLUDE_DIRS}
|
||||
${FFTW3_INCLUDE_DIRS}
|
||||
${GLFW3_INCLUDE_DIRS}
|
||||
${VOLK_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_directories(sdrpp_core PUBLIC
|
||||
${GLEW_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
${GLFW3_LIBRARY_DIRS}
|
||||
${VOLK_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(sdrpp_core PUBLIC
|
||||
${OPENGL_LIBRARIES}
|
||||
${GLEW_LIBRARIES}
|
||||
${FFTW3_LIBRARIES}
|
||||
${GLFW3_LIBRARIES}
|
||||
${VOLK_LIBRARIES}
|
||||
)
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
|
||||
endif ()
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
|
||||
set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE)
|
||||
|
||||
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
|
||||
|
||||
# Install directives
|
||||
install(TARGETS sdrpp_core DESTINATION lib)
|
||||
102
core/src/config.cpp
Normal file
102
core/src/config.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include <config.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
ConfigManager::ConfigManager() {
|
||||
|
||||
}
|
||||
|
||||
ConfigManager::~ConfigManager() {
|
||||
disableAutoSave();
|
||||
}
|
||||
|
||||
void ConfigManager::setPath(std::string file) {
|
||||
path = file;
|
||||
}
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::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);
|
||||
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);
|
||||
conf = def;
|
||||
save(false);
|
||||
}
|
||||
if (lock) { mtx.unlock(); }
|
||||
}
|
||||
|
||||
void ConfigManager::save(bool lock) {
|
||||
if (lock) { mtx.lock(); }
|
||||
std::ofstream file(path.c_str());
|
||||
file << conf.dump(4);
|
||||
file.close();
|
||||
if (lock) { mtx.unlock(); }
|
||||
}
|
||||
|
||||
void ConfigManager::enableAutoSave() {
|
||||
if (autoSaveEnabled) { return; }
|
||||
autoSaveEnabled = true;
|
||||
termFlag = false;
|
||||
autoSaveThread = std::thread(&ConfigManager::autoSaveWorker, this);
|
||||
}
|
||||
|
||||
void ConfigManager::disableAutoSave() {
|
||||
if (!autoSaveEnabled) { return; }
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(termMtx);
|
||||
autoSaveEnabled = false;
|
||||
termFlag = true;
|
||||
}
|
||||
termCond.notify_one();
|
||||
if (autoSaveThread.joinable()) { autoSaveThread.join(); }
|
||||
}
|
||||
|
||||
void ConfigManager::acquire() {
|
||||
mtx.lock();
|
||||
}
|
||||
|
||||
void ConfigManager::release(bool modified) {
|
||||
changed |= modified;
|
||||
mtx.unlock();
|
||||
}
|
||||
|
||||
void ConfigManager::autoSaveWorker() {
|
||||
while (autoSaveEnabled) {
|
||||
if (!mtx.try_lock()) {
|
||||
spdlog::warn("ConfigManager locked, waiting...");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
continue;
|
||||
}
|
||||
if (changed) {
|
||||
changed = false;
|
||||
save(false);
|
||||
}
|
||||
mtx.unlock();
|
||||
|
||||
// Sleep but listen for wakeup call
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(termMtx);
|
||||
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } );
|
||||
}
|
||||
}
|
||||
}
|
||||
37
core/src/config.h
Normal file
37
core/src/config.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
ConfigManager();
|
||||
~ConfigManager();
|
||||
void setPath(std::string file);
|
||||
void load(json def, bool lock = true);
|
||||
void save(bool lock = true);
|
||||
void enableAutoSave();
|
||||
void disableAutoSave();
|
||||
void acquire();
|
||||
void release(bool modified = false);
|
||||
|
||||
json conf;
|
||||
|
||||
private:
|
||||
void autoSaveWorker();
|
||||
|
||||
std::string path = "";
|
||||
volatile bool changed = false;
|
||||
volatile bool autoSaveEnabled = false;
|
||||
std::thread autoSaveThread;
|
||||
std::mutex mtx;
|
||||
|
||||
std::mutex termMtx;
|
||||
std::condition_variable termCond;
|
||||
volatile bool termFlag = false;
|
||||
|
||||
};
|
||||
477
core/src/core.cpp
Normal file
477
core/src/core.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
#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 <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>
|
||||
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb_image_resize.h>
|
||||
#include <gui/gui.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#ifndef INSTALL_PREFIX
|
||||
#ifdef __APPLE__
|
||||
#define INSTALL_PREFIX "/usr/local"
|
||||
#else
|
||||
#define INSTALL_PREFIX "/usr"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace core {
|
||||
ConfigManager configManager;
|
||||
ModuleManager moduleManager;
|
||||
ModuleComManager modComManager;
|
||||
GLFWwindow* window;
|
||||
|
||||
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);
|
||||
gui::waterfall.setBandwidth(effectiveSr);
|
||||
gui::waterfall.setViewOffset(0);
|
||||
gui::waterfall.setViewBandwidth(effectiveSr);
|
||||
sigpath::signalPath.setSampleRate(effectiveSr);
|
||||
gui::mainWindow.setViewBandwidthSlider(effectiveSr);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Load default options and parse command line
|
||||
options::loadDefaults();
|
||||
if (!options::parse(argc, argv)) { return -1; }
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!options::opts.showConsole) { FreeConsole(); }
|
||||
#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);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(options::opts.root)) {
|
||||
spdlog::error("{0} is not a directory", options::opts.root);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ======== DEFAULT CONFIG ========
|
||||
json defConfig;
|
||||
defConfig["bandColors"]["amateur"] = "#FF0000FF";
|
||||
defConfig["bandColors"]["aviation"] = "#00FF00FF";
|
||||
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
|
||||
defConfig["bandColors"]["marine"] = "#00FFFFFF";
|
||||
defConfig["bandColors"]["military"] = "#FFFF00FF";
|
||||
defConfig["bandPlan"] = "General";
|
||||
defConfig["bandPlanEnabled"] = true;
|
||||
defConfig["bandPlanPos"] = 0;
|
||||
defConfig["centerTuning"] = false;
|
||||
defConfig["colorMap"] = "Classic";
|
||||
defConfig["fastFFT"] = false;
|
||||
defConfig["fftHeight"] = 300;
|
||||
defConfig["fftRate"] = 20;
|
||||
defConfig["fftSize"] = 65536;
|
||||
defConfig["fftWindow"] = 1;
|
||||
defConfig["frequency"] = 100000000.0;
|
||||
defConfig["fullWaterfallUpdate"] = false;
|
||||
defConfig["max"] = 0.0;
|
||||
defConfig["maximized"] = false;
|
||||
|
||||
// Menu
|
||||
defConfig["menuElements"] = json::array();
|
||||
|
||||
defConfig["menuElements"][0]["name"] = "Source";
|
||||
defConfig["menuElements"][0]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][1]["name"] = "Radio";
|
||||
defConfig["menuElements"][1]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][2]["name"] = "Recorder";
|
||||
defConfig["menuElements"][2]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][3]["name"] = "Sinks";
|
||||
defConfig["menuElements"][3]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][3]["name"] = "Frequency Manager";
|
||||
defConfig["menuElements"][3]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][4]["name"] = "VFO Color";
|
||||
defConfig["menuElements"][4]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][5]["name"] = "Scripting";
|
||||
defConfig["menuElements"][5]["open"] = false;
|
||||
|
||||
defConfig["menuElements"][6]["name"] = "Band Plan";
|
||||
defConfig["menuElements"][6]["open"] = true;
|
||||
|
||||
defConfig["menuElements"][7]["name"] = "Display";
|
||||
defConfig["menuElements"][7]["open"] = true;
|
||||
|
||||
defConfig["menuWidth"] = 300;
|
||||
defConfig["min"] = -120.0;
|
||||
|
||||
// Module instances
|
||||
defConfig["moduleInstances"]["Airspy Source"]["module"] = "airspy_source";
|
||||
defConfig["moduleInstances"]["Airspy Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["AirspyHF+ Source"]["module"] = "airspyhf_source";
|
||||
defConfig["moduleInstances"]["AirspyHF+ 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"]["HackRF Source"]["module"] = "hackrf_source";
|
||||
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
|
||||
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
|
||||
defConfig["moduleInstances"]["LimeSDR 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"]["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"]["Audio Sink"] = "audio_sink";
|
||||
|
||||
defConfig["moduleInstances"]["Radio"] = "radio";
|
||||
|
||||
defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager";
|
||||
defConfig["moduleInstances"]["Recorder"] = "recorder";
|
||||
defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server";
|
||||
|
||||
|
||||
// Themes
|
||||
defConfig["theme"] = "Dark";
|
||||
|
||||
defConfig["modules"] = json::array();
|
||||
defConfig["offsetMode"] = (int)0; // Off
|
||||
defConfig["offset"] = 0.0;
|
||||
defConfig["showMenu"] = true;
|
||||
defConfig["showWaterfall"] = true;
|
||||
defConfig["source"] = "";
|
||||
defConfig["decimationPower"] = 0;
|
||||
defConfig["iqCorrection"] = false;
|
||||
|
||||
defConfig["streams"]["Radio"]["muted"] = false;
|
||||
defConfig["streams"]["Radio"]["sink"] = "Audio";
|
||||
defConfig["streams"]["Radio"]["volume"] = 1.0f;
|
||||
|
||||
defConfig["windowSize"]["h"] = 720;
|
||||
defConfig["windowSize"]["w"] = 1280;
|
||||
|
||||
defConfig["vfoOffsets"] = json::object();
|
||||
|
||||
defConfig["vfoColors"]["Radio"] = "#FFFFFF";
|
||||
|
||||
#ifdef _WIN32
|
||||
defConfig["modulesDirectory"] = "./modules";
|
||||
defConfig["resourcesDirectory"] = "./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");
|
||||
core::configManager.load(defConfig);
|
||||
core::configManager.enableAutoSave();
|
||||
|
||||
|
||||
core::configManager.acquire();
|
||||
// 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());
|
||||
core::configManager.conf[item.key()] = defConfig[item.key()];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused elements
|
||||
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());
|
||||
core::configManager.conf.erase(item.key());
|
||||
}
|
||||
}
|
||||
|
||||
// Update to new module representation in config if needed
|
||||
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {
|
||||
if (!inst.is_string()) { continue; }
|
||||
std::string mod = inst;
|
||||
json newMod;
|
||||
newMod["module"] = mod;
|
||||
newMod["enabled"] = true;
|
||||
core::configManager.conf["moduleInstances"][_name] = newMod;
|
||||
}
|
||||
|
||||
core::configManager.release(true);
|
||||
|
||||
if (options::opts.serverMode) { return server_main(); }
|
||||
|
||||
// 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
|
||||
#else
|
||||
// GL 3.0 + GLSL 120
|
||||
const char* glsl_version = "#version 120";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
#endif
|
||||
|
||||
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();
|
||||
|
||||
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)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!style::loadFonts(resDir)) { return -1; }
|
||||
thememenu::init(resDir);
|
||||
|
||||
LoadingScreen::setWindow(core::window);
|
||||
|
||||
LoadingScreen::show("Loading icons");
|
||||
spdlog::info("Loading icons");
|
||||
if (!icons::load(resDir)) { return -1; }
|
||||
|
||||
LoadingScreen::show("Loading band plans");
|
||||
spdlog::info("Loading band plans");
|
||||
bandplan::loadFromDir(resDir + "/bandplans");
|
||||
|
||||
LoadingScreen::show("Loading band plan colors");
|
||||
spdlog::info("Loading band plans color table");
|
||||
bandplan::loadColorTable(bandColors);
|
||||
|
||||
gui::mainWindow.init();
|
||||
|
||||
spdlog::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);
|
||||
}
|
||||
|
||||
// Shut down all modules
|
||||
for (auto& [name, mod] : core::moduleManager.modules) {
|
||||
mod.end();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(core::window);
|
||||
glfwTerminate();
|
||||
|
||||
sigpath::signalPath.stop();
|
||||
|
||||
core::configManager.disableAutoSave();
|
||||
core::configManager.save();
|
||||
|
||||
return 0;
|
||||
}
|
||||
15
core/src/core.h
Normal file
15
core/src/core.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <config.h>
|
||||
#include <module.h>
|
||||
#include <module.h>
|
||||
#include <module_com.h>
|
||||
|
||||
namespace core {
|
||||
SDRPP_EXPORT ConfigManager configManager;
|
||||
SDRPP_EXPORT ModuleManager moduleManager;
|
||||
SDRPP_EXPORT ModuleComManager modComManager;
|
||||
|
||||
void setInputSampleRate(double samplerate);
|
||||
};
|
||||
|
||||
int sdrpp_main(int argc, char *argv[]);
|
||||
52
core/src/credits.cpp
Normal file
52
core/src/credits.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <credits.h>
|
||||
|
||||
namespace sdrpp_credits {
|
||||
const char* contributors[] = {
|
||||
"Aang23",
|
||||
"Alexsey Shestacov",
|
||||
"Aosync",
|
||||
"Benjamin Kyd",
|
||||
"Benjamin Vernoux",
|
||||
"Cropinghigh",
|
||||
"Fred F4EED",
|
||||
"Howard0su",
|
||||
"Joshua Kimsey",
|
||||
"Martin Hauke",
|
||||
"Marvin Sinister",
|
||||
"Maxime Biette",
|
||||
"Paulo Matias",
|
||||
"Raov",
|
||||
"Cam K.",
|
||||
"Szymon Zakrent",
|
||||
"Tobias Mädel",
|
||||
"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* patrons[] = {
|
||||
"Croccydile",
|
||||
"Daniele D'Agnelli",
|
||||
"EB3FRN",
|
||||
"Eric Johnson",
|
||||
"W4IPA",
|
||||
"Lee Donaghy",
|
||||
"ON4MU",
|
||||
"Passion-Radio.com",
|
||||
"Scanner School",
|
||||
"SignalsEverywhere",
|
||||
"Syne Ardwin (WI9SYN)"
|
||||
};
|
||||
|
||||
const int contributorCount = sizeof(contributors) / sizeof(char*);
|
||||
const int libraryCount = sizeof(libraries) / sizeof(char*);
|
||||
const int patronCount = sizeof(patrons) / sizeof(char*);
|
||||
}
|
||||
11
core/src/credits.h
Normal file
11
core/src/credits.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <module.h>
|
||||
|
||||
namespace sdrpp_credits {
|
||||
SDRPP_EXPORT const char* contributors[];
|
||||
SDRPP_EXPORT const char* libraries[];
|
||||
SDRPP_EXPORT const char* patrons[];
|
||||
SDRPP_EXPORT const int contributorCount;
|
||||
SDRPP_EXPORT const int libraryCount;
|
||||
SDRPP_EXPORT const int patronCount;
|
||||
}
|
||||
202
core/src/dsp/audio.h
Normal file
202
core/src/dsp/audio.h
Normal file
@@ -0,0 +1,202 @@
|
||||
#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 missmatch");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
||||
229
core/src/dsp/block.h
Normal file
229
core/src/dsp/block.h
Normal file
@@ -0,0 +1,229 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace dsp {
|
||||
|
||||
class generic_unnamed_block {
|
||||
public:
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
|
||||
virtual int run() = 0;
|
||||
|
||||
friend BLOCK;
|
||||
|
||||
private:
|
||||
void workerLoop() {
|
||||
while (run() >= 0);
|
||||
}
|
||||
|
||||
void acquire() {
|
||||
ctrlMtx.lock();
|
||||
}
|
||||
|
||||
void release() {
|
||||
ctrlMtx.unlock();
|
||||
}
|
||||
|
||||
void registerInput(untyped_stream* inStream) {
|
||||
inputs.push_back(inStream);
|
||||
}
|
||||
|
||||
void unregisterInput(untyped_stream* inStream) {
|
||||
inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end());
|
||||
}
|
||||
|
||||
void registerOutput(untyped_stream* outStream) {
|
||||
outputs.push_back(outStream);
|
||||
}
|
||||
|
||||
void unregisterOutput(untyped_stream* outStream) {
|
||||
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::vector<untyped_stream*> inputs;
|
||||
std::vector<untyped_stream*> outputs;
|
||||
|
||||
bool running = false;
|
||||
bool tempStopped = false;
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -1,33 +1,25 @@
|
||||
#pragma once
|
||||
#include <condition_variable>
|
||||
#include <algorithm>
|
||||
#include <math.h>
|
||||
#include <dsp/block.h>
|
||||
#include <string.h>
|
||||
|
||||
#define STREAM_BUF_SZ 1000000
|
||||
#define RING_BUF_SZ 1000000
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class stream {
|
||||
class RingBuffer {
|
||||
public:
|
||||
stream() {
|
||||
RingBuffer() {}
|
||||
|
||||
}
|
||||
RingBuffer(int maxLatency) { init(maxLatency); }
|
||||
|
||||
stream(int maxLatency) {
|
||||
size = STREAM_BUF_SZ;
|
||||
_buffer = new T[size];
|
||||
_stopReader = false;
|
||||
_stopWriter = false;
|
||||
this->maxLatency = maxLatency;
|
||||
writec = 0;
|
||||
readc = 0;
|
||||
readable = 0;
|
||||
writable = size;
|
||||
memset(_buffer, 0, size * sizeof(T));
|
||||
~RingBuffer() {
|
||||
if (!_init) { return; }
|
||||
delete _buffer;
|
||||
_init = false;
|
||||
}
|
||||
|
||||
void init(int maxLatency) {
|
||||
size = STREAM_BUF_SZ;
|
||||
size = RING_BUF_SZ;
|
||||
_buffer = new T[size];
|
||||
_stopReader = false;
|
||||
_stopWriter = false;
|
||||
@@ -37,9 +29,11 @@ namespace dsp {
|
||||
readable = 0;
|
||||
writable = size;
|
||||
memset(_buffer, 0, size * sizeof(T));
|
||||
_init = true;
|
||||
}
|
||||
|
||||
int read(T* data, int len) {
|
||||
assert(_init);
|
||||
int dataRead = 0;
|
||||
int toRead = 0;
|
||||
while (dataRead < len) {
|
||||
@@ -69,6 +63,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int readAndSkip(T* data, int len, int skip) {
|
||||
assert(_init);
|
||||
int dataRead = 0;
|
||||
int toRead = 0;
|
||||
while (dataRead < len) {
|
||||
@@ -114,6 +109,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int waitUntilReadable() {
|
||||
assert(_init);
|
||||
if (_stopReader) { return -1; }
|
||||
int _r = getReadable();
|
||||
if (_r != 0) { return _r; }
|
||||
@@ -124,6 +120,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int getReadable(bool lock = true) {
|
||||
assert(_init);
|
||||
if (lock) { _readable_mtx.lock(); };
|
||||
int _r = readable;
|
||||
if (lock) { _readable_mtx.unlock(); };
|
||||
@@ -131,6 +128,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int write(T* data, int len) {
|
||||
assert(_init);
|
||||
int dataWritten = 0;
|
||||
int toWrite = 0;
|
||||
while (dataWritten < len) {
|
||||
@@ -161,6 +159,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int waitUntilwritable() {
|
||||
assert(_init);
|
||||
if (_stopWriter) { return -1; }
|
||||
int _w = getWritable();
|
||||
if (_w != 0) { return _w; }
|
||||
@@ -171,6 +170,7 @@ namespace dsp {
|
||||
}
|
||||
|
||||
int getWritable(bool lock = true) {
|
||||
assert(_init);
|
||||
if (lock) { _writable_mtx.lock(); };
|
||||
int _w = writable;
|
||||
if (lock) { _writable_mtx.unlock(); _readable_mtx.lock(); };
|
||||
@@ -180,36 +180,44 @@ namespace dsp {
|
||||
}
|
||||
|
||||
void stopReader() {
|
||||
assert(_init);
|
||||
_stopReader = true;
|
||||
canReadVar.notify_one();
|
||||
}
|
||||
|
||||
void stopWriter() {
|
||||
assert(_init);
|
||||
_stopWriter = true;
|
||||
canWriteVar.notify_one();
|
||||
}
|
||||
|
||||
bool getReadStop() {
|
||||
assert(_init);
|
||||
return _stopReader;
|
||||
}
|
||||
|
||||
bool getWriteStop() {
|
||||
assert(_init);
|
||||
return _stopWriter;
|
||||
}
|
||||
|
||||
void clearReadStop() {
|
||||
assert(_init);
|
||||
_stopReader = false;
|
||||
}
|
||||
|
||||
void clearWriteStop() {
|
||||
assert(_init);
|
||||
_stopWriter = false;
|
||||
}
|
||||
|
||||
void setMaxLatency(int maxLatency) {
|
||||
assert(_init);
|
||||
this->maxLatency = maxLatency;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _init = false;
|
||||
T* _buffer;
|
||||
int size;
|
||||
int readc;
|
||||
@@ -224,4 +232,128 @@ 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;
|
||||
|
||||
};
|
||||
};
|
||||
255
core/src/dsp/clock_recovery.h
Normal file
255
core/src/dsp/clock_recovery.h
Normal file
@@ -0,0 +1,255 @@
|
||||
#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;
|
||||
|
||||
// Perfrom 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;
|
||||
|
||||
};
|
||||
}
|
||||
345
core/src/dsp/convertion.h
Normal file
345
core/src/dsp/convertion.h
Normal file
@@ -0,0 +1,345 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
76
core/src/dsp/correction.h
Normal file
76
core/src/dsp/correction.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#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;
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
107
core/src/dsp/decimation.h
Normal file
107
core/src/dsp/decimation.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
422
core/src/dsp/deframing.h
Normal file
422
core/src/dsp/deframing.h
Normal file
@@ -0,0 +1,422 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
734
core/src/dsp/demodulator.h
Normal file
734
core/src/dsp/demodulator.h
Normal file
@@ -0,0 +1,734 @@
|
||||
#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/convertion.h>
|
||||
#include <dsp/audio.h>
|
||||
#include <dsp/stereo_fm.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 MSKDemod : public generic_hier_block<MSKDemod> {
|
||||
public:
|
||||
MSKDemod() {}
|
||||
|
||||
MSKDemod(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<MSKDemod>::registerBlock(&demod);
|
||||
generic_hier_block<MSKDemod>::registerBlock(&recov);
|
||||
generic_hier_block<MSKDemod>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* input) {
|
||||
assert((generic_hier_block<MSKDemod>::_block_init));
|
||||
demod.setInput(input);
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_hier_block<MSKDemod>::_block_init);
|
||||
generic_hier_block<MSKDemod>::tempStop();
|
||||
_sampleRate = sampleRate;
|
||||
demod.setSampleRate(_sampleRate);
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
generic_hier_block<MSKDemod>::tempStart();
|
||||
}
|
||||
|
||||
void setDeviation(float deviation) {
|
||||
assert(generic_hier_block<MSKDemod>::_block_init);
|
||||
_deviation = deviation;
|
||||
demod.setDeviation(deviation);
|
||||
}
|
||||
|
||||
void setBaudRate(float baudRate, float omegaRelLimit) {
|
||||
assert(generic_hier_block<MSKDemod>::_block_init);
|
||||
_baudRate = baudRate;
|
||||
_omegaRelLimit = omegaRelLimit;
|
||||
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
|
||||
}
|
||||
|
||||
void setMMGains(float omegaGain, float myGain) {
|
||||
assert(generic_hier_block<MSKDemod>::_block_init);
|
||||
_omegaGain = omegaGain;
|
||||
_muGain = myGain;
|
||||
recov.setGains(_omegaGain, _muGain);
|
||||
}
|
||||
|
||||
void setOmegaRelLimit(float omegaRelLimit) {
|
||||
assert(generic_hier_block<MSKDemod>::_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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
134
core/src/dsp/falcon_fec.h
Normal file
134
core/src/dsp/falcon_fec.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
128
core/src/dsp/falcon_packet.h
Normal file
128
core/src/dsp/falcon_packet.h
Normal file
@@ -0,0 +1,128 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
272
core/src/dsp/filter.h
Normal file
272
core/src/dsp/filter.h
Normal file
@@ -0,0 +1,272 @@
|
||||
#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;
|
||||
|
||||
};
|
||||
}
|
||||
136
core/src/dsp/interpolation_taps.h
Normal file
136
core/src/dsp/interpolation_taps.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#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 },
|
||||
};
|
||||
248
core/src/dsp/math.h
Normal file
248
core/src/dsp/math.h
Normal file
@@ -0,0 +1,248 @@
|
||||
#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>) {
|
||||
volk_32fc_x2_add_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_a->readBuf, (lv_32fc_t*)_b->readBuf, a_count);
|
||||
}
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
||||
82
core/src/dsp/measure.h
Normal file
82
core/src/dsp/measure.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <fftw3.h>
|
||||
#include <volk/volk.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <dsp/types.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace dsp {
|
||||
class LevelMeter : public generic_block<LevelMeter> {
|
||||
public:
|
||||
LevelMeter() {}
|
||||
|
||||
LevelMeter(stream<stereo_t>* in) { init(in); }
|
||||
|
||||
void init(stream<stereo_t>* in) {
|
||||
_in = in;
|
||||
generic_block<LevelMeter>::registerInput(_in);
|
||||
generic_block<LevelMeter>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<stereo_t>* in) {
|
||||
assert(generic_block<LevelMeter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<LevelMeter>::ctrlMtx);
|
||||
generic_block<LevelMeter>::tempStop();
|
||||
generic_block<LevelMeter>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<LevelMeter>::registerInput(_in);
|
||||
generic_block<LevelMeter>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
float maxL = 0, maxR = 0;
|
||||
float absL, absR;
|
||||
for (int i = 0; i < count; i++) {
|
||||
absL = fabs(_in->readBuf[i].l);
|
||||
absR = fabs(_in->readBuf[i].r);
|
||||
if (absL > maxL) { maxL = absL; }
|
||||
if (absR > maxR) { maxR = absR; }
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
|
||||
float _lvlL = 10.0f * logf(maxL);
|
||||
float _lvlR = 10.0f * logf(maxR);
|
||||
|
||||
// Update max values
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(lvlMtx);
|
||||
if (_lvlL > lvlL) { lvlL = _lvlL; }
|
||||
if (_lvlR > lvlR) { lvlR = _lvlR; }
|
||||
}
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
float getLeftLevel() {
|
||||
std::lock_guard<std::mutex> lck(lvlMtx);
|
||||
float ret = lvlL;
|
||||
lvlL = -90.0f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float getRightLevel() {
|
||||
std::lock_guard<std::mutex> lck(lvlMtx);
|
||||
float ret = lvlR;
|
||||
lvlR = -90.0f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
float lvlL = -90.0f;
|
||||
float lvlR = -90.0f;
|
||||
stream<stereo_t>* _in;
|
||||
std::mutex lvlMtx;
|
||||
|
||||
};
|
||||
}
|
||||
67
core/src/dsp/meteor/hrpt.h
Normal file
67
core/src/dsp/meteor/hrpt.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/utils/bitstream.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace meteor {
|
||||
class HRPTDemux : public generic_block<HRPTDemux> {
|
||||
public:
|
||||
HRPTDemux() {}
|
||||
|
||||
HRPTDemux(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<HRPTDemux>::registerInput(_in);
|
||||
generic_block<HRPTDemux>::registerOutput(&telemOut);
|
||||
generic_block<HRPTDemux>::registerOutput(&BISMout);
|
||||
generic_block<HRPTDemux>::registerOutput(&SSPDOut);
|
||||
generic_block<HRPTDemux>::registerOutput(&MTVZAOut);
|
||||
generic_block<HRPTDemux>::registerOutput(&MSUMROut);
|
||||
generic_block<HRPTDemux>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<HRPTDemux>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HRPTDemux>::ctrlMtx);
|
||||
generic_block<HRPTDemux>::tempStop();
|
||||
generic_block<HRPTDemux>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HRPTDemux>::registerInput(_in);
|
||||
generic_block<HRPTDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
memcpy(telemOut.writeBuf + (i * 2), _in->readBuf + 4 + (i * 256), 2);
|
||||
memcpy(BISMout.writeBuf + (i * 4), _in->readBuf + 4 + (i * 256) + 2, 4);
|
||||
memcpy(SSPDOut.writeBuf + (i * 4), _in->readBuf + 4 + (i * 256) + 6, 4);
|
||||
memcpy(MTVZAOut.writeBuf + (i * 8), _in->readBuf + 4 + (i * 256) + 10, 8);
|
||||
memcpy(MSUMROut.writeBuf + (i * 238), _in->readBuf + 4 + (i * 256) + 18, (i == 3) ? 234 : 238);
|
||||
}
|
||||
|
||||
if (!telemOut.swap(8)) { return -1; }
|
||||
if (!BISMout.swap(16)) { return -1; }
|
||||
if (!SSPDOut.swap(16)) { return -1; }
|
||||
if (!MTVZAOut.swap(32)) { return -1; }
|
||||
if (!MSUMROut.swap(948)) { return -1; }
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> telemOut;
|
||||
stream<uint8_t> BISMout;
|
||||
stream<uint8_t> SSPDOut;
|
||||
stream<uint8_t> MTVZAOut;
|
||||
stream<uint8_t> MSUMROut;
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
78
core/src/dsp/meteor/msumr.h
Normal file
78
core/src/dsp/meteor/msumr.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/utils/bitstream.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace meteor {
|
||||
const uint64_t MSUMR_SYNC_WORD = 0x0218A7A392DD9ABF;
|
||||
const uint8_t MSUMR_SYNC_BYTES[8] = { 0x02, 0x18, 0xA7, 0xA3, 0x92, 0xDD, 0x9A, 0xBF };
|
||||
|
||||
class MSUMRDemux : public generic_block<MSUMRDemux> {
|
||||
public:
|
||||
MSUMRDemux() {}
|
||||
|
||||
MSUMRDemux(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<MSUMRDemux>::registerInput(_in);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr1Out);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr2Out);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr3Out);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr4Out);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr5Out);
|
||||
generic_block<MSUMRDemux>::registerOutput(&msumr6Out);
|
||||
generic_block<MSUMRDemux>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<MSUMRDemux>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<MSUMRDemux>::ctrlMtx);
|
||||
generic_block<MSUMRDemux>::tempStop();
|
||||
generic_block<MSUMRDemux>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<MSUMRDemux>::registerInput(_in);
|
||||
generic_block<MSUMRDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int pixels = 0;
|
||||
for (int i = 0; i < 11790; i += 30) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
msumr1Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10), 10, _in->readBuf);
|
||||
msumr2Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 1), 10, _in->readBuf);
|
||||
msumr3Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 2), 10, _in->readBuf);
|
||||
msumr4Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 3), 10, _in->readBuf);
|
||||
msumr5Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 4), 10, _in->readBuf);
|
||||
msumr6Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 5), 10, _in->readBuf);
|
||||
}
|
||||
pixels += 4;
|
||||
}
|
||||
|
||||
if (!msumr1Out.swap(1572)) { return -1; }
|
||||
if (!msumr2Out.swap(1572)) { return -1; }
|
||||
if (!msumr3Out.swap(1572)) { return -1; }
|
||||
if (!msumr4Out.swap(1572)) { return -1; }
|
||||
if (!msumr5Out.swap(1572)) { return -1; }
|
||||
if (!msumr6Out.swap(1572)) { return -1; }
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint16_t> msumr1Out;
|
||||
stream<uint16_t> msumr2Out;
|
||||
stream<uint16_t> msumr3Out;
|
||||
stream<uint16_t> msumr4Out;
|
||||
stream<uint16_t> msumr5Out;
|
||||
stream<uint16_t> msumr6Out;
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
111
core/src/dsp/noaa/hrpt.h
Normal file
111
core/src/dsp/noaa/hrpt.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/utils/bitstream.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace noaa {
|
||||
inline uint16_t HRPTReadWord(int offset, uint8_t* buffer) {
|
||||
return (uint16_t)readBits(offset * 10, 10, buffer);
|
||||
}
|
||||
|
||||
const uint8_t HRPTSyncWord[] = {
|
||||
1,0,1,0,0,0,0,1,0,0,
|
||||
0,1,0,1,1,0,1,1,1,1,
|
||||
1,1,0,1,0,1,1,1,0,0,
|
||||
0,1,1,0,0,1,1,1,0,1,
|
||||
1,0,0,0,0,0,1,1,1,1,
|
||||
0,0,1,0,0,1,0,1,0,1
|
||||
};
|
||||
|
||||
class HRPTDemux : public generic_block<HRPTDemux> {
|
||||
public:
|
||||
HRPTDemux() {}
|
||||
|
||||
HRPTDemux(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<HRPTDemux>::registerInput(_in);
|
||||
generic_block<HRPTDemux>::registerOutput(&AVHRRChan1Out);
|
||||
generic_block<HRPTDemux>::registerOutput(&AVHRRChan2Out);
|
||||
generic_block<HRPTDemux>::registerOutput(&AVHRRChan3Out);
|
||||
generic_block<HRPTDemux>::registerOutput(&AVHRRChan4Out);
|
||||
generic_block<HRPTDemux>::registerOutput(&AVHRRChan5Out);
|
||||
generic_block<HRPTDemux>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<HRPTDemux>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HRPTDemux>::ctrlMtx);
|
||||
generic_block<HRPTDemux>::tempStop();
|
||||
generic_block<HRPTDemux>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HRPTDemux>::registerInput(_in);
|
||||
generic_block<HRPTDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int minFrame = readBits(61, 2, _in->readBuf);
|
||||
|
||||
// If GAC frame, reject
|
||||
if (minFrame == 0) {
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
// Extract TIP frames if present
|
||||
if (minFrame == 1) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 104; j++) {
|
||||
TIPOut.writeBuf[j] = (HRPTReadWord(103 + (i * 104) + j, _in->readBuf) >> 2) & 0xFF;
|
||||
}
|
||||
if (!TIPOut.swap(104)) { return -1; };
|
||||
}
|
||||
}
|
||||
|
||||
// Exctact AIP otherwise
|
||||
else if (minFrame == 3) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 104; j++) {
|
||||
AIPOut.writeBuf[j] = (HRPTReadWord(103 + (i * 104) + j, _in->readBuf) >> 2) & 0xFF;
|
||||
}
|
||||
if (!AIPOut.swap(104)) { return -1; };
|
||||
}
|
||||
}
|
||||
|
||||
// Extract AVHRR data
|
||||
for (int i = 0; i < 2048; i++) {
|
||||
AVHRRChan1Out.writeBuf[i] = HRPTReadWord(750 + (i * 5), _in->readBuf);
|
||||
AVHRRChan2Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 1, _in->readBuf);
|
||||
AVHRRChan3Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 2, _in->readBuf);
|
||||
AVHRRChan4Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 3, _in->readBuf);
|
||||
AVHRRChan5Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 4, _in->readBuf);
|
||||
}
|
||||
if (!AVHRRChan1Out.swap(2048)) { return -1; };
|
||||
if (!AVHRRChan2Out.swap(2048)) { return -1; };
|
||||
if (!AVHRRChan3Out.swap(2048)) { return -1; };
|
||||
if (!AVHRRChan4Out.swap(2048)) { return -1; };
|
||||
if (!AVHRRChan5Out.swap(2048)) { return -1; };
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> TIPOut;
|
||||
stream<uint8_t> AIPOut;
|
||||
|
||||
stream<uint16_t> AVHRRChan1Out;
|
||||
stream<uint16_t> AVHRRChan2Out;
|
||||
stream<uint16_t> AVHRRChan3Out;
|
||||
stream<uint16_t> AVHRRChan4Out;
|
||||
stream<uint16_t> AVHRRChan5Out;
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
242
core/src/dsp/noaa/tip.h
Normal file
242
core/src/dsp/noaa/tip.h
Normal file
@@ -0,0 +1,242 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/utils/bitstream.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace noaa {
|
||||
class TIPDemux : public generic_block<TIPDemux> {
|
||||
public:
|
||||
TIPDemux() {}
|
||||
|
||||
TIPDemux(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<TIPDemux>::registerInput(_in);
|
||||
generic_block<TIPDemux>::registerOutput(&HIRSOut);
|
||||
generic_block<TIPDemux>::registerOutput(&SEMOut);
|
||||
generic_block<TIPDemux>::registerOutput(&DCSOut);
|
||||
generic_block<TIPDemux>::registerOutput(&SBUVOut);
|
||||
generic_block<TIPDemux>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
assert(generic_block<TIPDemux>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<TIPDemux>::ctrlMtx);
|
||||
generic_block<TIPDemux>::tempStop();
|
||||
generic_block<TIPDemux>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<TIPDemux>::registerInput(_in);
|
||||
generic_block<TIPDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// Extract HIRS
|
||||
HIRSOut.writeBuf[0] = _in->readBuf[16];
|
||||
HIRSOut.writeBuf[1] = _in->readBuf[17];
|
||||
HIRSOut.writeBuf[2] = _in->readBuf[22];
|
||||
HIRSOut.writeBuf[3] = _in->readBuf[23];
|
||||
HIRSOut.writeBuf[4] = _in->readBuf[26];
|
||||
HIRSOut.writeBuf[5] = _in->readBuf[27];
|
||||
HIRSOut.writeBuf[6] = _in->readBuf[30];
|
||||
HIRSOut.writeBuf[7] = _in->readBuf[31];
|
||||
HIRSOut.writeBuf[8] = _in->readBuf[34];
|
||||
HIRSOut.writeBuf[9] = _in->readBuf[35];
|
||||
HIRSOut.writeBuf[10] = _in->readBuf[38];
|
||||
HIRSOut.writeBuf[11] = _in->readBuf[39];
|
||||
HIRSOut.writeBuf[12] = _in->readBuf[42];
|
||||
HIRSOut.writeBuf[13] = _in->readBuf[43];
|
||||
HIRSOut.writeBuf[14] = _in->readBuf[54];
|
||||
HIRSOut.writeBuf[15] = _in->readBuf[55];
|
||||
HIRSOut.writeBuf[16] = _in->readBuf[58];
|
||||
HIRSOut.writeBuf[17] = _in->readBuf[59];
|
||||
HIRSOut.writeBuf[18] = _in->readBuf[62];
|
||||
HIRSOut.writeBuf[19] = _in->readBuf[63];
|
||||
HIRSOut.writeBuf[20] = _in->readBuf[66];
|
||||
HIRSOut.writeBuf[21] = _in->readBuf[67];
|
||||
HIRSOut.writeBuf[22] = _in->readBuf[70];
|
||||
HIRSOut.writeBuf[23] = _in->readBuf[71];
|
||||
HIRSOut.writeBuf[24] = _in->readBuf[74];
|
||||
HIRSOut.writeBuf[25] = _in->readBuf[75];
|
||||
HIRSOut.writeBuf[26] = _in->readBuf[78];
|
||||
HIRSOut.writeBuf[27] = _in->readBuf[79];
|
||||
HIRSOut.writeBuf[28] = _in->readBuf[82];
|
||||
HIRSOut.writeBuf[29] = _in->readBuf[83];
|
||||
HIRSOut.writeBuf[30] = _in->readBuf[84];
|
||||
HIRSOut.writeBuf[31] = _in->readBuf[85];
|
||||
HIRSOut.writeBuf[32] = _in->readBuf[88];
|
||||
HIRSOut.writeBuf[33] = _in->readBuf[89];
|
||||
HIRSOut.writeBuf[34] = _in->readBuf[92];
|
||||
HIRSOut.writeBuf[35] = _in->readBuf[93];
|
||||
if (!HIRSOut.swap(36)) { return -1; };
|
||||
|
||||
// Extract SEM
|
||||
SEMOut.writeBuf[0] = _in->readBuf[20];
|
||||
SEMOut.writeBuf[1] = _in->readBuf[21];
|
||||
if (!SEMOut.swap(2)) { return -1; };
|
||||
|
||||
// Extract DCS
|
||||
DCSOut.writeBuf[0] = _in->readBuf[18];
|
||||
DCSOut.writeBuf[1] = _in->readBuf[19];
|
||||
DCSOut.writeBuf[2] = _in->readBuf[24];
|
||||
DCSOut.writeBuf[3] = _in->readBuf[25];
|
||||
DCSOut.writeBuf[4] = _in->readBuf[28];
|
||||
DCSOut.writeBuf[5] = _in->readBuf[29];
|
||||
DCSOut.writeBuf[6] = _in->readBuf[32];
|
||||
DCSOut.writeBuf[7] = _in->readBuf[33];
|
||||
DCSOut.writeBuf[8] = _in->readBuf[40];
|
||||
DCSOut.writeBuf[9] = _in->readBuf[41];
|
||||
DCSOut.writeBuf[10] = _in->readBuf[44];
|
||||
DCSOut.writeBuf[11] = _in->readBuf[45];
|
||||
DCSOut.writeBuf[12] = _in->readBuf[52];
|
||||
DCSOut.writeBuf[13] = _in->readBuf[53];
|
||||
DCSOut.writeBuf[14] = _in->readBuf[56];
|
||||
DCSOut.writeBuf[15] = _in->readBuf[57];
|
||||
DCSOut.writeBuf[16] = _in->readBuf[60];
|
||||
DCSOut.writeBuf[17] = _in->readBuf[61];
|
||||
DCSOut.writeBuf[18] = _in->readBuf[64];
|
||||
DCSOut.writeBuf[19] = _in->readBuf[65];
|
||||
DCSOut.writeBuf[20] = _in->readBuf[68];
|
||||
DCSOut.writeBuf[21] = _in->readBuf[69];
|
||||
DCSOut.writeBuf[22] = _in->readBuf[72];
|
||||
DCSOut.writeBuf[23] = _in->readBuf[73];
|
||||
DCSOut.writeBuf[24] = _in->readBuf[76];
|
||||
DCSOut.writeBuf[25] = _in->readBuf[77];
|
||||
DCSOut.writeBuf[26] = _in->readBuf[86];
|
||||
DCSOut.writeBuf[27] = _in->readBuf[87];
|
||||
DCSOut.writeBuf[28] = _in->readBuf[90];
|
||||
DCSOut.writeBuf[29] = _in->readBuf[91];
|
||||
DCSOut.writeBuf[30] = _in->readBuf[94];
|
||||
DCSOut.writeBuf[31] = _in->readBuf[95];
|
||||
if (!DCSOut.swap(32)) { return -1; };
|
||||
|
||||
// Extract SBUV
|
||||
SBUVOut.writeBuf[0] = _in->readBuf[36];
|
||||
SBUVOut.writeBuf[1] = _in->readBuf[37];
|
||||
SBUVOut.writeBuf[2] = _in->readBuf[80];
|
||||
SBUVOut.writeBuf[3] = _in->readBuf[81];
|
||||
if (!SBUVOut.swap(4)) { return -1; };
|
||||
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> HIRSOut;
|
||||
stream<uint8_t> SEMOut;
|
||||
stream<uint8_t> DCSOut;
|
||||
stream<uint8_t> SBUVOut;
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
inline uint16_t HIRSSignedToUnsigned(uint16_t n) {
|
||||
return (n & 0x1000) ? (0x1000 + (n & 0xFFF)) : (0xFFF - (n & 0xFFF));
|
||||
}
|
||||
|
||||
class HIRSDemux : public generic_block<HIRSDemux> {
|
||||
public:
|
||||
HIRSDemux() {}
|
||||
|
||||
HIRSDemux(stream<uint8_t>* in) { init(in); }
|
||||
|
||||
void init(stream<uint8_t>* in) {
|
||||
_in = in;
|
||||
generic_block<HIRSDemux>::registerInput(_in);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
generic_block<HIRSDemux>::registerOutput(&radChannels[i]);
|
||||
}
|
||||
|
||||
// Clear buffers
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
|
||||
}
|
||||
}
|
||||
|
||||
void setInput(stream<uint8_t>* in) {
|
||||
std::lock_guard<std::mutex> lck(generic_block<HIRSDemux>::ctrlMtx);
|
||||
generic_block<HIRSDemux>::tempStop();
|
||||
generic_block<HIRSDemux>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HIRSDemux>::registerInput(_in);
|
||||
generic_block<HIRSDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
int element = readBits(19, 6, _in->readBuf);
|
||||
|
||||
// If we've skipped or are on a non image element and there's data avilable, send it
|
||||
if ((element < lastElement || element > 55) && newImageData) {
|
||||
newImageData = false;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (!radChannels[i].swap(56)) { return -1; }
|
||||
}
|
||||
|
||||
// Clear buffers
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
|
||||
}
|
||||
}
|
||||
lastElement = element;
|
||||
|
||||
// If data is part of a line, save it
|
||||
if (element <= 55) {
|
||||
newImageData = true;
|
||||
|
||||
radChannels[0].writeBuf[element] = HIRSSignedToUnsigned(readBits(26, 13, _in->readBuf));
|
||||
radChannels[1].writeBuf[element] = HIRSSignedToUnsigned(readBits(52, 13, _in->readBuf));
|
||||
radChannels[2].writeBuf[element] = HIRSSignedToUnsigned(readBits(65, 13, _in->readBuf));
|
||||
radChannels[3].writeBuf[element] = HIRSSignedToUnsigned(readBits(91, 13, _in->readBuf));
|
||||
radChannels[4].writeBuf[element] = HIRSSignedToUnsigned(readBits(221, 13, _in->readBuf));
|
||||
radChannels[5].writeBuf[element] = HIRSSignedToUnsigned(readBits(208, 13, _in->readBuf));
|
||||
radChannels[6].writeBuf[element] = HIRSSignedToUnsigned(readBits(143, 13, _in->readBuf));
|
||||
radChannels[7].writeBuf[element] = HIRSSignedToUnsigned(readBits(156, 13, _in->readBuf));
|
||||
radChannels[8].writeBuf[element] = HIRSSignedToUnsigned(readBits(273, 13, _in->readBuf));
|
||||
radChannels[9].writeBuf[element] = HIRSSignedToUnsigned(readBits(182, 13, _in->readBuf));
|
||||
radChannels[10].writeBuf[element] = HIRSSignedToUnsigned(readBits(119, 13, _in->readBuf));
|
||||
radChannels[11].writeBuf[element] = HIRSSignedToUnsigned(readBits(247, 13, _in->readBuf));
|
||||
radChannels[12].writeBuf[element] = HIRSSignedToUnsigned(readBits(78, 13, _in->readBuf));
|
||||
radChannels[13].writeBuf[element] = HIRSSignedToUnsigned(readBits(195, 13, _in->readBuf));
|
||||
radChannels[14].writeBuf[element] = HIRSSignedToUnsigned(readBits(234, 13, _in->readBuf));
|
||||
radChannels[15].writeBuf[element] = HIRSSignedToUnsigned(readBits(260, 13, _in->readBuf));
|
||||
radChannels[16].writeBuf[element] = HIRSSignedToUnsigned(readBits(39, 13, _in->readBuf));
|
||||
radChannels[17].writeBuf[element] = HIRSSignedToUnsigned(readBits(104, 13, _in->readBuf));
|
||||
radChannels[18].writeBuf[element] = HIRSSignedToUnsigned(readBits(130, 13, _in->readBuf));
|
||||
radChannels[19].writeBuf[element] = HIRSSignedToUnsigned(readBits(169, 13, _in->readBuf));
|
||||
}
|
||||
|
||||
// If we are writing the last pixel of a line, send it already
|
||||
if (element == 55) {
|
||||
newImageData = false;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (!radChannels[i].swap(56)) { return -1; }
|
||||
}
|
||||
|
||||
// Clear buffers
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
|
||||
}
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint16_t> radChannels[20];
|
||||
|
||||
private:
|
||||
stream<uint8_t>* _in;
|
||||
int lastElement = 0;
|
||||
bool newImageData = false;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
325
core/src/dsp/pll.h
Normal file
325
core/src/dsp/pll.h
Normal file
@@ -0,0 +1,325 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/interpolation_taps.h>
|
||||
#include <math.h>
|
||||
#include <dsp/utils/macros.h>
|
||||
#include <dsp/math.h>
|
||||
|
||||
namespace dsp {
|
||||
template <int ORDER>
|
||||
class CostasLoop: public generic_block<CostasLoop<ORDER>> {
|
||||
public:
|
||||
CostasLoop() {}
|
||||
|
||||
CostasLoop(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
|
||||
|
||||
void init(stream<complex_t>* in, float loopBandwidth) {
|
||||
_in = in;
|
||||
lastVCO.re = 1.0f;
|
||||
lastVCO.im = 0.0f;
|
||||
_loopBandwidth = loopBandwidth;
|
||||
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
|
||||
generic_block<CostasLoop<ORDER>>::registerInput(_in);
|
||||
generic_block<CostasLoop<ORDER>>::registerOutput(&out);
|
||||
generic_block<CostasLoop<ORDER>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<CostasLoop<ORDER>>::_block_init);
|
||||
generic_block<CostasLoop<ORDER>>::tempStop();
|
||||
generic_block<CostasLoop<ORDER>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<CostasLoop<ORDER>>::registerInput(_in);
|
||||
generic_block<CostasLoop<ORDER>>::tempStart();
|
||||
}
|
||||
|
||||
void setLoopBandwidth(float loopBandwidth) {
|
||||
assert(generic_block<CostasLoop<ORDER>>::_block_init);
|
||||
generic_block<CostasLoop<ORDER>>::tempStop();
|
||||
_loopBandwidth = loopBandwidth;
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
generic_block<CostasLoop<ORDER>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
complex_t outVal;
|
||||
float error;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
// Mix the VFO with the input to create the output value
|
||||
outVal.re = (lastVCO.re*_in->readBuf[i].re) - (lastVCO.im*_in->readBuf[i].im);
|
||||
outVal.im = (lastVCO.im*_in->readBuf[i].re) + (lastVCO.re*_in->readBuf[i].im);
|
||||
out.writeBuf[i] = outVal;
|
||||
|
||||
// Calculate the phase error estimation
|
||||
if constexpr (ORDER == 2) {
|
||||
error = outVal.re * outVal.im;
|
||||
}
|
||||
if constexpr (ORDER == 4) {
|
||||
error = (DSP_STEP(outVal.re) * outVal.im) - (DSP_STEP(outVal.im) * outVal.re);
|
||||
}
|
||||
if constexpr (ORDER == 8) {
|
||||
// This is taken from GR, I have no idea how it works but it does...
|
||||
const float K = (sqrtf(2.0) - 1);
|
||||
if (fabsf(outVal.re) >= fabsf(outVal.im)) {
|
||||
error = ((outVal.re > 0.0f ? 1.0f : -1.0f) * outVal.im -
|
||||
(outVal.im > 0.0f ? 1.0f : -1.0f) * outVal.re * K);
|
||||
} else {
|
||||
error = ((outVal.re > 0.0f ? 1.0f : -1.0f) * outVal.im * K -
|
||||
(outVal.im > 0.0f ? 1.0f : -1.0f) * outVal.re);
|
||||
}
|
||||
}
|
||||
|
||||
if (error > 1.0f) { error = 1.0f; }
|
||||
else if (error < -1.0f) { error = -1.0f; }
|
||||
|
||||
// Integrate frequency and clamp it
|
||||
vcoFrequency += _beta * error;
|
||||
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
|
||||
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
|
||||
|
||||
// Calculate new phase and wrap it
|
||||
vcoPhase += vcoFrequency + (_alpha * error);
|
||||
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
|
||||
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
|
||||
|
||||
// Calculate output
|
||||
lastVCO.re = cosf(-vcoPhase);
|
||||
lastVCO.im = sinf(-vcoPhase);
|
||||
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float _loopBandwidth = 1.0f;
|
||||
|
||||
float _alpha; // Integral coefficient
|
||||
float _beta; // Proportional coefficient
|
||||
float vcoFrequency = 0.0f;
|
||||
float vcoPhase = 0.0f;
|
||||
complex_t lastVCO;
|
||||
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class CarrierTrackingPLL: public generic_block<CarrierTrackingPLL<T>> {
|
||||
public:
|
||||
CarrierTrackingPLL() {}
|
||||
|
||||
CarrierTrackingPLL(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
|
||||
|
||||
void init(stream<complex_t>* in, float loopBandwidth) {
|
||||
_in = in;
|
||||
lastVCO.re = 1.0f;
|
||||
lastVCO.im = 0.0f;
|
||||
_loopBandwidth = loopBandwidth;
|
||||
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
|
||||
generic_block<CarrierTrackingPLL<T>>::registerInput(_in);
|
||||
generic_block<CarrierTrackingPLL<T>>::registerOutput(&out);
|
||||
generic_block<CarrierTrackingPLL<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<CarrierTrackingPLL<T>>::_block_init);
|
||||
generic_block<CarrierTrackingPLL<T>>::tempStop();
|
||||
generic_block<CarrierTrackingPLL<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<CarrierTrackingPLL<T>>::registerInput(_in);
|
||||
generic_block<CarrierTrackingPLL<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setLoopBandwidth(float loopBandwidth) {
|
||||
assert(generic_block<CarrierTrackingPLL<T>>::_block_init);
|
||||
generic_block<CarrierTrackingPLL<T>>::tempStop();
|
||||
_loopBandwidth = loopBandwidth;
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
generic_block<CarrierTrackingPLL<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
complex_t outVal;
|
||||
float error;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
// Mix the VFO with the input to create the output value
|
||||
outVal.re = (lastVCO.re*_in->readBuf[i].re) - ((-lastVCO.im)*_in->readBuf[i].im);
|
||||
outVal.im = ((-lastVCO.im)*_in->readBuf[i].re) + (lastVCO.re*_in->readBuf[i].im);
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
out.writeBuf[i] = outVal.fastPhase();
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
out.writeBuf[i] = outVal;
|
||||
}
|
||||
|
||||
// Calculate the phase error estimation
|
||||
// TODO: Figure out why fastPhase doesn't work
|
||||
error = _in->readBuf[i].phase() - vcoPhase;
|
||||
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
|
||||
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
|
||||
|
||||
// if (error > 1.0f) { error = 1.0f; }
|
||||
// else if (error < -1.0f) { error = -1.0f; }
|
||||
|
||||
// Integrate frequency and clamp it
|
||||
vcoFrequency += _beta * error;
|
||||
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
|
||||
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
|
||||
|
||||
// Calculate new phase and wrap it
|
||||
vcoPhase += vcoFrequency + (_alpha * error);
|
||||
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
|
||||
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
|
||||
|
||||
// Calculate output
|
||||
lastVCO.re = cosf(vcoPhase);
|
||||
lastVCO.im = sinf(vcoPhase);
|
||||
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
float _loopBandwidth = 1.0f;
|
||||
|
||||
float _alpha; // Integral coefficient
|
||||
float _beta; // Proportional coefficient
|
||||
float vcoFrequency = 0.0f;
|
||||
float vcoPhase = 0.0f;
|
||||
complex_t lastVCO;
|
||||
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class PLL: public generic_block<PLL> {
|
||||
public:
|
||||
PLL() {}
|
||||
|
||||
PLL(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
|
||||
|
||||
void init(stream<complex_t>* in, float loopBandwidth) {
|
||||
_in = in;
|
||||
lastVCO.re = 1.0f;
|
||||
lastVCO.im = 0.0f;
|
||||
_loopBandwidth = loopBandwidth;
|
||||
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
|
||||
generic_block<PLL>::registerInput(_in);
|
||||
generic_block<PLL>::registerOutput(&out);
|
||||
generic_block<PLL>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<PLL>::_block_init);
|
||||
generic_block<PLL>::tempStop();
|
||||
generic_block<PLL>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<PLL>::registerInput(_in);
|
||||
generic_block<PLL>::tempStart();
|
||||
}
|
||||
|
||||
void setLoopBandwidth(float loopBandwidth) {
|
||||
assert(generic_block<PLL>::_block_init);
|
||||
generic_block<PLL>::tempStop();
|
||||
_loopBandwidth = loopBandwidth;
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
generic_block<PLL>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
complex_t outVal;
|
||||
float error;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = lastVCO;
|
||||
|
||||
// Calculate the phase error estimation
|
||||
// TODO: Figure out why fastPhase doesn't work
|
||||
error = _in->readBuf[i].phase() - vcoPhase;
|
||||
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
|
||||
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
|
||||
|
||||
// Integrate frequency and clamp it
|
||||
vcoFrequency += _beta * error;
|
||||
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
|
||||
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
|
||||
|
||||
// Calculate new phase and wrap it
|
||||
vcoPhase += vcoFrequency + (_alpha * error);
|
||||
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
|
||||
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
|
||||
|
||||
// Calculate output
|
||||
lastVCO.re = cosf(vcoPhase);
|
||||
lastVCO.im = sinf(vcoPhase);
|
||||
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float _loopBandwidth = 1.0f;
|
||||
|
||||
float _alpha; // Integral coefficient
|
||||
float _beta; // Proportional coefficient
|
||||
float vcoFrequency = ((19000.0f / 250000.0f) * 2.0f * FL_M_PI);
|
||||
float vcoPhase = 0.0f;
|
||||
complex_t lastVCO;
|
||||
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
}
|
||||
618
core/src/dsp/processing.h
Normal file
618
core/src/dsp/processing.h
Normal file
@@ -0,0 +1,618 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <volk/volk.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <dsp/math.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class FrequencyXlator : public generic_block<FrequencyXlator<T>> {
|
||||
public:
|
||||
FrequencyXlator() {}
|
||||
|
||||
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
|
||||
|
||||
void init(stream<complex_t>* in, float sampleRate, float freq) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_freq = freq;
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
generic_block<FrequencyXlator<T>>::registerInput(_in);
|
||||
generic_block<FrequencyXlator<T>>::registerOutput(&out);
|
||||
generic_block<FrequencyXlator<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<FrequencyXlator<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx);
|
||||
generic_block<FrequencyXlator<T>>::tempStop();
|
||||
generic_block<FrequencyXlator<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FrequencyXlator<T>>::registerInput(_in);
|
||||
generic_block<FrequencyXlator<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<FrequencyXlator<T>>::_block_init);
|
||||
_sampleRate = sampleRate;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
assert(generic_block<FrequencyXlator<T>>::_block_init);
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setFrequency(float freq) {
|
||||
assert(generic_block<FrequencyXlator<T>>::_block_init);
|
||||
_freq = freq;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getFrequency() {
|
||||
assert(generic_block<FrequencyXlator<T>>::_block_init);
|
||||
return _freq;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
// TODO: Do float xlation
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT");
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t>) {
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float _sampleRate;
|
||||
float _freq;
|
||||
lv_32fc_t phaseDelta;
|
||||
lv_32fc_t phase;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class AGC : public generic_block<AGC> {
|
||||
public:
|
||||
AGC() {}
|
||||
|
||||
AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); }
|
||||
|
||||
void init(stream<float>* in, float fallRate, float sampleRate) {
|
||||
_in = in;
|
||||
_sampleRate = sampleRate;
|
||||
_fallRate = fallRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
generic_block<AGC>::registerInput(_in);
|
||||
generic_block<AGC>::registerOutput(&out);
|
||||
generic_block<AGC>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<AGC>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
generic_block<AGC>::tempStop();
|
||||
generic_block<AGC>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<AGC>::registerInput(_in);
|
||||
generic_block<AGC>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<AGC>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
_sampleRate = sampleRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
}
|
||||
|
||||
void setFallRate(float fallRate) {
|
||||
assert(generic_block<AGC>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
|
||||
_fallRate = fallRate;
|
||||
_CorrectedFallRate = _fallRate / _sampleRate;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
level = pow(10, ((10.0f * log10f(level)) - (_CorrectedFallRate * count)) / 10.0f);
|
||||
|
||||
if (level < 10e-14) { level = 10e-14; }
|
||||
|
||||
float absVal;
|
||||
for (int i = 0; i < count; i++) {
|
||||
absVal = fabsf(_in->readBuf[i]);
|
||||
if (absVal > level) { level = absVal; }
|
||||
}
|
||||
|
||||
|
||||
volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count);
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> out;
|
||||
|
||||
private:
|
||||
float level = 0.0f;
|
||||
float _fallRate;
|
||||
float _CorrectedFallRate;
|
||||
float _sampleRate;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class ComplexAGC : public generic_block<ComplexAGC> {
|
||||
public:
|
||||
ComplexAGC() {}
|
||||
|
||||
ComplexAGC(stream<complex_t>* in, float setPoint, float maxGain, float rate) { init(in, setPoint, maxGain, rate); }
|
||||
|
||||
void init(stream<complex_t>* in, float setPoint, float maxGain, float rate) {
|
||||
_in = in;
|
||||
_setPoint = setPoint;
|
||||
_maxGain = maxGain;
|
||||
_rate = rate;
|
||||
generic_block<ComplexAGC>::registerInput(_in);
|
||||
generic_block<ComplexAGC>::registerOutput(&out);
|
||||
generic_block<ComplexAGC>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<ComplexAGC>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<ComplexAGC>::ctrlMtx);
|
||||
generic_block<ComplexAGC>::tempStop();
|
||||
generic_block<ComplexAGC>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<ComplexAGC>::registerInput(_in);
|
||||
generic_block<ComplexAGC>::tempStart();
|
||||
}
|
||||
|
||||
void setSetPoint(float setPoint) {
|
||||
assert(generic_block<ComplexAGC>::_block_init);
|
||||
_setPoint = setPoint;
|
||||
}
|
||||
|
||||
void setMaxGain(float maxGain) {
|
||||
assert(generic_block<ComplexAGC>::_block_init);
|
||||
_maxGain = maxGain;
|
||||
}
|
||||
|
||||
void setRate(float rate) {
|
||||
assert(generic_block<ComplexAGC>::_block_init);
|
||||
_rate = rate;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
dsp::complex_t val;
|
||||
for (int i = 0; i < count; i++) {
|
||||
val = _in->readBuf[i] * _gain;
|
||||
out.writeBuf[i] = val;
|
||||
_gain += (_setPoint - val.amplitude()) * _rate;
|
||||
if (_gain > _maxGain) { _gain = _maxGain; }
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float _gain = 1.0f;
|
||||
float _setPoint = 1.0f;
|
||||
float _maxGain = 10e4;
|
||||
float _rate = 10e-4;
|
||||
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
class DelayImag : public generic_block<DelayImag> {
|
||||
public:
|
||||
DelayImag() {}
|
||||
|
||||
DelayImag(stream<complex_t>* in) { init(in); }
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
generic_block<DelayImag>::registerInput(_in);
|
||||
generic_block<DelayImag>::registerOutput(&out);
|
||||
generic_block<DelayImag>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<DelayImag>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<DelayImag>::ctrlMtx);
|
||||
generic_block<DelayImag>::tempStop();
|
||||
generic_block<DelayImag>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<DelayImag>::registerInput(_in);
|
||||
generic_block<DelayImag>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
dsp::complex_t val;
|
||||
for (int i = 0; i < count; i++) {
|
||||
val = _in->readBuf[i];
|
||||
out.writeBuf[i].re = val.re;
|
||||
out.writeBuf[i].im = lastIm;
|
||||
lastIm = val.im;
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
float lastIm = 0.0f;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <class T>
|
||||
class Volume : public generic_block<Volume<T>> {
|
||||
public:
|
||||
Volume() {}
|
||||
|
||||
Volume(stream<T>* in, float volume) { init(in, volume); }
|
||||
|
||||
void init(stream<T>* in, float volume) {
|
||||
_in = in;
|
||||
_volume = volume;
|
||||
generic_block<Volume<T>>::registerInput(_in);
|
||||
generic_block<Volume<T>>::registerOutput(&out);
|
||||
generic_block<Volume<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<Volume<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx);
|
||||
generic_block<Volume<T>>::tempStop();
|
||||
generic_block<Volume<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Volume<T>>::registerInput(_in);
|
||||
generic_block<Volume<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setVolume(float volume) {
|
||||
assert(generic_block<Volume<T>>::_block_init);
|
||||
_volume = volume;
|
||||
level = powf(_volume, 2);
|
||||
}
|
||||
|
||||
float getVolume() {
|
||||
assert(generic_block<Volume<T>>::_block_init);
|
||||
return _volume;
|
||||
}
|
||||
|
||||
void setMuted(bool muted) {
|
||||
assert(generic_block<Volume<T>>::_block_init);
|
||||
_muted = muted;
|
||||
}
|
||||
|
||||
bool getMuted() {
|
||||
assert(generic_block<Volume<T>>::_block_init);
|
||||
return _muted;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (_muted) {
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
memset(out.writeBuf, 0, sizeof(stereo_t) * count);
|
||||
}
|
||||
else {
|
||||
memset(out.writeBuf, 0, sizeof(float) * count);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if constexpr (std::is_same_v<T, stereo_t>) {
|
||||
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count * 2);
|
||||
}
|
||||
else {
|
||||
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count);
|
||||
}
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
float level = 1.0f;
|
||||
float _volume = 1.0f;
|
||||
bool _muted = false;
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
|
||||
class Squelch : public generic_block<Squelch> {
|
||||
public:
|
||||
Squelch() {}
|
||||
|
||||
Squelch(stream<complex_t>* in, float level) { init(in, level); }
|
||||
|
||||
~Squelch() {
|
||||
if (!generic_block<Squelch>::_block_init) { return; }
|
||||
generic_block<Squelch>::stop();
|
||||
delete[] normBuffer;
|
||||
generic_block<Squelch>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in, float level) {
|
||||
_in = in;
|
||||
_level = level;
|
||||
normBuffer = new float[STREAM_BUFFER_SIZE];
|
||||
generic_block<Squelch>::registerInput(_in);
|
||||
generic_block<Squelch>::registerOutput(&out);
|
||||
generic_block<Squelch>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<Squelch>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx);
|
||||
generic_block<Squelch>::tempStop();
|
||||
generic_block<Squelch>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Squelch>::registerInput(_in);
|
||||
generic_block<Squelch>::tempStart();
|
||||
}
|
||||
|
||||
void setLevel(float level) {
|
||||
assert(generic_block<Squelch>::_block_init);
|
||||
_level = level;
|
||||
}
|
||||
|
||||
float getLevel() {
|
||||
assert(generic_block<Squelch>::_block_init);
|
||||
return _level;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
float sum;
|
||||
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count);
|
||||
volk_32f_accumulator_s32f(&sum, normBuffer, count);
|
||||
sum /= (float)count;
|
||||
|
||||
if (10.0f * log10f(sum) >= _level) {
|
||||
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
|
||||
}
|
||||
else {
|
||||
memset(out.writeBuf, 0, count * sizeof(complex_t));
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
|
||||
private:
|
||||
float* normBuffer;
|
||||
float _level = -50.0f;
|
||||
stream<complex_t>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class Packer : public generic_block<Packer<T>> {
|
||||
public:
|
||||
Packer() {}
|
||||
|
||||
Packer(stream<T>* in, int count) { init(in, count); }
|
||||
|
||||
void init(stream<T>* in, int count) {
|
||||
_in = in;
|
||||
samples = count;
|
||||
generic_block<Packer<T>>::registerInput(_in);
|
||||
generic_block<Packer<T>>::registerOutput(&out);
|
||||
generic_block<Packer<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<Packer<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Packer<T>>::ctrlMtx);
|
||||
generic_block<Packer<T>>::tempStop();
|
||||
generic_block<Packer<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Packer<T>>::registerInput(_in);
|
||||
generic_block<Packer<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setSampleCount(int count) {
|
||||
assert(generic_block<Packer<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Packer<T>>::ctrlMtx);
|
||||
generic_block<Packer<T>>::tempStop();
|
||||
samples = count;
|
||||
generic_block<Packer<T>>::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;
|
||||
|
||||
};
|
||||
|
||||
class Threshold : public generic_block<Threshold> {
|
||||
public:
|
||||
Threshold() {}
|
||||
|
||||
Threshold(stream<float>* in) { init(in); }
|
||||
|
||||
~Threshold() {
|
||||
if (!generic_block<Threshold>::_block_init) { return; }
|
||||
generic_block<Threshold>::stop();
|
||||
delete[] normBuffer;
|
||||
generic_block<Threshold>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<float>* in) {
|
||||
_in = in;
|
||||
normBuffer = new float[STREAM_BUFFER_SIZE];
|
||||
generic_block<Threshold>::registerInput(_in);
|
||||
generic_block<Threshold>::registerOutput(&out);
|
||||
generic_block<Threshold>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<float>* in) {
|
||||
assert(generic_block<Threshold>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Threshold>::ctrlMtx);
|
||||
generic_block<Threshold>::tempStop();
|
||||
generic_block<Threshold>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Threshold>::registerInput(_in);
|
||||
generic_block<Threshold>::tempStart();
|
||||
}
|
||||
|
||||
void setLevel(float level) {
|
||||
assert(generic_block<Threshold>::_block_init);
|
||||
_level = level;
|
||||
}
|
||||
|
||||
float getLevel() {
|
||||
assert(generic_block<Threshold>::_block_init);
|
||||
return _level;
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
out.writeBuf[i] = (_in->readBuf[i] > 0.0f);
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<uint8_t> out;
|
||||
|
||||
|
||||
private:
|
||||
float* normBuffer;
|
||||
float _level = -50.0f;
|
||||
stream<float>* _in;
|
||||
|
||||
};
|
||||
|
||||
class BFMPilotToStereo : public generic_block<BFMPilotToStereo> {
|
||||
public:
|
||||
BFMPilotToStereo() {}
|
||||
|
||||
BFMPilotToStereo(stream<complex_t>* in) { init(in); }
|
||||
|
||||
~BFMPilotToStereo() {
|
||||
generic_block<BFMPilotToStereo>::stop();
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
void init(stream<complex_t>* in) {
|
||||
_in = in;
|
||||
|
||||
buffer = new complex_t[STREAM_BUFFER_SIZE];
|
||||
|
||||
generic_block<BFMPilotToStereo>::registerInput(_in);
|
||||
generic_block<BFMPilotToStereo>::registerOutput(&out);
|
||||
generic_block<BFMPilotToStereo>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInputs(stream<complex_t>* in) {
|
||||
assert(generic_block<BFMPilotToStereo>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<BFMPilotToStereo>::ctrlMtx);
|
||||
generic_block<BFMPilotToStereo>::tempStop();
|
||||
generic_block<BFMPilotToStereo>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<BFMPilotToStereo>::registerInput(_in);
|
||||
generic_block<BFMPilotToStereo>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
volk_32fc_x2_multiply_32fc((lv_32fc_t*)buffer, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, count);
|
||||
_in->flush();
|
||||
|
||||
volk_32fc_conjugate_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)buffer, count);
|
||||
|
||||
if (!out.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
complex_t* buffer;
|
||||
|
||||
};
|
||||
}
|
||||
219
core/src/dsp/resampling.h
Normal file
219
core/src/dsp/resampling.h
Normal file
@@ -0,0 +1,219 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <numeric>
|
||||
#include <string.h>
|
||||
#include <dsp/math.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> {
|
||||
public:
|
||||
PolyphaseResampler() {}
|
||||
|
||||
PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); }
|
||||
|
||||
~PolyphaseResampler() {
|
||||
if (!generic_block<PolyphaseResampler<T>>::_block_init) { return; }
|
||||
generic_block<PolyphaseResampler<T>>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
freeTapPhases();
|
||||
generic_block<PolyphaseResampler<T>>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) {
|
||||
_in = in;
|
||||
_window = window;
|
||||
_inSampleRate = inSampleRate;
|
||||
_outSampleRate = outSampleRate;
|
||||
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
|
||||
tapCount = _window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
_window->createTaps(taps, tapCount, _interp);
|
||||
|
||||
buildTapPhases();
|
||||
|
||||
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
|
||||
memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2);
|
||||
counter = 0;
|
||||
offset = 0;
|
||||
generic_block<PolyphaseResampler<T>>::registerInput(_in);
|
||||
generic_block<PolyphaseResampler<T>>::registerOutput(&out);
|
||||
generic_block<PolyphaseResampler<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
generic_block<PolyphaseResampler<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
counter = 0;
|
||||
offset = 0;
|
||||
generic_block<PolyphaseResampler<T>>::registerInput(_in);
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setInSampleRate(float inSampleRate) {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_inSampleRate = inSampleRate;
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
buildTapPhases();
|
||||
counter = 0;
|
||||
offset = 0;
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate) {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_outSampleRate = outSampleRate;
|
||||
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
|
||||
_interp = _outSampleRate / _gcd;
|
||||
_decim = _inSampleRate / _gcd;
|
||||
buildTapPhases();
|
||||
counter = 0;
|
||||
offset = 0;
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
int getInterpolation() {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
return _interp;
|
||||
}
|
||||
|
||||
int getDecimation() {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
return _decim;
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_window* window) {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
|
||||
generic_block<PolyphaseResampler<T>>::tempStop();
|
||||
_window = window;
|
||||
volk_free(taps);
|
||||
tapCount = window->getTapCount();
|
||||
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
|
||||
window->createTaps(taps, tapCount, _interp);
|
||||
buildTapPhases();
|
||||
counter = 0;
|
||||
offset = 0;
|
||||
generic_block<PolyphaseResampler<T>>::tempStart();
|
||||
}
|
||||
|
||||
int calcOutSize(int in) {
|
||||
assert(generic_block<PolyphaseResampler<T>>::_block_init);
|
||||
return (in * _interp) / _decim;
|
||||
}
|
||||
|
||||
virtual int run() override {
|
||||
int count = _in->read();
|
||||
if (count < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(&buffer[tapsPerPhase], _in->readBuf, count * sizeof(T));
|
||||
_in->flush();
|
||||
|
||||
// Write to output
|
||||
int outIndex = 0;
|
||||
int inOffset = offset;
|
||||
int _counter = counter;
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
while (inOffset < count) {
|
||||
volk_32f_x2_dot_prod_32f(&out.writeBuf[outIndex++], &buffer[inOffset], tapPhases[_counter], tapsPerPhase);
|
||||
_counter += _decim;
|
||||
inOffset += (_counter / _interp);
|
||||
_counter = (_counter % _interp);
|
||||
}
|
||||
}
|
||||
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
|
||||
while (inOffset < count) {
|
||||
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex++], (lv_32fc_t*)&buffer[inOffset], tapPhases[_counter], tapsPerPhase);
|
||||
_counter += _decim;
|
||||
inOffset += (_counter / _interp);
|
||||
_counter = (_counter % _interp);
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.swap(outIndex)) { return -1; }
|
||||
|
||||
offset = inOffset - count;
|
||||
counter = _counter;
|
||||
|
||||
memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T));
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
void buildTapPhases(){
|
||||
if(!taps){
|
||||
return;
|
||||
}
|
||||
|
||||
if(!tapPhases.empty()){
|
||||
freeTapPhases();
|
||||
}
|
||||
|
||||
int phases = _interp;
|
||||
tapsPerPhase = (tapCount+phases-1)/phases; //Integer division ceiling
|
||||
|
||||
bufStart = &buffer[tapsPerPhase];
|
||||
|
||||
for(int i = 0; i < phases; i++){
|
||||
tapPhases.push_back((float*)volk_malloc(tapsPerPhase * sizeof(float), volk_get_alignment()));
|
||||
}
|
||||
|
||||
int currentTap = 0;
|
||||
for(int tap = 0; tap < tapsPerPhase; tap++) {
|
||||
for (int phase = 0; phase < phases; phase++) {
|
||||
if(currentTap < tapCount) {
|
||||
tapPhases[(_interp - 1) - phase][tap] = taps[currentTap++];
|
||||
}
|
||||
else{
|
||||
tapPhases[(_interp - 1) - phase][tap] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void freeTapPhases(){
|
||||
for(auto & tapPhase : tapPhases){
|
||||
volk_free(tapPhase);
|
||||
}
|
||||
tapPhases.clear();
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
|
||||
dsp::filter_window::generic_window* _window;
|
||||
|
||||
T* bufStart;
|
||||
T* buffer;
|
||||
int tapCount;
|
||||
int _interp, _decim;
|
||||
float _inSampleRate, _outSampleRate;
|
||||
float* taps;
|
||||
|
||||
int counter = 0;
|
||||
int offset = 0;
|
||||
|
||||
int tapsPerPhase;
|
||||
std::vector<float*> tapPhases;
|
||||
|
||||
};
|
||||
}
|
||||
197
core/src/dsp/routing.h
Normal file
197
core/src/dsp/routing.h
Normal file
@@ -0,0 +1,197 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/buffer.h>
|
||||
#include <string.h>
|
||||
#include <numeric>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class Splitter : public generic_block<Splitter<T>> {
|
||||
public:
|
||||
Splitter() {}
|
||||
|
||||
Splitter(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
generic_block<Splitter>::registerInput(_in);
|
||||
generic_block<Splitter>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<Splitter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
generic_block<Splitter>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Splitter>::registerInput(_in);
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
void bindStream(stream<T>* stream) {
|
||||
assert(generic_block<Splitter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
out.push_back(stream);
|
||||
generic_block<Splitter>::registerOutput(stream);
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
void unbindStream(stream<T>* stream) {
|
||||
assert(generic_block<Splitter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
|
||||
generic_block<Splitter>::tempStop();
|
||||
generic_block<Splitter>::unregisterOutput(stream);
|
||||
out.erase(std::remove(out.begin(), out.end(), stream), out.end());
|
||||
generic_block<Splitter>::tempStart();
|
||||
}
|
||||
|
||||
private:
|
||||
int run() {
|
||||
// TODO: If too slow, buffering might be necessary
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
for (const auto& stream : out) {
|
||||
memcpy(stream->writeBuf, _in->readBuf, count * sizeof(T));
|
||||
if (!stream->swap(count)) { return -1; }
|
||||
}
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
std::vector<stream<T>*> out;
|
||||
|
||||
};
|
||||
|
||||
|
||||
// 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 generic_block<Reshaper<T>> {
|
||||
public:
|
||||
Reshaper() {}
|
||||
|
||||
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
|
||||
|
||||
// NOTE: For some reason, the base class destrcutor 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 (!generic_block<Reshaper<T>>::_block_init) { return; }
|
||||
generic_block<Reshaper<T>>::stop();
|
||||
}
|
||||
|
||||
void init(stream<T>* in, int keep, int skip) {
|
||||
_in = in;
|
||||
_keep = keep;
|
||||
_skip = skip;
|
||||
ringBuf.init(keep * 2);
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::registerOutput(&out);
|
||||
generic_block<Reshaper<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<Reshaper<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
generic_block<Reshaper<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<Reshaper<T>>::registerInput(_in);
|
||||
generic_block<Reshaper<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setKeep(int keep) {
|
||||
assert(generic_block<Reshaper<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
_keep = keep;
|
||||
ringBuf.setMaxLatency(keep * 2);
|
||||
generic_block<Reshaper<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setSkip(int skip) {
|
||||
assert(generic_block<Reshaper<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
|
||||
generic_block<Reshaper<T>>::tempStop();
|
||||
_skip = skip;
|
||||
generic_block<Reshaper<T>>::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;
|
||||
|
||||
};
|
||||
}
|
||||
193
core/src/dsp/sink.h
Normal file
193
core/src/dsp/sink.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/buffer.h>
|
||||
#include <fstream>
|
||||
|
||||
namespace dsp {
|
||||
template <class T>
|
||||
class HandlerSink : public generic_block<HandlerSink<T>> {
|
||||
public:
|
||||
HandlerSink() {}
|
||||
|
||||
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
|
||||
|
||||
void init(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) {
|
||||
_in = in;
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSink<T>>::registerInput(_in);
|
||||
generic_block<HandlerSink<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<HandlerSink<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
|
||||
generic_block<HandlerSink<T>>::tempStop();
|
||||
generic_block<HandlerSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<HandlerSink<T>>::registerInput(_in);
|
||||
generic_block<HandlerSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) {
|
||||
assert(generic_block<HandlerSink<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
|
||||
generic_block<HandlerSink<T>>::tempStop();
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
_handler(_in->readBuf, count, _ctx);
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
stream<T>* _in;
|
||||
void (*_handler)(T* data, int count, void* ctx);
|
||||
void* _ctx;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class RingBufferSink : public generic_block<RingBufferSink<T>> {
|
||||
public:
|
||||
RingBufferSink() {}
|
||||
|
||||
RingBufferSink(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
data.init(480); // TODO: Use an argument
|
||||
generic_block<RingBufferSink<T>>::registerInput(_in);
|
||||
generic_block<RingBufferSink<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<RingBufferSink<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx);
|
||||
generic_block<RingBufferSink<T>>::tempStop();
|
||||
generic_block<RingBufferSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<RingBufferSink<T>>::registerInput(_in);
|
||||
generic_block<RingBufferSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
if (data.write(_in->readBuf, count) < 0) { return -1; }
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
RingBuffer<T> data;
|
||||
|
||||
private:
|
||||
void doStop() {
|
||||
_in->stopReader();
|
||||
data.stopWriter();
|
||||
if (generic_block<RingBufferSink<T>>::workerThread.joinable()) {
|
||||
generic_block<RingBufferSink<T>>::workerThread.join();
|
||||
}
|
||||
_in->clearReadStop();
|
||||
data.clearWriteStop();
|
||||
}
|
||||
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class NullSink : public generic_block<NullSink<T>> {
|
||||
public:
|
||||
NullSink() {}
|
||||
|
||||
NullSink(stream<T>* in) { init(in); }
|
||||
|
||||
void init(stream<T>* in) {
|
||||
_in = in;
|
||||
generic_block<NullSink<T>>::registerInput(_in);
|
||||
generic_block<NullSink<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<NullSink<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx);
|
||||
generic_block<NullSink<T>>::tempStop();
|
||||
generic_block<NullSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<NullSink<T>>::registerInput(_in);
|
||||
generic_block<NullSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
stream<T>* _in;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class FileSink : public generic_block<FileSink<T>> {
|
||||
public:
|
||||
FileSink() {}
|
||||
|
||||
FileSink(stream<T>* in, std::string path) { init(in, path); }
|
||||
|
||||
~FileSink() {
|
||||
if (!generic_block<FileSink<T>>::_block_init) { return; }
|
||||
generic_block<FileSink<T>>::stop();
|
||||
if (file.is_open()) { file.close(); }
|
||||
generic_block<FileSink<T>>::_block_init = false;
|
||||
}
|
||||
|
||||
void init(stream<T>* in, std::string path) {
|
||||
_in = in;
|
||||
file = std::ofstream(path, std::ios::binary);
|
||||
generic_block<FileSink<T>>::registerInput(_in);
|
||||
generic_block<FileSink<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<T>* in) {
|
||||
assert(generic_block<FileSink<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FileSink<T>>::ctrlMtx);
|
||||
generic_block<FileSink<T>>::tempStop();
|
||||
generic_block<FileSink<T>>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FileSink<T>>::registerInput(_in);
|
||||
generic_block<FileSink<T>>::tempStart();
|
||||
}
|
||||
|
||||
bool isOpen() {
|
||||
assert(generic_block<FileSink<T>>::_block_init);
|
||||
return file.is_open();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _in->read();
|
||||
if (count < 0) { return -1; }
|
||||
|
||||
if (file.is_open()) {
|
||||
file.write((char*)_in->readBuf, count * sizeof(T));
|
||||
}
|
||||
|
||||
_in->flush();
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
stream<T>* _in;
|
||||
std::ofstream file;
|
||||
|
||||
};
|
||||
}
|
||||
116
core/src/dsp/source.h
Normal file
116
core/src/dsp/source.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/math.h>
|
||||
|
||||
namespace dsp {
|
||||
class SineSource : public generic_block<SineSource> {
|
||||
public:
|
||||
SineSource() {}
|
||||
|
||||
SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); }
|
||||
|
||||
void init(int blockSize, float sampleRate, float freq) {
|
||||
_blockSize = blockSize;
|
||||
_sampleRate = sampleRate;
|
||||
_freq = freq;
|
||||
zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment());
|
||||
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
||||
zeroPhase[i] = lv_cmake(1.0f, 0.0f);
|
||||
}
|
||||
phase = lv_cmake(1.0f, 0.0f);
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
generic_block<SineSource>::registerOutput(&out);
|
||||
generic_block<SineSource>::_block_init = true;
|
||||
}
|
||||
|
||||
void setBlockSize(int blockSize) {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx);
|
||||
generic_block<SineSource>::tempStop();
|
||||
_blockSize = blockSize;
|
||||
generic_block<SineSource>::tempStart();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
return _blockSize;
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
_sampleRate = sampleRate;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getSampleRate() {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
return _sampleRate;
|
||||
}
|
||||
|
||||
void setFrequency(float freq) {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
_freq = freq;
|
||||
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
|
||||
}
|
||||
|
||||
float getFrequency() {
|
||||
assert(generic_block<SineSource>::_block_init);
|
||||
return _freq;
|
||||
}
|
||||
|
||||
int run() {
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, zeroPhase, phaseDelta, &phase, _blockSize);
|
||||
if(!out.swap(_blockSize)) { return -1; }
|
||||
return _blockSize;
|
||||
}
|
||||
|
||||
stream<complex_t> out;
|
||||
|
||||
private:
|
||||
int _blockSize;
|
||||
float _sampleRate;
|
||||
float _freq;
|
||||
lv_32fc_t phaseDelta;
|
||||
lv_32fc_t phase;
|
||||
lv_32fc_t* zeroPhase;
|
||||
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class HandlerSource : public generic_block<HandlerSource<T>> {
|
||||
public:
|
||||
HandlerSource() {}
|
||||
|
||||
HandlerSource(int (*handler)(T* data, void* ctx), void* ctx) { init(handler, ctx); }
|
||||
|
||||
void init(int (*handler)(T* data, void* ctx), void* ctx) {
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSource<T>>::registerOutput(&out);
|
||||
generic_block<HandlerSource<T>>::_block_init = true;
|
||||
}
|
||||
|
||||
void setHandler(int (*handler)(T* data, void* ctx), void* ctx) {
|
||||
assert(generic_block<HandlerSource<T>>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<HandlerSource<T>>::ctrlMtx);
|
||||
generic_block<HandlerSource<T>>::tempStop();
|
||||
_handler = handler;
|
||||
_ctx = ctx;
|
||||
generic_block<HandlerSource<T>>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _handler(out.writeBuf, _ctx);
|
||||
if (count < 0) { return -1; }
|
||||
out.swap(count);
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<T> out;
|
||||
|
||||
private:
|
||||
int (*_handler)(T* data, void* ctx);
|
||||
void* _ctx;
|
||||
|
||||
};
|
||||
}
|
||||
289
core/src/dsp/stereo_fm.h
Normal file
289
core/src/dsp/stereo_fm.h
Normal file
@@ -0,0 +1,289 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <dsp/types.h>
|
||||
|
||||
namespace dsp {
|
||||
|
||||
class FMStereoDemuxPilotFilter : public generic_block<FMStereoDemuxPilotFilter> {
|
||||
public:
|
||||
FMStereoDemuxPilotFilter() {}
|
||||
|
||||
FMStereoDemuxPilotFilter(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) { init(in, window); }
|
||||
|
||||
~FMStereoDemuxPilotFilter() {
|
||||
if (!generic_block<FMStereoDemuxPilotFilter>::_block_init) { return; }
|
||||
generic_block<FMStereoDemuxPilotFilter>::stop();
|
||||
volk_free(buffer);
|
||||
volk_free(taps);
|
||||
generic_block<FMStereoDemuxPilotFilter>::_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<FMStereoDemuxPilotFilter>::registerInput(_in);
|
||||
generic_block<FMStereoDemuxPilotFilter>::registerOutput(&dataOut);
|
||||
generic_block<FMStereoDemuxPilotFilter>::registerOutput(&pilotOut);
|
||||
generic_block<FMStereoDemuxPilotFilter>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* in) {
|
||||
assert(generic_block<FMStereoDemuxPilotFilter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMStereoDemuxPilotFilter>::ctrlMtx);
|
||||
generic_block<FMStereoDemuxPilotFilter>::tempStop();
|
||||
generic_block<FMStereoDemuxPilotFilter>::unregisterInput(_in);
|
||||
_in = in;
|
||||
generic_block<FMStereoDemuxPilotFilter>::registerInput(_in);
|
||||
generic_block<FMStereoDemuxPilotFilter>::tempStart();
|
||||
}
|
||||
|
||||
void updateWindow(dsp::filter_window::generic_complex_window* window) {
|
||||
assert(generic_block<FMStereoDemuxPilotFilter>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMStereoDemuxPilotFilter>::ctrlMtx);
|
||||
std::lock_guard<std::mutex> lck2(bufMtx);
|
||||
_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; }
|
||||
|
||||
bufMtx.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*)&pilotOut.writeBuf[i], (lv_32fc_t*)&buffer[i+1], (lv_32fc_t*)taps, tapCount);
|
||||
}
|
||||
|
||||
memcpy(dataOut.writeBuf, &buffer[tapCount - ((tapCount-1)/2)], count * sizeof(complex_t));
|
||||
|
||||
if (!pilotOut.swap(count) || !dataOut.swap(count)) {
|
||||
bufMtx.unlock();
|
||||
return -1;
|
||||
}
|
||||
|
||||
memmove(buffer, &buffer[count], tapCount * sizeof(complex_t));
|
||||
|
||||
bufMtx.unlock();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<complex_t> dataOut;
|
||||
stream<complex_t> pilotOut;
|
||||
|
||||
|
||||
private:
|
||||
stream<complex_t>* _in;
|
||||
|
||||
dsp::filter_window::generic_complex_window* _window;
|
||||
|
||||
std::mutex bufMtx;
|
||||
|
||||
complex_t* bufStart;
|
||||
complex_t* buffer;
|
||||
int tapCount;
|
||||
complex_t* taps;
|
||||
|
||||
};
|
||||
|
||||
class FMStereoDemux: public generic_block<FMStereoDemux> {
|
||||
public:
|
||||
FMStereoDemux() {}
|
||||
|
||||
FMStereoDemux(stream<complex_t>* data, stream<complex_t>* pilot, float loopBandwidth) { init(data, pilot, loopBandwidth); }
|
||||
|
||||
void init(stream<complex_t>* data, stream<complex_t>* pilot, float loopBandwidth) {
|
||||
_data = data;
|
||||
_pilot = pilot;
|
||||
lastVCO.re = 1.0f;
|
||||
lastVCO.im = 0.0f;
|
||||
_loopBandwidth = loopBandwidth;
|
||||
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
|
||||
generic_block<FMStereoDemux>::registerInput(_data);
|
||||
generic_block<FMStereoDemux>::registerInput(_pilot);
|
||||
generic_block<FMStereoDemux>::registerOutput(&AplusBOut);
|
||||
generic_block<FMStereoDemux>::registerOutput(&AminusBOut);
|
||||
generic_block<FMStereoDemux>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInput(stream<complex_t>* data, stream<complex_t>* pilot) {
|
||||
assert(generic_block<FMStereoDemux>::_block_init);
|
||||
generic_block<FMStereoDemux>::tempStop();
|
||||
generic_block<FMStereoDemux>::unregisterInput(_data);
|
||||
generic_block<FMStereoDemux>::unregisterInput(_pilot);
|
||||
_data = data;
|
||||
_pilot = pilot;
|
||||
generic_block<FMStereoDemux>::registerInput(_data);
|
||||
generic_block<FMStereoDemux>::registerInput(_pilot);
|
||||
generic_block<FMStereoDemux>::tempStart();
|
||||
}
|
||||
|
||||
void setLoopBandwidth(float loopBandwidth) {
|
||||
assert(generic_block<FMStereoDemux>::_block_init);
|
||||
generic_block<FMStereoDemux>::tempStop();
|
||||
_loopBandwidth = loopBandwidth;
|
||||
float dampningFactor = sqrtf(2.0f) / 2.0f;
|
||||
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
|
||||
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
|
||||
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
|
||||
generic_block<FMStereoDemux>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int count = _data->read();
|
||||
if (count < 0) { return -1; }
|
||||
int pCount = _pilot->read();
|
||||
if (pCount < 0) { return -1; }
|
||||
|
||||
complex_t doubledVCO;
|
||||
float error;
|
||||
|
||||
volk_32fc_deinterleave_real_32f(AplusBOut.writeBuf, (lv_32fc_t*)_data->readBuf, count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
// Double the VCO, then mix it with the input data.
|
||||
// IMPORTANT: THERE SHOULDN'T BE A NEED FOR A GAIN HERE
|
||||
doubledVCO = lastVCO*lastVCO;
|
||||
AminusBOut.writeBuf[i] = (_data->readBuf[i].re * doubledVCO.re) * 2.0f;
|
||||
|
||||
// Calculate the phase error estimation
|
||||
error = _pilot->readBuf[i].phase() - vcoPhase;
|
||||
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
|
||||
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
|
||||
|
||||
// Integrate frequency and clamp it
|
||||
vcoFrequency += _beta * error;
|
||||
if (vcoFrequency > upperLimit) { vcoFrequency = upperLimit; }
|
||||
else if (vcoFrequency < lowerLimit) { vcoFrequency = lowerLimit; }
|
||||
|
||||
// Calculate new phase and wrap it
|
||||
vcoPhase += vcoFrequency + (_alpha * error);
|
||||
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
|
||||
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
|
||||
|
||||
// Calculate output
|
||||
lastVCO.re = cosf(vcoPhase);
|
||||
lastVCO.im = sinf(vcoPhase);
|
||||
}
|
||||
|
||||
_data->flush();
|
||||
_pilot->flush();
|
||||
|
||||
if (!AplusBOut.swap(count)) { return -1; }
|
||||
if (!AminusBOut.swap(count)) { return -1; }
|
||||
return count;
|
||||
}
|
||||
|
||||
stream<float> AplusBOut;
|
||||
stream<float> AminusBOut;
|
||||
|
||||
float gain = 2.0f;
|
||||
|
||||
private:
|
||||
float _loopBandwidth = 0.01f;
|
||||
|
||||
const float expectedFreq = ((19000.0f / 250000.0f) * 2.0f * FL_M_PI);
|
||||
const float upperLimit = ((19200.0f / 250000.0f) * 2.0f * FL_M_PI);
|
||||
const float lowerLimit = ((18800.0f / 250000.0f) * 2.0f * FL_M_PI);
|
||||
|
||||
float _alpha; // Integral coefficient
|
||||
float _beta; // Proportional coefficient
|
||||
float vcoFrequency = expectedFreq;
|
||||
float vcoPhase = 0.0f;
|
||||
complex_t lastVCO;
|
||||
|
||||
stream<complex_t>* _data;
|
||||
stream<complex_t>* _pilot;
|
||||
};
|
||||
|
||||
class FMStereoReconstruct : public generic_block<FMStereoReconstruct> {
|
||||
public:
|
||||
FMStereoReconstruct() {}
|
||||
|
||||
FMStereoReconstruct(stream<float>* a, stream<float>* b) { init(a, b); }
|
||||
|
||||
~FMStereoReconstruct() {
|
||||
generic_block<FMStereoReconstruct>::stop();
|
||||
delete[] leftBuf;
|
||||
delete[] rightBuf;
|
||||
}
|
||||
|
||||
void init(stream<float>* aplusb, stream<float>* aminusb) {
|
||||
_aplusb = aplusb;
|
||||
_aminusb = aminusb;
|
||||
|
||||
leftBuf = new float[STREAM_BUFFER_SIZE];
|
||||
rightBuf = new float[STREAM_BUFFER_SIZE];
|
||||
|
||||
generic_block<FMStereoReconstruct>::registerInput(aplusb);
|
||||
generic_block<FMStereoReconstruct>::registerInput(aminusb);
|
||||
generic_block<FMStereoReconstruct>::registerOutput(&out);
|
||||
generic_block<FMStereoReconstruct>::_block_init = true;
|
||||
}
|
||||
|
||||
void setInputs(stream<float>* aplusb, stream<float>* aminusb) {
|
||||
assert(generic_block<FMStereoReconstruct>::_block_init);
|
||||
std::lock_guard<std::mutex> lck(generic_block<FMStereoReconstruct>::ctrlMtx);
|
||||
generic_block<FMStereoReconstruct>::tempStop();
|
||||
generic_block<FMStereoReconstruct>::unregisterInput(_aplusb);
|
||||
generic_block<FMStereoReconstruct>::unregisterInput(_aminusb);
|
||||
_aplusb = aplusb;
|
||||
_aminusb = aminusb;
|
||||
generic_block<FMStereoReconstruct>::registerInput(_aplusb);
|
||||
generic_block<FMStereoReconstruct>::registerInput(_aminusb);
|
||||
generic_block<FMStereoReconstruct>::tempStart();
|
||||
}
|
||||
|
||||
int run() {
|
||||
int a_count = _aplusb->read();
|
||||
if (a_count < 0) { return -1; }
|
||||
int b_count = _aminusb->read();
|
||||
if (b_count < 0) { return -1; }
|
||||
if (a_count != b_count) {
|
||||
_aplusb->flush();
|
||||
_aminusb->flush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
volk_32f_x2_add_32f(rightBuf, _aplusb->readBuf, _aminusb->readBuf, a_count);
|
||||
volk_32f_x2_subtract_32f(leftBuf, _aplusb->readBuf, _aminusb->readBuf, a_count);
|
||||
_aplusb->flush();
|
||||
_aminusb->flush();
|
||||
|
||||
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, leftBuf, rightBuf, a_count);
|
||||
|
||||
if (!out.swap(a_count)) { return -1; }
|
||||
return a_count;
|
||||
}
|
||||
|
||||
stream<stereo_t> out;
|
||||
|
||||
private:
|
||||
stream<float>* _aplusb;
|
||||
stream<float>* _aminusb;
|
||||
|
||||
float* leftBuf;
|
||||
float* rightBuf;
|
||||
|
||||
};
|
||||
}
|
||||
126
core/src/dsp/stream.h
Normal file
126
core/src/dsp/stream.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <volk/volk.h>
|
||||
|
||||
// 1MB buffer
|
||||
#define STREAM_BUFFER_SIZE 1000000
|
||||
|
||||
namespace dsp {
|
||||
class untyped_stream {
|
||||
public:
|
||||
virtual bool swap(int size) { return false; }
|
||||
virtual int read() { return -1; }
|
||||
virtual void flush() {}
|
||||
virtual void stopWriter() {}
|
||||
virtual void clearWriteStop() {}
|
||||
virtual void stopReader() {}
|
||||
virtual void clearReadStop() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class stream : public untyped_stream {
|
||||
public:
|
||||
stream() {
|
||||
writeBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
|
||||
readBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
|
||||
}
|
||||
|
||||
~stream() {
|
||||
volk_free(writeBuf);
|
||||
volk_free(readBuf);
|
||||
}
|
||||
|
||||
bool swap(int size) {
|
||||
{
|
||||
// Wait to either swap or stop
|
||||
std::unique_lock<std::mutex> lck(swapMtx);
|
||||
swapCV.wait(lck, [this]{ return (canSwap || writerStop); });
|
||||
|
||||
// If writer was stopped, abandon operation
|
||||
if (writerStop) { return false; }
|
||||
|
||||
// Swap buffers
|
||||
dataSize = size;
|
||||
T* temp = writeBuf;
|
||||
writeBuf = readBuf;
|
||||
readBuf = temp;
|
||||
canSwap = false;
|
||||
}
|
||||
|
||||
// Notify reader that some data is ready
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
dataReady = true;
|
||||
}
|
||||
rdyCV.notify_all();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int read() {
|
||||
// Wait for data to be ready or to be stopped
|
||||
std::unique_lock<std::mutex> lck(rdyMtx);
|
||||
rdyCV.wait(lck, [this]{ return (dataReady || readerStop); });
|
||||
|
||||
return (readerStop ? -1 : dataSize);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
// Clear data ready
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
dataReady = false;
|
||||
}
|
||||
|
||||
// Notify writer that buffers can be swapped
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(swapMtx);
|
||||
canSwap = true;
|
||||
}
|
||||
|
||||
swapCV.notify_all();
|
||||
}
|
||||
|
||||
void stopWriter() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(swapMtx);
|
||||
writerStop = true;
|
||||
}
|
||||
swapCV.notify_all();
|
||||
}
|
||||
|
||||
void clearWriteStop() {
|
||||
writerStop = false;
|
||||
}
|
||||
|
||||
void stopReader() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(rdyMtx);
|
||||
readerStop = true;
|
||||
}
|
||||
rdyCV.notify_all();
|
||||
}
|
||||
|
||||
void clearReadStop() {
|
||||
readerStop = false;
|
||||
}
|
||||
|
||||
T* writeBuf;
|
||||
T* readBuf;
|
||||
|
||||
private:
|
||||
std::mutex swapMtx;
|
||||
std::condition_variable swapCV;
|
||||
bool canSwap = true;
|
||||
|
||||
std::mutex rdyMtx;
|
||||
std::condition_variable rdyCV;
|
||||
bool dataReady = false;
|
||||
|
||||
bool readerStop = false;
|
||||
bool writerStop = false;
|
||||
|
||||
int dataSize = 0;
|
||||
};
|
||||
}
|
||||
84
core/src/dsp/types.h
Normal file
84
core/src/dsp/types.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
#include <math.h>
|
||||
#include <dsp/utils/math.h>
|
||||
|
||||
namespace dsp {
|
||||
struct complex_t {
|
||||
complex_t operator*(const float b) {
|
||||
return complex_t{re*b, im*b};
|
||||
}
|
||||
|
||||
complex_t operator/(const float b) {
|
||||
return complex_t{re/b, im/b};
|
||||
}
|
||||
|
||||
complex_t operator*(const complex_t& b) {
|
||||
return complex_t{(re*b.re) - (im*b.im), (im*b.re) + (re*b.im)};
|
||||
}
|
||||
|
||||
complex_t operator+(const complex_t& b) {
|
||||
return complex_t{re+b.re, im+b.im};
|
||||
}
|
||||
|
||||
complex_t operator-(const complex_t& b) {
|
||||
return complex_t{re-b.re, im-b.im};
|
||||
}
|
||||
|
||||
inline complex_t conj() {
|
||||
return complex_t{re, -im};
|
||||
}
|
||||
|
||||
inline float phase() {
|
||||
return atan2f(im, re);
|
||||
}
|
||||
|
||||
inline float fastPhase() {
|
||||
float abs_im = fabsf(im);
|
||||
float r, angle;
|
||||
if (re == 0.0f && im == 0.0f) { return 0.0f; }
|
||||
if (re>=0.0f) {
|
||||
r = (re - abs_im) / (re + abs_im);
|
||||
angle = (FL_M_PI / 4.0f) - (FL_M_PI / 4.0f) * r;
|
||||
}
|
||||
else {
|
||||
r = (re + abs_im) / (abs_im - re);
|
||||
angle = (3.0f * (FL_M_PI / 4.0f)) - (FL_M_PI / 4.0f) * r;
|
||||
}
|
||||
if (im < 0.0f) {
|
||||
return -angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
inline float amplitude() {
|
||||
return sqrt((re*re) + (im*im));
|
||||
}
|
||||
|
||||
inline float fastAmplitude() {
|
||||
float re_abs = fabsf(re);
|
||||
float im_abs = fabsf(re);
|
||||
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
|
||||
return im_abs + 0.4f * re_abs;
|
||||
}
|
||||
|
||||
float re;
|
||||
float im;
|
||||
};
|
||||
|
||||
struct stereo_t {
|
||||
stereo_t operator*(const float b) {
|
||||
return stereo_t{l*b, r*b};
|
||||
}
|
||||
|
||||
stereo_t operator+(const stereo_t& b) {
|
||||
return stereo_t{l+b.l, r+b.r};
|
||||
}
|
||||
|
||||
stereo_t operator-(const stereo_t& b) {
|
||||
return stereo_t{l-b.l, r-b.r};
|
||||
}
|
||||
|
||||
float l;
|
||||
float r;
|
||||
};
|
||||
}
|
||||
43
core/src/dsp/utils/bitstream.h
Normal file
43
core/src/dsp/utils/bitstream.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
namespace dsp {
|
||||
inline uint64_t readBits(int offset, int length, uint8_t* buffer) {
|
||||
uint64_t outputValue = 0;
|
||||
|
||||
int lastBit = offset + (length - 1);
|
||||
|
||||
int firstWord = offset / 8;
|
||||
int lastWord = lastBit / 8;
|
||||
int firstOffset = offset - (firstWord * 8);
|
||||
int lastOffset = lastBit - (lastWord * 8);
|
||||
|
||||
int wordCount = (lastWord - firstWord) + 1;
|
||||
|
||||
// If the data fits in a single byte, just get it
|
||||
if (wordCount == 1) {
|
||||
return (buffer[firstWord] & (0xFF >> firstOffset)) >> (7 - lastOffset);
|
||||
}
|
||||
|
||||
int bitsRead = length;
|
||||
for (int i = 0; i < wordCount; i++) {
|
||||
// First word
|
||||
if (i == 0) {
|
||||
bitsRead -= 8 - firstOffset;
|
||||
outputValue |= (uint64_t)(buffer[firstWord] & (0xFF >> firstOffset)) << bitsRead;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Last word
|
||||
if (i == (wordCount - 1)) {
|
||||
outputValue |= (uint64_t)buffer[lastWord] >> (7 - lastOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
// Just a normal byte
|
||||
bitsRead -= 8;
|
||||
outputValue |= (uint64_t)buffer[firstWord + i] << bitsRead;
|
||||
}
|
||||
|
||||
return outputValue;
|
||||
}
|
||||
}
|
||||
121
core/src/dsp/utils/ccsds.h
Normal file
121
core/src/dsp/utils/ccsds.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace ccsds {
|
||||
const uint8_t TO_DUAL_BASIS[256] = {
|
||||
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 FROM_DUAL_BASIS[256] = {
|
||||
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 SCRAMBLING_SEQUENCE[255] = {
|
||||
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
|
||||
};
|
||||
|
||||
const uint32_t ASM_VALUE = 0x1ACFFC1D;
|
||||
const uint8_t ASM_BYTES[4] = {0x1A, 0xCF, 0xFC, 0x1D};
|
||||
const uint8_t ASM_SYMS[16] = {0b00, 0b01, 0b10, 0b10, 0b11, 0b00, 0b11, 0b11, 0b11, 0b11, 0b11, 0b00, 0b00, 0b01, 0b11, 0b01};
|
||||
const uint8_t ASM_BITS[32] = {0,0,0,1,1,0,1,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,0,1};
|
||||
|
||||
class FrameDataDecoder {
|
||||
public:
|
||||
FrameDataDecoder(int interleaving, bool dualBasis, int rsBlockSize, int rsParitySize) {
|
||||
_interleaving = interleaving;
|
||||
_dualBasis = dualBasis;
|
||||
_rsBlockSize = rsBlockSize;
|
||||
_rsParitySize = rsParitySize;
|
||||
}
|
||||
|
||||
void decode(uint8_t* in, uint8_t* out, int count) {
|
||||
// Deinterleave
|
||||
if (_dualBasis) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
workBuffer[i % _interleaving][i / _interleaving] = FROM_DUAL_BASIS[in[i]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
workBuffer[i % _interleaving][i / _interleaving] = in[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Reed solomon
|
||||
|
||||
// Reinterleave and descramble if needed
|
||||
if (_dualBasis) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = TO_DUAL_BASIS[workOutputBuffer[i % _interleaving][i / _interleaving]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
out[i] = workOutputBuffer[i % _interleaving][i / _interleaving];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t workBuffer[5][255];
|
||||
uint8_t workOutputBuffer[5][255];
|
||||
int _interleaving;
|
||||
bool _dualBasis;
|
||||
int _rsBlockSize;
|
||||
int _rsParitySize;
|
||||
|
||||
};
|
||||
|
||||
inline void descramble(uint8_t* in, uint8_t* out, int count) {
|
||||
for (int i = 0; i < count; i++){
|
||||
out[i] = in[i] ^ SCRAMBLING_SEQUENCE[i % 255];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
core/src/dsp/utils/macros.h
Normal file
6
core/src/dsp/utils/macros.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <dsp/types.h>
|
||||
|
||||
#define DSP_SIGN(n) ((n) >= 0)
|
||||
#define DSP_STEP_CPLX(c) (complex_t{(c.re > 0.0f) ? 1.0f : -1.0f, (c.im > 0.0f) ? 1.0f : -1.0f})
|
||||
#define DSP_STEP(n) (((n) > 0.0f) ? 1.0f : -1.0f)
|
||||
12
core/src/dsp/utils/math.h
Normal file
12
core/src/dsp/utils/math.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <math.h>
|
||||
|
||||
#define FL_M_PI 3.1415926535f
|
||||
|
||||
namespace dsp {
|
||||
namespace math {
|
||||
inline double sinc(double omega, double x, double norm) {
|
||||
return (x == 0.0f) ? 1.0f : (sin(omega*x)/(norm*x));
|
||||
}
|
||||
}
|
||||
}
|
||||
28
core/src/dsp/utils/window_functions.h
Normal file
28
core/src/dsp/utils/window_functions.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <dsp/types.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace window_function {
|
||||
inline double blackman(double n, double N, double alpha = 0.16f) {
|
||||
double a0 = (1.0f-alpha) / 2.0f;
|
||||
double a2 = alpha / 2.0f;
|
||||
return a0 - (0.5f*cos(2.0f*FL_M_PI*(n/N))) + (a2*cos(4.0f*FL_M_PI*(n/N)));
|
||||
}
|
||||
|
||||
inline double blackmanThirdOrder(double n, double N, double a0, double a1, double a2, double a3) {
|
||||
return a0 - (a1*cos(2.0f*FL_M_PI*(n/N))) + (a2*cos(4.0f*FL_M_PI*(n/N))) - (a3*cos(6.0f*FL_M_PI*(n/N)));
|
||||
}
|
||||
|
||||
inline double nuttall(double n, double N) {
|
||||
return blackmanThirdOrder(n, N, 0.3635819f, 0.4891775f, 0.1365995f, 0.0106411f);
|
||||
}
|
||||
|
||||
inline double blackmanNuttall(double n, double N) {
|
||||
return blackmanThirdOrder(n, N, 0.3635819f, 0.4891775f, 0.1365995f, 0.0106411f);
|
||||
}
|
||||
|
||||
inline double blackmanHarris(double n, double N) {
|
||||
return blackmanThirdOrder(n, N, 0.35875f, 0.48829f, 0.14128f, 0.01168f);
|
||||
}
|
||||
}
|
||||
}
|
||||
126
core/src/dsp/vfo.h
Normal file
126
core/src/dsp/vfo.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/window.h>
|
||||
#include <dsp/resampling.h>
|
||||
#include <dsp/processing.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace dsp {
|
||||
class VFO {
|
||||
public:
|
||||
VFO() {}
|
||||
|
||||
~VFO() {
|
||||
if (!_init) { return; }
|
||||
stop();
|
||||
_init = false;
|
||||
}
|
||||
|
||||
VFO(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
|
||||
init(in, offset, inSampleRate, outSampleRate, bandWidth);
|
||||
};
|
||||
|
||||
void init(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
|
||||
_in = in;
|
||||
_offset = offset;
|
||||
_inSampleRate = inSampleRate;
|
||||
_outSampleRate = outSampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
|
||||
xlator.init(_in, _inSampleRate, -_offset);
|
||||
win.init(realCutoff, realCutoff, inSampleRate);
|
||||
resamp.init(&xlator.out, &win, _inSampleRate, _outSampleRate);
|
||||
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
resamp.updateWindow(&win);
|
||||
|
||||
out = &resamp.out;
|
||||
|
||||
_init = true;
|
||||
}
|
||||
|
||||
void start() {
|
||||
assert(_init);
|
||||
if (running) { return; }
|
||||
xlator.start();
|
||||
resamp.start();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
assert(_init);
|
||||
if (!running) { return; }
|
||||
xlator.stop();
|
||||
resamp.stop();
|
||||
}
|
||||
|
||||
void setInSampleRate(float inSampleRate) {
|
||||
assert(_init);
|
||||
_inSampleRate = inSampleRate;
|
||||
if (running) { xlator.stop(); resamp.stop(); }
|
||||
xlator.setSampleRate(_inSampleRate);
|
||||
resamp.setInSampleRate(_inSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { xlator.start(); resamp.start(); }
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate) {
|
||||
assert(_init);
|
||||
_outSampleRate = outSampleRate;
|
||||
if (running) { resamp.stop(); }
|
||||
resamp.setOutSampleRate(_outSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { resamp.start(); }
|
||||
}
|
||||
|
||||
void setOutSampleRate(float outSampleRate, float bandWidth) {
|
||||
assert(_init);
|
||||
_outSampleRate = outSampleRate;
|
||||
_bandWidth = bandWidth;
|
||||
if (running) { resamp.stop(); }
|
||||
resamp.setOutSampleRate(_outSampleRate);
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
if (running) { resamp.start(); }
|
||||
}
|
||||
|
||||
void setOffset(float offset) {
|
||||
assert(_init);
|
||||
_offset = offset;
|
||||
xlator.setFrequency(-_offset);
|
||||
}
|
||||
|
||||
void setBandwidth(float bandWidth) {
|
||||
assert(_init);
|
||||
_bandWidth = bandWidth;
|
||||
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
|
||||
win.setCutoff(realCutoff);
|
||||
win.setTransWidth(realCutoff);
|
||||
resamp.updateWindow(&win);
|
||||
}
|
||||
|
||||
stream<complex_t>* out;
|
||||
|
||||
private:
|
||||
bool _init = false;
|
||||
bool running = false;
|
||||
float _offset, _inSampleRate, _outSampleRate, _bandWidth;
|
||||
filter_window::BlackmanWindow win;
|
||||
stream<complex_t>* _in;
|
||||
FrequencyXlator<complex_t> xlator;
|
||||
PolyphaseResampler<complex_t> resamp;
|
||||
|
||||
};
|
||||
}
|
||||
272
core/src/dsp/window.h
Normal file
272
core/src/dsp/window.h
Normal file
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
#include <dsp/block.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/utils/window_functions.h>
|
||||
|
||||
namespace dsp {
|
||||
namespace filter_window {
|
||||
class generic_window {
|
||||
public:
|
||||
virtual int getTapCount() { return -1; }
|
||||
virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {}
|
||||
};
|
||||
|
||||
class generic_complex_window {
|
||||
public:
|
||||
virtual int getTapCount() { return -1; }
|
||||
virtual void createTaps(dsp::complex_t* taps, int tapCount, float factor = 1.0f) {}
|
||||
};
|
||||
|
||||
class BlackmanWindow : public filter_window::generic_window {
|
||||
public:
|
||||
BlackmanWindow() {}
|
||||
BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); }
|
||||
|
||||
void init(float cutoff, float transWidth, float sampleRate) {
|
||||
_cutoff = cutoff;
|
||||
_transWidth = transWidth;
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setCutoff(float cutoff) {
|
||||
_cutoff = cutoff;
|
||||
}
|
||||
|
||||
void setTransWidth(float transWidth) {
|
||||
_transWidth = transWidth;
|
||||
}
|
||||
|
||||
int getTapCount() {
|
||||
float fc = _cutoff / _sampleRate;
|
||||
if (fc > 1.0f) {
|
||||
fc = 1.0f;
|
||||
}
|
||||
|
||||
int _M = 4.0f / (_transWidth / _sampleRate);
|
||||
if (_M < 4) {
|
||||
_M = 4;
|
||||
}
|
||||
|
||||
if (_M % 2 == 0) { _M++; }
|
||||
|
||||
return _M;
|
||||
}
|
||||
|
||||
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
|
||||
// Calculate cuttoff frequency
|
||||
float omega = 2.0f * FL_M_PI * (_cutoff / _sampleRate);
|
||||
if (omega > FL_M_PI) { omega = FL_M_PI; }
|
||||
|
||||
// Generate taps
|
||||
float val;
|
||||
float sum = 0.0f;
|
||||
float tc = tapCount;
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
val = math::sinc(omega, (float)i - (tc/2), FL_M_PI) * window_function::blackman(i, tc - 1);
|
||||
taps[i] = val;
|
||||
sum += val;
|
||||
}
|
||||
|
||||
// Normalize taps and multiply by supplied factor
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
taps[i] *= factor;
|
||||
taps[i] /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float _cutoff, _transWidth, _sampleRate;
|
||||
|
||||
};
|
||||
|
||||
class BandPassBlackmanWindow : public filter_window::generic_complex_window {
|
||||
public:
|
||||
BandPassBlackmanWindow() {}
|
||||
BandPassBlackmanWindow(float lowCutoff, float highCutoff, float transWidth, float sampleRate) { init(lowCutoff, highCutoff, transWidth, sampleRate); }
|
||||
|
||||
void init(float lowCutoff, float highCutoff, float transWidth, float sampleRate) {
|
||||
assert(lowCutoff <= highCutoff);
|
||||
_lowCutoff = lowCutoff;
|
||||
_highCutoff = highCutoff;
|
||||
_transWidth = transWidth;
|
||||
_sampleRate = sampleRate;
|
||||
|
||||
// Calculate other values
|
||||
_offset = (_lowCutoff + _highCutoff) / 2.0f;
|
||||
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setCutoffs(float lowCutoff, float highCutoff) {
|
||||
assert(lowCutoff <= highCutoff);
|
||||
_lowCutoff = lowCutoff;
|
||||
_highCutoff = highCutoff;
|
||||
|
||||
// Calculate other values
|
||||
_offset = (_lowCutoff + _highCutoff) / 2.0f;
|
||||
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
|
||||
}
|
||||
|
||||
void setLowCutoff(float lowCutoff) {
|
||||
assert(lowCutoff <= _highCutoff);
|
||||
_lowCutoff = lowCutoff;
|
||||
|
||||
// Calculate other values
|
||||
_offset = (_lowCutoff + _highCutoff) / 2.0f;
|
||||
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
|
||||
}
|
||||
|
||||
void setHighCutoff(float highCutoff) {
|
||||
assert(_lowCutoff <= highCutoff);
|
||||
_highCutoff = highCutoff;
|
||||
|
||||
// Calculate other values
|
||||
_offset = (_lowCutoff + _highCutoff) / 2.0f;
|
||||
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
|
||||
}
|
||||
|
||||
void setTransWidth(float transWidth) {
|
||||
_transWidth = transWidth;
|
||||
}
|
||||
|
||||
int getTapCount() {
|
||||
float fc = _cutoff / _sampleRate;
|
||||
if (fc > 1.0f) {
|
||||
fc = 1.0f;
|
||||
}
|
||||
|
||||
int _M = 4.0f / (_transWidth / _sampleRate);
|
||||
if (_M < 4) {
|
||||
_M = 4;
|
||||
}
|
||||
|
||||
if (_M % 2 == 0) { _M++; }
|
||||
|
||||
return _M;
|
||||
}
|
||||
|
||||
void createTaps(dsp::complex_t* taps, int tapCount, float factor = 1.0f) {
|
||||
// Calculate cuttoff frequency
|
||||
float omega = 2.0f * FL_M_PI * (_cutoff / _sampleRate);
|
||||
if (omega > FL_M_PI) { omega = FL_M_PI; }
|
||||
|
||||
// Generate taps
|
||||
float val;
|
||||
float sum = 0.0f;
|
||||
float tc = tapCount;
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
val = math::sinc(omega, (float)i - (tc/2), FL_M_PI) * window_function::blackman(i, tc - 1);
|
||||
taps[i].re = val;
|
||||
taps[i].im = 0;
|
||||
sum += val;
|
||||
}
|
||||
|
||||
// Normalize taps and multiply by supplied factor
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
taps[i] = taps[i] * factor;
|
||||
taps[i] = taps[i] / sum;
|
||||
}
|
||||
|
||||
// Add offset
|
||||
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
|
||||
lv_32fc_t phaseDelta = lv_cmake(std::cos((-_offset / _sampleRate) * 2.0f * FL_M_PI), std::sin((-_offset / _sampleRate) * 2.0f * FL_M_PI));
|
||||
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)taps, (lv_32fc_t*)taps, phaseDelta, &phase, tapCount);
|
||||
}
|
||||
|
||||
private:
|
||||
float _lowCutoff, _highCutoff;
|
||||
float _cutoff, _transWidth, _sampleRate, _offset;
|
||||
};
|
||||
}
|
||||
|
||||
class RRCTaps : public filter_window::generic_window {
|
||||
public:
|
||||
RRCTaps() {}
|
||||
RRCTaps(int tapCount, float sampleRate, float baudRate, float alpha) { init(tapCount, sampleRate, baudRate, alpha); }
|
||||
|
||||
void init(int tapCount, float sampleRate, float baudRate, float alpha) {
|
||||
_tapCount = tapCount;
|
||||
_sampleRate = sampleRate;
|
||||
_baudRate = baudRate;
|
||||
_alpha = alpha;
|
||||
}
|
||||
|
||||
int getTapCount() {
|
||||
return _tapCount;
|
||||
}
|
||||
|
||||
void setSampleRate(float sampleRate) {
|
||||
_sampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void setBaudRate(float baudRate) {
|
||||
_baudRate = baudRate;
|
||||
}
|
||||
|
||||
void setTapCount(int count) {
|
||||
_tapCount = count;
|
||||
}
|
||||
|
||||
void setAlpha(float alpha) {
|
||||
_alpha = alpha;
|
||||
}
|
||||
|
||||
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
|
||||
// ======== CREDIT: GNU Radio =========
|
||||
tapCount |= 1; // ensure that tapCount is odd
|
||||
|
||||
double spb = _sampleRate / _baudRate; // samples per bit/symbol
|
||||
double scale = 0;
|
||||
for (int i = 0; i < tapCount; i++)
|
||||
{
|
||||
double x1, x2, x3, num, den;
|
||||
double xindx = i - tapCount / 2;
|
||||
x1 = FL_M_PI * xindx / spb;
|
||||
x2 = 4 * _alpha * xindx / spb;
|
||||
x3 = x2 * x2 - 1;
|
||||
|
||||
// Avoid Rounding errors...
|
||||
if (fabs(x3) >= 0.000001) {
|
||||
if (i != tapCount / 2)
|
||||
num = cos((1 + _alpha) * x1) +
|
||||
sin((1 - _alpha) * x1) / (4 * _alpha * xindx / spb);
|
||||
else
|
||||
num = cos((1 + _alpha) * x1) + (1 - _alpha) * FL_M_PI / (4 * _alpha);
|
||||
den = x3 * FL_M_PI;
|
||||
}
|
||||
else {
|
||||
if (_alpha == 1)
|
||||
{
|
||||
taps[i] = -1;
|
||||
scale += taps[i];
|
||||
continue;
|
||||
}
|
||||
x3 = (1 - _alpha) * x1;
|
||||
x2 = (1 + _alpha) * x1;
|
||||
num = (sin(x2) * (1 + _alpha) * FL_M_PI -
|
||||
cos(x3) * ((1 - _alpha) * FL_M_PI * spb) / (4 * _alpha * xindx) +
|
||||
sin(x3) * spb * spb / (4 * _alpha * xindx * xindx));
|
||||
den = -32 * FL_M_PI * _alpha * _alpha * xindx / spb;
|
||||
}
|
||||
taps[i] = 4 * _alpha * num / den;
|
||||
scale += taps[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < tapCount; i++) {
|
||||
taps[i] = taps[i] / scale;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int _tapCount;
|
||||
float _sampleRate, _baudRate, _alpha;
|
||||
|
||||
};
|
||||
}
|
||||
6
core/src/glfw_window.h
Normal file
6
core/src/glfw_window.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <module.h>
|
||||
|
||||
namespace core {
|
||||
SDRPP_EXPORT GLFWwindow* window;
|
||||
};
|
||||
49
core/src/gui/colormaps.cpp
Normal file
49
core/src/gui/colormaps.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <gui/colormaps.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <fstream>
|
||||
#include <json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace colormaps {
|
||||
std::map<std::string, Map> maps;
|
||||
|
||||
void loadMap(std::string path) {
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Could not load {0}, file doesn't exist", path);
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
Map map;
|
||||
std::vector<std::string> mapTxt;
|
||||
|
||||
try {
|
||||
map.name = data["name"];
|
||||
map.author = data["author"];
|
||||
mapTxt = data["map"].get<std::vector<std::string>>();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
spdlog::error("Could not load {0}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
map.entryCount = mapTxt.size();
|
||||
map.map = new float[mapTxt.size() * 3];
|
||||
int i = 0;
|
||||
for(auto const& col : mapTxt) {
|
||||
uint8_t r, g, b, a;
|
||||
map.map[i * 3] = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
map.map[(i * 3) + 1] = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
map.map[(i * 3) + 2] = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
i++;
|
||||
}
|
||||
|
||||
maps[map.name] = map;
|
||||
}
|
||||
}
|
||||
18
core/src/gui/colormaps.h
Normal file
18
core/src/gui/colormaps.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <module.h>
|
||||
#include <map>
|
||||
|
||||
namespace colormaps {
|
||||
struct Map {
|
||||
std::string name;
|
||||
std::string author;
|
||||
float* map;
|
||||
int entryCount;
|
||||
};
|
||||
|
||||
void loadMap(std::string path);
|
||||
|
||||
SDRPP_EXPORT std::map<std::string, Map> maps;
|
||||
}
|
||||
66
core/src/gui/dialogs/credits.cpp
Normal file
66
core/src/gui/dialogs/credits.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <gui/dialogs/credits.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <credits.h>
|
||||
#include <version.h>
|
||||
|
||||
namespace credits {
|
||||
ImFont* bigFont;
|
||||
|
||||
void init() {
|
||||
|
||||
}
|
||||
|
||||
void show() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0,0,0,0));
|
||||
ImVec2 dispSize = ImGui::GetIO().DisplaySize;
|
||||
ImVec2 center = ImVec2(dispSize.x/2.0f, dispSize.y/2.0f);
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
ImGui::OpenPopup("Credits");
|
||||
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
|
||||
|
||||
ImGui::PushFont(style::hugeFont);
|
||||
ImGui::Text("SDR++ ");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::Text("This software is brought to you by Alexandre Rouma with the help of\n\n");
|
||||
|
||||
ImGui::Columns(3, "CreditColumns", true);
|
||||
|
||||
ImGui::Text("Contributors");
|
||||
for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Libraries");
|
||||
for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::Text("Patrons");
|
||||
for (int i = 0; i < sdrpp_credits::patronCount; i++) {
|
||||
ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
|
||||
}
|
||||
|
||||
ImGui::Columns(1, "CreditColumnsEnd", true);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")");
|
||||
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
6
core/src/gui/dialogs/credits.h
Normal file
6
core/src/gui/dialogs/credits.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace credits {
|
||||
void init();
|
||||
void show();
|
||||
}
|
||||
89
core/src/gui/dialogs/loading_screen.cpp
Normal file
89
core/src/gui/dialogs/loading_screen.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <GL/glew.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <imgui.h>
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
#include <gui/icons.h>
|
||||
#include <gui/style.h>
|
||||
#include <credits.h>
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace LoadingScreen {
|
||||
GLFWwindow* _win;
|
||||
|
||||
void setWindow(GLFWwindow* win) {
|
||||
_win = win;
|
||||
}
|
||||
|
||||
void show(std::string msg) {
|
||||
glfwPollEvents();
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
|
||||
ImGui::NewFrame();
|
||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
|
||||
ImGui::OpenPopup("Credits");
|
||||
ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground);
|
||||
|
||||
ImGui::PushFont(style::hugeFont);
|
||||
ImGui::Text("SDR++ ");
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
ImGui::Image(icons::LOGO, ImVec2(128, 128));
|
||||
// ImGui::Spacing();
|
||||
// ImGui::Spacing();
|
||||
// ImGui::Spacing();
|
||||
|
||||
// ImGui::Text("This software is brought to you by\n\n");
|
||||
|
||||
// ImGui::Columns(3, "CreditColumns", true);
|
||||
|
||||
// ImGui::Text("Contributors");
|
||||
// for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
|
||||
// ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
|
||||
// }
|
||||
|
||||
// ImGui::NextColumn();
|
||||
// ImGui::Text("Libraries");
|
||||
// for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
|
||||
// ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
|
||||
// }
|
||||
|
||||
// ImGui::NextColumn();
|
||||
// ImGui::Text("Patrons");
|
||||
// for (int i = 0; i < sdrpp_credits::patronCount; i++) {
|
||||
// ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
|
||||
// }
|
||||
|
||||
// ImGui::Columns(1, "CreditColumnsEnd", true);
|
||||
|
||||
// ImGui::Spacing();
|
||||
// ImGui::Spacing();
|
||||
// ImGui::Spacing();
|
||||
ImVec2 origPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosY(origPos.y + 50);
|
||||
ImGui::Text("%s", msg.c_str());
|
||||
ImGui::SetCursorPos(origPos);
|
||||
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar(1);
|
||||
ImGui::PopStyleColor(1);
|
||||
|
||||
ImGui::End();
|
||||
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(_win, &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());
|
||||
|
||||
glfwSwapBuffers(_win);
|
||||
}
|
||||
}
|
||||
10
core/src/gui/dialogs/loading_screen.h
Normal file
10
core/src/gui/dialogs/loading_screen.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
namespace LoadingScreen {
|
||||
void setWindow(GLFWwindow* win);
|
||||
void show(std::string msg);
|
||||
};
|
||||
1709
core/src/gui/file_dialogs.h
Normal file
1709
core/src/gui/file_dialogs.h
Normal file
File diff suppressed because it is too large
Load Diff
9
core/src/gui/gui.cpp
Normal file
9
core/src/gui/gui.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <gui/gui.h>
|
||||
|
||||
namespace gui {
|
||||
MainWindow mainWindow;
|
||||
ImGui::WaterFall waterfall;
|
||||
FrequencySelect freqSelect;
|
||||
ThemeManager themeManager;
|
||||
Menu menu;
|
||||
};
|
||||
18
core/src/gui/gui.h
Normal file
18
core/src/gui/gui.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <gui/widgets/menu.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <module.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/theme_manager.h>
|
||||
|
||||
namespace gui {
|
||||
SDRPP_EXPORT ImGui::WaterFall waterfall;
|
||||
SDRPP_EXPORT FrequencySelect freqSelect;
|
||||
SDRPP_EXPORT Menu menu;
|
||||
SDRPP_EXPORT ThemeManager themeManager;
|
||||
SDRPP_EXPORT MainWindow mainWindow;
|
||||
|
||||
void selectSource(std::string name);
|
||||
};
|
||||
53
core/src/gui/icons.cpp
Normal file
53
core/src/gui/icons.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <gui/icons.h>
|
||||
#include <stdint.h>
|
||||
#include <GL/glew.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <imgui/stb_image.h>
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace icons {
|
||||
ImTextureID LOGO;
|
||||
ImTextureID PLAY;
|
||||
ImTextureID STOP;
|
||||
ImTextureID MENU;
|
||||
ImTextureID MUTED;
|
||||
ImTextureID UNMUTED;
|
||||
ImTextureID NORMAL_TUNING;
|
||||
ImTextureID CENTER_TUNING;
|
||||
|
||||
GLuint loadTexture(std::string path) {
|
||||
int w,h,n;
|
||||
stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, 0);
|
||||
GLuint texId;
|
||||
glGenTextures(1, &texId);
|
||||
glBindTexture(GL_TEXTURE_2D, texId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
|
||||
stbi_image_free(data);
|
||||
return texId;
|
||||
}
|
||||
|
||||
bool load(std::string resDir) {
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Inavlid resource directory: {0}", resDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGO = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/sdrpp.png");
|
||||
PLAY = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/play.png");
|
||||
STOP = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/stop.png");
|
||||
MENU = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/menu.png");
|
||||
MUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/muted.png");
|
||||
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
|
||||
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
|
||||
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
18
core/src/gui/icons.h
Normal file
18
core/src/gui/icons.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <imgui/imgui.h>
|
||||
#include <GL/glew.h>
|
||||
#include <string>
|
||||
|
||||
namespace icons {
|
||||
extern ImTextureID LOGO;
|
||||
extern ImTextureID PLAY;
|
||||
extern ImTextureID STOP;
|
||||
extern ImTextureID MENU;
|
||||
extern ImTextureID MUTED;
|
||||
extern ImTextureID UNMUTED;
|
||||
extern ImTextureID NORMAL_TUNING;
|
||||
extern ImTextureID CENTER_TUNING;
|
||||
|
||||
GLuint loadTexture(std::string path);
|
||||
bool load(std::string resDir);
|
||||
}
|
||||
676
core/src/gui/main_window.cpp
Normal file
676
core/src/gui/main_window.cpp
Normal file
@@ -0,0 +1,676 @@
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/gui.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 <thread>
|
||||
#include <complex>
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <signal_path/dsp.h>
|
||||
#include <gui/icons.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <gui/style.h>
|
||||
#include <config.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
#include <gui/menus/source.h>
|
||||
#include <gui/menus/display.h>
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/menus/sink.h>
|
||||
#include <gui/menus/vfo_color.h>
|
||||
#include <gui/menus/module_manager.h>
|
||||
#include <gui/menus/theme.h>
|
||||
#include <gui/dialogs/credits.h>
|
||||
#include <filesystem>
|
||||
#include <signal_path/source.h>
|
||||
#include <gui/dialogs/loading_screen.h>
|
||||
#include <options.h>
|
||||
#include <gui/colormaps.h>
|
||||
#include <gui/widgets/snr_meter.h>
|
||||
#include <gui/tuner.h>
|
||||
|
||||
void MainWindow::init() {
|
||||
LoadingScreen::show("Initializing UI");
|
||||
gui::waterfall.init();
|
||||
gui::waterfall.setRawFFTSize(fftSize);
|
||||
|
||||
credits::init();
|
||||
|
||||
core::configManager.acquire();
|
||||
json menuElements = core::configManager.conf["menuElements"];
|
||||
std::string modulesDir = core::configManager.conf["modulesDirectory"];
|
||||
std::string resourcesDir = core::configManager.conf["resourcesDirectory"];
|
||||
core::configManager.release();
|
||||
|
||||
// Load menu elements
|
||||
gui::menu.order.clear();
|
||||
for (auto& elem : menuElements) {
|
||||
if (!elem.contains("name")) { spdlog::error("Menu element is missing name key"); continue; }
|
||||
if (!elem["name"].is_string()) { spdlog::error("Menu element name isn't a string"); continue; }
|
||||
if (!elem.contains("open")) { spdlog::error("Menu element is missing open key"); continue; }
|
||||
if (!elem["open"].is_boolean()) { spdlog::error("Menu element name isn't a string"); continue; }
|
||||
Menu::MenuOption_t opt;
|
||||
opt.name = elem["name"];
|
||||
opt.open = elem["open"];
|
||||
gui::menu.order.push_back(opt);
|
||||
}
|
||||
|
||||
gui::menu.registerEntry("Source", sourecmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
|
||||
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
|
||||
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
|
||||
gui::menu.registerEntry("VFO Color", vfo_color_menu::draw, NULL);
|
||||
gui::menu.registerEntry("Module Manager", module_manager_menu::draw, NULL);
|
||||
|
||||
gui::freqSelect.init();
|
||||
|
||||
// Set default values for waterfall in case no source init's it
|
||||
gui::waterfall.setBandwidth(8000000);
|
||||
gui::waterfall.setViewBandwidth(8000000);
|
||||
|
||||
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fftwPlan = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
|
||||
sigpath::signalPath.init(8000000, 20, fftSize, &dummyStream, (dsp::complex_t*)fft_in, fftHandler, this);
|
||||
sigpath::signalPath.start();
|
||||
|
||||
vfoCreatedHandler.handler = vfoAddedHandler;
|
||||
vfoCreatedHandler.ctx = this;
|
||||
sigpath::vfoManager.onVfoCreated.bindHandler(&vfoCreatedHandler);
|
||||
|
||||
spdlog::info("Loading modules");
|
||||
|
||||
// Load modules from /module directory
|
||||
if (std::filesystem::is_directory(modulesDir)) {
|
||||
for (const auto & file : std::filesystem::directory_iterator(modulesDir)) {
|
||||
std::string path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) {
|
||||
continue;
|
||||
}
|
||||
if (!file.is_regular_file()) { continue; }
|
||||
spdlog::info("Loading {0}", path);
|
||||
LoadingScreen::show("Loading " + path);
|
||||
core::moduleManager.loadModule(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spdlog::warn("Module directory {0} does not exist, not loading modules from directory", modulesDir);
|
||||
}
|
||||
|
||||
// Read module config
|
||||
core::configManager.acquire();
|
||||
std::vector<std::string> modules = core::configManager.conf["modules"];
|
||||
auto modList = core::configManager.conf["moduleInstances"].items();
|
||||
core::configManager.release();
|
||||
|
||||
// Load additional modules specified through config
|
||||
for (auto const& path : modules) {
|
||||
spdlog::info("Loading {0}", path);
|
||||
LoadingScreen::show("Loading " + path);
|
||||
core::moduleManager.loadModule(path);
|
||||
}
|
||||
|
||||
// Create module instances
|
||||
for (auto const& [name, _module] : modList) {
|
||||
std::string mod = _module["module"];
|
||||
bool enabled = _module["enabled"];
|
||||
spdlog::info("Initializing {0} ({1})", name, mod);
|
||||
LoadingScreen::show("Initializing " + name + " (" + mod + ")");
|
||||
core::moduleManager.createInstance(name, mod);
|
||||
if (!enabled) { core::moduleManager.disableInstance(name); }
|
||||
}
|
||||
|
||||
// Load color maps
|
||||
LoadingScreen::show("Loading color maps");
|
||||
spdlog::info("Loading color maps");
|
||||
if (std::filesystem::is_directory(resourcesDir + "/colormaps")) {
|
||||
for (const auto & file : std::filesystem::directory_iterator(resourcesDir + "/colormaps")) {
|
||||
std::string path = file.path().generic_string();
|
||||
LoadingScreen::show("Loading " + path);
|
||||
spdlog::info("Loading {0}", path);
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
if (!file.is_regular_file()) { continue; }
|
||||
colormaps::loadMap(path);
|
||||
}
|
||||
}
|
||||
else {
|
||||
spdlog::warn("Color map directory {0} does not exist, not loading modules from directory", modulesDir);
|
||||
}
|
||||
|
||||
gui::waterfall.updatePalletteFromArray(colormaps::maps["Turbo"].map, colormaps::maps["Turbo"].entryCount);
|
||||
|
||||
sourecmenu::init();
|
||||
sinkmenu::init();
|
||||
bandplanmenu::init();
|
||||
displaymenu::init();
|
||||
vfo_color_menu::init();
|
||||
module_manager_menu::init();
|
||||
|
||||
// TODO for 0.2.5
|
||||
// Fix gain not updated on startup, soapysdr
|
||||
|
||||
// Update UI settings
|
||||
LoadingScreen::show("Loading configuration");
|
||||
core::configManager.acquire();
|
||||
fftMin = core::configManager.conf["min"];
|
||||
fftMax = core::configManager.conf["max"];
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
double frequency = core::configManager.conf["frequency"];
|
||||
|
||||
showMenu = core::configManager.conf["showMenu"];
|
||||
startedWithMenuClosed = !showMenu;
|
||||
|
||||
gui::freqSelect.setFrequency(frequency);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
sigpath::sourceManager.tune(frequency);
|
||||
gui::waterfall.setCenterFrequency(frequency);
|
||||
bw = gui::waterfall.getBandwidth();
|
||||
gui::waterfall.vfoFreqChanged = false;
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
gui::waterfall.selectFirstVFO();
|
||||
|
||||
menuWidth = core::configManager.conf["menuWidth"];
|
||||
newWidth = menuWidth;
|
||||
|
||||
fftHeight = core::configManager.conf["fftHeight"];
|
||||
gui::waterfall.setFFTHeight(fftHeight);
|
||||
|
||||
tuningMode = core::configManager.conf["centerTuning"] ? tuner::TUNER_MODE_CENTER : tuner::TUNER_MODE_NORMAL;
|
||||
|
||||
core::configManager.release();
|
||||
|
||||
// Correct the offset of all VFOs so that they fit on the screen
|
||||
float finalBwHalf = gui::waterfall.getBandwidth() / 2.0;
|
||||
for (auto& [_name, _vfo] : gui::waterfall.vfos) {
|
||||
if (_vfo->lowerOffset < -finalBwHalf) {
|
||||
sigpath::vfoManager.setCenterOffset(_name, (_vfo->bandwidth/2)-finalBwHalf);
|
||||
continue;
|
||||
}
|
||||
if (_vfo->upperOffset > finalBwHalf) {
|
||||
sigpath::vfoManager.setCenterOffset(_name, finalBwHalf-(_vfo->bandwidth/2));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
initComplete = true;
|
||||
|
||||
core::moduleManager.doPostInitAll();
|
||||
}
|
||||
|
||||
void MainWindow::fftHandler(dsp::complex_t* samples, int count, void* ctx) {
|
||||
MainWindow* _this = (MainWindow*)ctx;
|
||||
std::lock_guard<std::mutex> lck(_this->fft_mtx);
|
||||
|
||||
// Apply window
|
||||
volk_32fc_32f_multiply_32fc((lv_32fc_t*)_this->fft_in, (lv_32fc_t*)samples, sigpath::signalPath.fftTaps, count);
|
||||
|
||||
// Zero out the rest of the samples
|
||||
if (count < _this->fftSize) {
|
||||
memset(&_this->fft_in[count], 0, (_this->fftSize-count) * sizeof(dsp::complex_t));
|
||||
}
|
||||
|
||||
// Execute FFT
|
||||
fftwf_execute(_this->fftwPlan);
|
||||
|
||||
// Get the FFT buffer
|
||||
float* fftBuf = gui::waterfall.getFFTBuffer();
|
||||
if (fftBuf == NULL) {
|
||||
gui::waterfall.pushFFT();
|
||||
return;
|
||||
}
|
||||
|
||||
// Take power of spectrum
|
||||
volk_32fc_s32f_power_spectrum_32f(fftBuf, (lv_32fc_t*)_this->fft_out, _this->fftSize, _this->fftSize);
|
||||
|
||||
// Push back data
|
||||
gui::waterfall.pushFFT();
|
||||
}
|
||||
|
||||
void MainWindow::vfoAddedHandler(VFOManager::VFO* vfo, void* ctx) {
|
||||
MainWindow* _this = (MainWindow*)ctx;
|
||||
std::string name = vfo->getName();
|
||||
core::configManager.acquire();
|
||||
if (!core::configManager.conf["vfoOffsets"].contains(name)) {
|
||||
core::configManager.release();
|
||||
return;
|
||||
}
|
||||
double offset = core::configManager.conf["vfoOffsets"][name];
|
||||
core::configManager.release();
|
||||
|
||||
double viewBW = gui::waterfall.getViewBandwidth();
|
||||
double viewOffset = gui::waterfall.getViewOffset();
|
||||
|
||||
double viewLower = viewOffset - (viewBW/2.0);
|
||||
double viewUpper = viewOffset + (viewBW/2.0);
|
||||
|
||||
double newOffset = std::clamp<double>(offset, viewLower, viewUpper);
|
||||
|
||||
sigpath::vfoManager.setCenterOffset(name, _this->initComplete ? newOffset : offset);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::draw() {
|
||||
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
|
||||
|
||||
ImGui::WaterfallVFO* vfo = NULL;
|
||||
if (gui::waterfall.selectedVFO != "") {
|
||||
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
|
||||
}
|
||||
|
||||
// Handle VFO movement
|
||||
if (vfo != NULL) {
|
||||
if (vfo->centerOffsetChanged) {
|
||||
if (tuningMode == tuner::TUNER_MODE_CENTER) {
|
||||
tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
}
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
|
||||
|
||||
// Handle selection of another VFO
|
||||
if (gui::waterfall.selectedVFOChanged) {
|
||||
gui::freqSelect.setFrequency((vfo != NULL) ? (vfo->generalOffset + gui::waterfall.getCenterFrequency()) : gui::waterfall.getCenterFrequency());
|
||||
gui::waterfall.selectedVFOChanged = false;
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
}
|
||||
|
||||
// Handle change in selected frequency
|
||||
if (gui::freqSelect.frequencyChanged) {
|
||||
gui::freqSelect.frequencyChanged = false;
|
||||
tuner::tune(tuningMode, gui::waterfall.selectedVFO, gui::freqSelect.frequency);
|
||||
if (vfo != NULL) {
|
||||
vfo->centerOffsetChanged = false;
|
||||
vfo->lowerOffsetChanged = false;
|
||||
vfo->upperOffsetChanged = false;
|
||||
}
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
|
||||
if (vfo != NULL) {
|
||||
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
// Handle dragging the frequency scale
|
||||
if (gui::waterfall.centerFreqMoved) {
|
||||
gui::waterfall.centerFreqMoved = false;
|
||||
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
|
||||
if (vfo != NULL) {
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
|
||||
}
|
||||
else {
|
||||
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
|
||||
}
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
int _fftHeight = gui::waterfall.getFFTHeight();
|
||||
if (fftHeight != _fftHeight) {
|
||||
fftHeight = _fftHeight;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftHeight"] = fftHeight;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
|
||||
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
|
||||
|
||||
int width = vMax.x - vMin.x;
|
||||
int height = vMax.y - vMin.y;
|
||||
|
||||
// To Bar
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
|
||||
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_MENU, false)) {
|
||||
showMenu = !showMenu;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["showMenu"] = showMenu;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
bool tmpPlaySate = playing;
|
||||
if (playButtonLocked && !tmpPlaySate) { style::beginDisabled(); }
|
||||
if (playing) {
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_stop_btn"));
|
||||
if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) {
|
||||
playing = false;
|
||||
onPlayStateChange.emit(false);
|
||||
sigpath::sourceManager.stop();
|
||||
sigpath::signalPath.inputBuffer.flush();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else { // TODO: Might need to check if there even is a device
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_play_btn"));
|
||||
if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) {
|
||||
sigpath::signalPath.inputBuffer.flush();
|
||||
sigpath::sourceManager.start();
|
||||
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
|
||||
playing = true;
|
||||
onPlayStateChange.emit(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
if (playButtonLocked && !tmpPlaySate) { style::endDisabled(); }
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
//ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
|
||||
sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
gui::freqSelect.draw();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
|
||||
if (tuningMode == tuner::TUNER_MODE_CENTER) {
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn"));
|
||||
if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
tuningMode = tuner::TUNER_MODE_NORMAL;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["centerTuning"] = false;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
else { // TODO: Might need to check if there even is a device
|
||||
ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn"));
|
||||
if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
|
||||
tuningMode = tuner::TUNER_MODE_CENTER;
|
||||
tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, gui::freqSelect.frequency);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["centerTuning"] = true;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
int snrWidth = std::min<int>(300, ImGui::GetWindowSize().x - ImGui::GetCursorPosX() - 87);
|
||||
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (snrWidth+87));
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
|
||||
ImGui::SetNextItemWidth(snrWidth);
|
||||
ImGui::SNRMeter((vfo != NULL) ? gui::waterfall.selectedVFOSNR : 0);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Logo button
|
||||
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
|
||||
ImGui::SetCursorPosY(10);
|
||||
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
|
||||
showCredits = true;
|
||||
}
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
||||
showCredits = false;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
|
||||
showCredits = false;
|
||||
}
|
||||
|
||||
// Handle menu resize
|
||||
ImVec2 winSize = ImGui::GetWindowSize();
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
if (!lockWaterfallControls) {
|
||||
float curY = ImGui::GetCursorPosY();
|
||||
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
if (grabbingMenu) {
|
||||
newWidth = mousePos.x;
|
||||
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
|
||||
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
|
||||
}
|
||||
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
|
||||
if (click) {
|
||||
grabbingMenu = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
|
||||
}
|
||||
if(!down && grabbingMenu) {
|
||||
grabbingMenu = false;
|
||||
menuWidth = newWidth;
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["menuWidth"] = menuWidth;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Left Column
|
||||
lockWaterfallControls = false;
|
||||
if (showMenu) {
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, menuWidth);
|
||||
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
ImGui::BeginChild("Left Column");
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
if (gui::menu.draw(firstMenuRender)) {
|
||||
core::configManager.acquire();
|
||||
json arr = json::array();
|
||||
for (int i = 0; i < gui::menu.order.size(); i++) {
|
||||
arr[i]["name"] = gui::menu.order[i].name;
|
||||
arr[i]["open"] = gui::menu.order[i].open;
|
||||
}
|
||||
core::configManager.conf["menuElements"] = arr;
|
||||
|
||||
// Update enabled and disabled modules
|
||||
for (auto [_name, inst] : core::moduleManager.instances) {
|
||||
if (!core::configManager.conf["moduleInstances"].contains(_name)) { continue; }
|
||||
core::configManager.conf["moduleInstances"][_name]["enabled"] = inst.instance->isEnabled();
|
||||
}
|
||||
|
||||
core::configManager.release(true);
|
||||
}
|
||||
if (startedWithMenuClosed) {
|
||||
startedWithMenuClosed = false;
|
||||
}
|
||||
else {
|
||||
firstMenuRender = false;
|
||||
}
|
||||
|
||||
if(ImGui::CollapsingHeader("Debug")) {
|
||||
ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
|
||||
ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency());
|
||||
ImGui::Text("Source name: %s", sourceName.c_str());
|
||||
ImGui::Checkbox("Show demo window", &demoWindow);
|
||||
ImGui::Text("ImGui version: %s", ImGui::GetVersion());
|
||||
|
||||
ImGui::Checkbox("Bypass buffering", &sigpath::signalPath.inputBuffer.bypass);
|
||||
|
||||
ImGui::Text("Buffering: %d", (sigpath::signalPath.inputBuffer.writeCur - sigpath::signalPath.inputBuffer.readCur + 32) % 32);
|
||||
|
||||
if (ImGui::Button("Test Bug")) {
|
||||
spdlog::error("Will this make the software crash?");
|
||||
}
|
||||
|
||||
if (ImGui::Button("Testing something")) {
|
||||
gui::menu.order[0].open = true;
|
||||
firstMenuRender = true;
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
else {
|
||||
// When hiding the menu bar
|
||||
ImGui::Columns(3, "WindowColumns", false);
|
||||
ImGui::SetColumnWidth(0, 8);
|
||||
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
|
||||
ImGui::SetColumnWidth(2, 60);
|
||||
}
|
||||
|
||||
// Right Column
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImGui::NextColumn();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::BeginChild("Waterfall");
|
||||
|
||||
gui::waterfall.draw();
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
if (!lockWaterfallControls) {
|
||||
// Handle arrow keys
|
||||
if (vfo != NULL && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_LEFT) && !gui::freqSelect.digitHovered) {
|
||||
double nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset - vfo->snapInterval;
|
||||
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
|
||||
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_RIGHT) && !gui::freqSelect.digitHovered) {
|
||||
double nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + vfo->snapInterval;
|
||||
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
|
||||
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
|
||||
}
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
|
||||
if (vfo != NULL) {
|
||||
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
// Handle scrollwheel
|
||||
int wheel = ImGui::GetIO().MouseWheel;
|
||||
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
|
||||
double nfreq;
|
||||
if (vfo != NULL) {
|
||||
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
|
||||
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
|
||||
}
|
||||
else {
|
||||
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);
|
||||
}
|
||||
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
|
||||
gui::freqSelect.setFrequency(nfreq);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
|
||||
if (vfo != NULL) {
|
||||
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
|
||||
}
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
ImGui::BeginChild("WaterfallControls");
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0));
|
||||
ImGui::Text("Zoom");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "")) {
|
||||
gui::waterfall.setViewBandwidth(bw);
|
||||
if (vfo != NULL) {
|
||||
gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0));
|
||||
ImGui::Text("Max");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -160.0f, "")) {
|
||||
fftMax = std::max<float>(fftMax, fftMin + 10);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["max"] = fftMax;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0));
|
||||
ImGui::Text("Min");
|
||||
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
|
||||
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -160.0f, "")) {
|
||||
fftMin = std::min<float>(fftMax - 10, fftMin);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["min"] = fftMin;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
gui::waterfall.setFFTMin(fftMin);
|
||||
gui::waterfall.setFFTMax(fftMax);
|
||||
gui::waterfall.setWaterfallMin(fftMin);
|
||||
gui::waterfall.setWaterfallMax(fftMax);
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (showCredits) {
|
||||
credits::show();
|
||||
}
|
||||
|
||||
if (demoWindow) {
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setViewBandwidthSlider(float bandwidth) {
|
||||
bw = bandwidth;
|
||||
}
|
||||
|
||||
bool MainWindow::sdrIsRunning() {
|
||||
return playing;
|
||||
}
|
||||
|
||||
void MainWindow::setFFTSize(int size) {
|
||||
std::lock_guard<std::mutex> lck(fft_mtx);
|
||||
fftSize = size;
|
||||
|
||||
gui::waterfall.setRawFFTSize(fftSize);
|
||||
sigpath::signalPath.setFFTSize(fftSize);
|
||||
|
||||
fftwf_free(fft_in);
|
||||
fftwf_free(fft_out);
|
||||
fftwf_destroy_plan(fftwPlan);
|
||||
|
||||
fft_in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fft_out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize);
|
||||
fftwPlan = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
}
|
||||
|
||||
void MainWindow::setFFTWindow(int win) {
|
||||
std::lock_guard<std::mutex> lck(fft_mtx);
|
||||
sigpath::signalPath.setFFTWindow(win);
|
||||
}
|
||||
|
||||
bool MainWindow::isPlaying() {
|
||||
return playing;
|
||||
}
|
||||
67
core/src/gui/main_window.h
Normal file
67
core/src/gui/main_window.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include <imgui/imgui.h>
|
||||
#include <fftw3.h>
|
||||
#include <dsp/types.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <signal_path/vfo_manager.h>
|
||||
#include <string>
|
||||
#include <utils/event.h>
|
||||
#include <mutex>
|
||||
#include <gui/tuner.h>
|
||||
|
||||
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
|
||||
|
||||
class MainWindow {
|
||||
public:
|
||||
void init();
|
||||
void draw();
|
||||
void setViewBandwidthSlider(float bandwidth);
|
||||
bool sdrIsRunning();
|
||||
void setFFTSize(int size);
|
||||
void setFFTWindow(int win);
|
||||
|
||||
// TODO: Replace with it's own class
|
||||
void setVFO(double freq);
|
||||
|
||||
bool isPlaying();
|
||||
|
||||
bool lockWaterfallControls = false;
|
||||
bool playButtonLocked = false;
|
||||
|
||||
Event<bool> onPlayStateChange;
|
||||
|
||||
private:
|
||||
static void fftHandler(dsp::complex_t* samples, int count, void* ctx);
|
||||
static void vfoAddedHandler(VFOManager::VFO* vfo, void* ctx);
|
||||
|
||||
// FFT Variables
|
||||
int fftSize = 8192 * 8;
|
||||
std::mutex fft_mtx;
|
||||
fftwf_complex *fft_in, *fft_out;
|
||||
fftwf_plan fftwPlan;
|
||||
|
||||
// GUI Variables
|
||||
bool firstMenuRender = true;
|
||||
bool startedWithMenuClosed = false;
|
||||
float fftMin = -70.0;
|
||||
float fftMax = 0.0;
|
||||
float bw = 8000000;
|
||||
bool playing = false;
|
||||
bool showCredits = false;
|
||||
std::string audioStreamName = "";
|
||||
std::string sourceName = "";
|
||||
int menuWidth = 300;
|
||||
bool grabbingMenu = false;
|
||||
int newWidth = 300;
|
||||
int fftHeight = 300;
|
||||
bool showMenu = true;
|
||||
int tuningMode = tuner::TUNER_MODE_NORMAL;
|
||||
dsp::stream<dsp::complex_t> dummyStream;
|
||||
bool demoWindow = false;
|
||||
int selectedWindow = 0;
|
||||
|
||||
bool initComplete = false;
|
||||
|
||||
EventHandler<VFOManager::VFO*> vfoCreatedHandler;
|
||||
|
||||
};
|
||||
67
core/src/gui/menus/bandplan.cpp
Normal file
67
core/src/gui/menus/bandplan.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <gui/menus/bandplan.h>
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
|
||||
namespace bandplanmenu {
|
||||
int bandplanId;
|
||||
bool bandPlanEnabled;
|
||||
int bandPlanPos = 0;
|
||||
|
||||
const char* bandPlanPosTxt = "Bottom\0Top\0";
|
||||
|
||||
void init() {
|
||||
// todo: check if the bandplan wasn't removed
|
||||
if (bandplan::bandplanNames.size() == 0) {
|
||||
gui::waterfall.hideBandplan();
|
||||
return;
|
||||
}
|
||||
|
||||
if (bandplan::bandplans.find(core::configManager.conf["bandPlan"]) != bandplan::bandplans.end()) {
|
||||
std::string name = core::configManager.conf["bandPlan"];
|
||||
bandplanId = std::distance(bandplan::bandplanNames.begin(), std::find(bandplan::bandplanNames.begin(),
|
||||
bandplan::bandplanNames.end(), name));
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[name];
|
||||
}
|
||||
else {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[0]];
|
||||
}
|
||||
|
||||
bandPlanEnabled = core::configManager.conf["bandPlanEnabled"];
|
||||
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
|
||||
bandPlanPos = core::configManager.conf["bandPlanPos"];
|
||||
gui::waterfall.setBandPlanPos(bandPlanPos);
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
ImGui::PushItemWidth(menuColumnWidth);
|
||||
if (ImGui::Combo("##_bandplan_name_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
|
||||
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["bandPlan"] = bandplan::bandplanNames[bandplanId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::Text("Position");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuColumnWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##_bandplan_pos_", &bandPlanPos, bandPlanPosTxt)) {
|
||||
gui::waterfall.setBandPlanPos(bandPlanPos);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["bandPlanPos"] = bandPlanPos;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Enabled", &bandPlanEnabled)) {
|
||||
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
|
||||
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
|
||||
ImGui::Text("Author: %s", plan.authorName.c_str());
|
||||
}
|
||||
};
|
||||
6
core/src/gui/menus/bandplan.h
Normal file
6
core/src/gui/menus/bandplan.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace bandplanmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
||||
156
core/src/gui/menus/display.cpp
Normal file
156
core/src/gui/menus/display.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <gui/menus/display.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
#include <gui/colormaps.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
namespace displaymenu {
|
||||
bool showWaterfall;
|
||||
bool fastFFT = true;
|
||||
bool fullWaterfallUpdate = true;
|
||||
int colorMapId = 0;
|
||||
std::vector<std::string> colorMapNames;
|
||||
std::string colorMapNamesTxt = "";
|
||||
std::string colorMapAuthor = "";
|
||||
int selectedWindow = 0;
|
||||
int fftRate = 20;
|
||||
|
||||
const int FFTSizes[] = {
|
||||
65536,
|
||||
32768,
|
||||
16384,
|
||||
8192,
|
||||
4096,
|
||||
2048,
|
||||
1024
|
||||
};
|
||||
|
||||
const char* FFTSizesStr = "65536\0"
|
||||
"32768\0"
|
||||
"16384\0"
|
||||
"8192\0"
|
||||
"4096\0"
|
||||
"2048\0"
|
||||
"1024\0";
|
||||
|
||||
int fftSizeId = 0;
|
||||
|
||||
void init() {
|
||||
showWaterfall = core::configManager.conf["showWaterfall"];
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
std::string colormapName = core::configManager.conf["colorMap"];
|
||||
if (colormaps::maps.find(colormapName) != colormaps::maps.end()) {
|
||||
colormaps::Map map = colormaps::maps[colormapName];
|
||||
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
|
||||
}
|
||||
|
||||
for (auto const& [name, map] : colormaps::maps) {
|
||||
colorMapNames.push_back(name);
|
||||
colorMapNamesTxt += name;
|
||||
colorMapNamesTxt += '\0';
|
||||
if (name == colormapName) {
|
||||
colorMapId = (colorMapNames.size() - 1);
|
||||
colorMapAuthor = map.author;
|
||||
}
|
||||
}
|
||||
|
||||
fastFFT = core::configManager.conf["fastFFT"];
|
||||
gui::waterfall.setFastFFT(fastFFT);
|
||||
|
||||
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
|
||||
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
||||
|
||||
fftSizeId = 0;
|
||||
int fftSize = core::configManager.conf["fftSize"];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (fftSize == FFTSizes[i]) {
|
||||
fftSizeId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
gui::mainWindow.setFFTSize(FFTSizes[fftSizeId]);
|
||||
|
||||
fftRate = core::configManager.conf["fftRate"];
|
||||
sigpath::signalPath.setFFTRate(fftRate);
|
||||
|
||||
selectedWindow = std::clamp<int>((int)core::configManager.conf["fftWindow"], 0, _FFT_WINDOW_COUNT-1);
|
||||
gui::mainWindow.setFFTWindow(selectedWindow);
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
bool homePressed = ImGui::IsKeyPressed(GLFW_KEY_HOME, false);
|
||||
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
|
||||
if (homePressed) { showWaterfall = !showWaterfall; }
|
||||
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["showWaterfall"] = showWaterfall;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Fast FFT##_sdrpp", &fastFFT)) {
|
||||
gui::waterfall.setFastFFT(fastFFT);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fastFFT"] = fastFFT;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
|
||||
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fullWaterfallUpdate"] = fullWaterfallUpdate;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::Text("FFT Framerate");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
|
||||
fftRate = std::max<int>(1, fftRate);
|
||||
sigpath::signalPath.setFFTRate(fftRate);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftRate"] = fftRate;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::Text("FFT Size");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
|
||||
gui::mainWindow.setFFTSize(FFTSizes[fftSizeId]);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::Text("FFT Window");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##sdrpp_fft_window", &selectedWindow, "Rectangular\0Blackman\0")) {
|
||||
gui::mainWindow.setFFTWindow(selectedWindow);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["fftWindow"] = selectedWindow;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (colorMapNames.size() > 0) {
|
||||
ImGui::Text("Color Map");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##_sdrpp_color_map_sel", &colorMapId, colorMapNamesTxt.c_str())) {
|
||||
colormaps::Map map = colormaps::maps[colorMapNames[colorMapId]];
|
||||
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["colorMap"] = colorMapNames[colorMapId];
|
||||
core::configManager.release(true);
|
||||
colorMapAuthor = map.author;
|
||||
}
|
||||
ImGui::Text("Color map Author: %s", colorMapAuthor.c_str());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
6
core/src/gui/menus/display.h
Normal file
6
core/src/gui/menus/display.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace displaymenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
||||
104
core/src/gui/menus/module_manager.cpp
Normal file
104
core/src/gui/menus/module_manager.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include <gui/menus/module_manager.h>
|
||||
#include <imgui.h>
|
||||
#include <core.h>
|
||||
#include <string.h>
|
||||
#include <gui/style.h>
|
||||
|
||||
namespace module_manager_menu {
|
||||
char modName[1024];
|
||||
std::vector<std::string> modTypes;
|
||||
std::vector<std::string> toBeRemoved;
|
||||
std::string modTypesTxt;
|
||||
int modTypeId;
|
||||
|
||||
void init() {
|
||||
modName[0] = 0;
|
||||
|
||||
modTypes.clear();
|
||||
modTypesTxt = "";
|
||||
for (auto& [name, mod] : core::moduleManager.modules) {
|
||||
modTypes.push_back(name);
|
||||
modTypesTxt += name;
|
||||
modTypesTxt += '\0';
|
||||
}
|
||||
modTypeId = 0;
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
bool modified = false;
|
||||
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Type");
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 10);
|
||||
ImGui::TableSetupScrollFreeze(3, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
float height = ImGui::CalcTextSize("-").y;
|
||||
|
||||
toBeRemoved.clear();
|
||||
|
||||
for (auto& [name, inst] : core::moduleManager.instances) {
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::Text(name.c_str());
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text(inst.module.info->name);
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImVec2 origPos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPos(ImVec2(origPos.x - 3, origPos.y));
|
||||
if (ImGui::Button(("##module_mgr_"+name).c_str(), ImVec2(height,height))) {
|
||||
toBeRemoved.push_back(name);
|
||||
modified = true;
|
||||
}
|
||||
ImGui::SetCursorPos(ImVec2(origPos.x + 2, origPos.y - 5));
|
||||
ImGui::Text("_");
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
||||
for (auto& rem : toBeRemoved) {
|
||||
core::moduleManager.deleteInstance(rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add module row with slightly different settings
|
||||
ImGui::BeginTable("Module Manager Add Table", 3);
|
||||
ImGui::TableSetupColumn("Name");
|
||||
ImGui::TableSetupColumn("Type");
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 16);
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
|
||||
ImGui::InputText("##module_mod_name", modName, 1000);
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
|
||||
ImGui::Combo("##module_mgr_type", &modTypeId, modTypesTxt.c_str());
|
||||
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
if (strlen(modName) == 0) { style::beginDisabled(); }
|
||||
if (ImGui::Button("+##module_mgr_add_btn", ImVec2(16,0))) {
|
||||
core::moduleManager.createInstance(modName, modTypes[modTypeId]);
|
||||
core::moduleManager.postInit(modName);
|
||||
modified = true;
|
||||
}
|
||||
if (strlen(modName) == 0) { style::endDisabled(); }
|
||||
ImGui::EndTable();
|
||||
|
||||
if (modified) {
|
||||
// Update enabled and disabled modules
|
||||
core::configManager.acquire();
|
||||
json instances;
|
||||
for (auto [_name, inst] : core::moduleManager.instances) {
|
||||
instances[_name]["module"] = inst.module.info->name;
|
||||
instances[_name]["enabled"] = inst.instance->isEnabled();
|
||||
}
|
||||
core::configManager.conf["moduleInstances"] = instances;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
6
core/src/gui/menus/module_manager.h
Normal file
6
core/src/gui/menus/module_manager.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace module_manager_menu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
||||
15
core/src/gui/menus/sink.cpp
Normal file
15
core/src/gui/menus/sink.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <gui/menus/sink.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <core.h>
|
||||
|
||||
namespace sinkmenu {
|
||||
void init() {
|
||||
core::configManager.acquire();
|
||||
sigpath::sinkManager.loadSinksFromConfig();
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
sigpath::sinkManager.showMenu();
|
||||
}
|
||||
};
|
||||
6
core/src/gui/menus/sink.h
Normal file
6
core/src/gui/menus/sink.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace sinkmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
};
|
||||
210
core/src/gui/menus/source.cpp
Normal file
210
core/src/gui/menus/source.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include <gui/menus/source.h>
|
||||
#include <imgui.h>
|
||||
#include <gui/gui.h>
|
||||
#include <core.h>
|
||||
#include <gui/main_window.h>
|
||||
#include <gui/style.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
|
||||
namespace sourecmenu {
|
||||
int offsetMode = 0;
|
||||
int sourceId = 0;
|
||||
double customOffset = 0.0;
|
||||
double effectiveOffset = 0.0;
|
||||
int decimationPower = 0;
|
||||
bool iqCorrection = false;
|
||||
|
||||
EventHandler<std::string> sourceRegisteredHandler;
|
||||
EventHandler<std::string> sourceUnregisterHandler;
|
||||
EventHandler<std::string> sourceUnregisteredHandler;
|
||||
|
||||
std::vector<std::string> sourceNames;
|
||||
std::string sourceNamesTxt;
|
||||
std::string selectedSource;
|
||||
|
||||
enum {
|
||||
OFFSET_MODE_NONE,
|
||||
OFFSET_MODE_CUSTOM,
|
||||
OFFSET_MODE_SPYVERTER,
|
||||
OFFSET_MODE_HAM_IT_UP,
|
||||
OFFSET_MODE_DK5AV_XB,
|
||||
OFFSET_MODE_KU_LNB_9750,
|
||||
OFFSET_MODE_KU_LNB_10700,
|
||||
_OFFSET_MODE_COUNT
|
||||
};
|
||||
|
||||
const char* offsetModesTxt = "None\0"
|
||||
"Custom\0"
|
||||
"SpyVerter\0"
|
||||
"Ham-It-Up\0"
|
||||
"DK5AV X-Band\0"
|
||||
"Ku LNB (9750MHz)\0"
|
||||
"Ku LNB (10700MHz)\0";
|
||||
|
||||
const char* decimationStages = "None\0"
|
||||
"2\0"
|
||||
"4\0"
|
||||
"8\0"
|
||||
"16\0"
|
||||
"32\0"
|
||||
"64\0";
|
||||
|
||||
void updateOffset() {
|
||||
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; }
|
||||
else if (offsetMode == OFFSET_MODE_SPYVERTER) { effectiveOffset = 120000000; } // 120MHz Up-conversion
|
||||
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) { effectiveOffset = 125000000; } // 125MHz Up-conversion
|
||||
else if (offsetMode == OFFSET_MODE_DK5AV_XB) { effectiveOffset = -6800000000; } // 6.8GHz Down-conversion
|
||||
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) { effectiveOffset = -9750000000; } // 9.750GHz Down-conversion
|
||||
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) { effectiveOffset = -10700000000; } // 10.7GHz Down-conversion
|
||||
else { effectiveOffset = 0; }
|
||||
sigpath::sourceManager.setTuningOffset(effectiveOffset);
|
||||
}
|
||||
|
||||
void refreshSources() {
|
||||
sourceNames = sigpath::sourceManager.getSourceNames();
|
||||
sourceNamesTxt.clear();
|
||||
for (auto name : sourceNames) {
|
||||
sourceNamesTxt += name;
|
||||
sourceNamesTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void selectSource(std::string name) {
|
||||
if (sourceNames.empty()) {
|
||||
selectedSource.clear();
|
||||
return;
|
||||
}
|
||||
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
|
||||
if (it == sourceNames.end()) {
|
||||
selectSource(sourceNames[0]);
|
||||
return;
|
||||
}
|
||||
sourceId = std::distance(sourceNames.begin(), it);
|
||||
selectedSource = sourceNames[sourceId];
|
||||
sigpath::sourceManager.selectSource(sourceNames[sourceId]);
|
||||
}
|
||||
|
||||
void onSourceRegistered(std::string name, void* ctx) {
|
||||
refreshSources();
|
||||
|
||||
if (selectedSource.empty()) {
|
||||
sourceId = 0;
|
||||
selectSource(sourceNames[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
||||
}
|
||||
|
||||
void onSourceUnregister(std::string name, void* ctx) {
|
||||
if (name != selectedSource) { return; }
|
||||
|
||||
// TODO: Stop everything
|
||||
}
|
||||
|
||||
void onSourceUnregistered(std::string name, void* ctx) {
|
||||
refreshSources();
|
||||
|
||||
if (sourceNames.empty()) {
|
||||
selectedSource = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == selectedSource) {
|
||||
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1);
|
||||
selectSource(sourceNames[sourceId]);
|
||||
return;
|
||||
}
|
||||
|
||||
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
|
||||
}
|
||||
|
||||
void init() {
|
||||
core::configManager.acquire();
|
||||
std::string selected = core::configManager.conf["source"];
|
||||
customOffset = core::configManager.conf["offset"];
|
||||
offsetMode = core::configManager.conf["offsetMode"];
|
||||
decimationPower = core::configManager.conf["decimationPower"];
|
||||
iqCorrection = core::configManager.conf["iqCorrection"];
|
||||
sigpath::signalPath.setIQCorrection(iqCorrection);
|
||||
updateOffset();
|
||||
|
||||
refreshSources();
|
||||
selectSource(selected);
|
||||
sigpath::signalPath.setDecimation(decimationPower);
|
||||
|
||||
sourceRegisteredHandler.handler = onSourceRegistered;
|
||||
sourceUnregisterHandler.handler = onSourceUnregister;
|
||||
sourceUnregisteredHandler.handler = onSourceUnregistered;
|
||||
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
|
||||
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
|
||||
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
|
||||
|
||||
core::configManager.release();
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float itemWidth = ImGui::GetContentRegionAvailWidth();
|
||||
bool running = gui::mainWindow.sdrIsRunning();
|
||||
|
||||
if (running) { style::beginDisabled(); }
|
||||
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) {
|
||||
selectSource(sourceNames[sourceId]);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["source"] = sourceNames[sourceId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
if (running) { style::endDisabled(); }
|
||||
|
||||
sigpath::sourceManager.showSelectedMenu();
|
||||
|
||||
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
|
||||
sigpath::signalPath.setIQCorrection(iqCorrection);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["iqCorrection"] = iqCorrection;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::Text("Offset mode");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) {
|
||||
updateOffset();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["offsetMode"] = offsetMode;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
|
||||
ImGui::Text("Offset");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (offsetMode == OFFSET_MODE_CUSTOM) {
|
||||
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) {
|
||||
updateOffset();
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["offset"] = customOffset;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
style::beginDisabled();
|
||||
ImGui::InputDouble("##freq_offset", &effectiveOffset, 1.0, 100.0);
|
||||
style::endDisabled();
|
||||
}
|
||||
|
||||
if (running) { style::beginDisabled(); }
|
||||
ImGui::Text("Decimation");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) {
|
||||
sigpath::signalPath.setDecimation(decimationPower);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["decimationPower"] = decimationPower;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
if (running) { style::endDisabled(); }
|
||||
}
|
||||
}
|
||||
6
core/src/gui/menus/source.h
Normal file
6
core/src/gui/menus/source.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace sourecmenu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
||||
47
core/src/gui/menus/theme.cpp
Normal file
47
core/src/gui/menus/theme.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <gui/menus/theme.h>
|
||||
#include <gui/gui.h>
|
||||
#include <options.h>
|
||||
#include <core.h>
|
||||
|
||||
namespace thememenu {
|
||||
int themeId;
|
||||
std::vector<std::string> themeNames;
|
||||
std::string themeNamesTxt;
|
||||
|
||||
void init(std::string resDir) {
|
||||
// TODO: Not hardcode theme directory
|
||||
gui::themeManager.loadThemesFromDir(resDir + "/themes/");
|
||||
core::configManager.acquire();
|
||||
std::string selectedThemeName = core::configManager.conf["theme"];
|
||||
core::configManager.release();
|
||||
|
||||
// Select theme by name, if not available, apply Dark theme
|
||||
themeNames = gui::themeManager.getThemeNames();
|
||||
auto it = std::find(themeNames.begin(), themeNames.end(), selectedThemeName);
|
||||
if (it == themeNames.end()) {
|
||||
it = std::find(themeNames.begin(), themeNames.end(), "Dark");
|
||||
selectedThemeName = "Dark";
|
||||
}
|
||||
gui::themeManager.applyTheme(selectedThemeName);
|
||||
themeId = std::distance(themeNames.begin(), it);
|
||||
|
||||
themeNamesTxt = "";
|
||||
for (auto name : themeNames) {
|
||||
themeNamesTxt += name;
|
||||
themeNamesTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
float menuWidth = ImGui::GetContentRegionAvailWidth();
|
||||
ImGui::Text("Theme");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
||||
if (ImGui::Combo("##theme_select_combo", &themeId, themeNamesTxt.c_str())) {
|
||||
gui::themeManager.applyTheme(themeNames[themeId]);
|
||||
core::configManager.acquire();
|
||||
core::configManager.conf["theme"] = themeNames[themeId];
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
core/src/gui/menus/theme.h
Normal file
7
core/src/gui/menus/theme.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace thememenu {
|
||||
void init(std::string resDir);
|
||||
void draw(void* ctx);
|
||||
}
|
||||
137
core/src/gui/menus/vfo_color.cpp
Normal file
137
core/src/gui/menus/vfo_color.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <gui/menus/vfo_color.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/widgets/waterfall.h>
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <string>
|
||||
#include <core.h>
|
||||
#include <map>
|
||||
|
||||
namespace vfo_color_menu {
|
||||
std::map<std::string, ImVec4> vfoColors;
|
||||
std::string openName = "";
|
||||
EventHandler<VFOManager::VFO*> vfoAddHndl;
|
||||
|
||||
void vfoAddHandler(VFOManager::VFO* vfo, void* ctx) {
|
||||
std::string name = vfo->getName();
|
||||
if (vfoColors.find(name) != vfoColors.end()) {
|
||||
ImVec4 col = vfoColors[name];
|
||||
vfo->setColor(IM_COL32((int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255), 50));
|
||||
return;
|
||||
}
|
||||
vfo->setColor(IM_COL32(255, 255, 255, 50));
|
||||
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void init() {
|
||||
// Load colors from config
|
||||
bool modified = false;
|
||||
core::configManager.acquire();
|
||||
json conf = core::configManager.conf["vfoColors"];
|
||||
for (auto& [name, val] : conf.items()) {
|
||||
// If not a string, repair with default
|
||||
if (!val.is_string()) {
|
||||
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
|
||||
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
modified = true;
|
||||
if (sigpath::vfoManager.vfoExists(name)) {
|
||||
sigpath::vfoManager.setColor(name, IM_COL32(255, 255, 255, 50));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not a valid hex color, repair with default
|
||||
std::string col = val;
|
||||
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
|
||||
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
|
||||
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
modified = true;
|
||||
if (sigpath::vfoManager.vfoExists(name)) {
|
||||
sigpath::vfoManager.setColor(name, IM_COL32(255, 255, 255, 50));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Since the color is valid, decode it and set the vfo's color
|
||||
float r, g, b;
|
||||
r = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
g = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
b = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
vfoColors[name] = ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
|
||||
if (sigpath::vfoManager.vfoExists(name)) {
|
||||
sigpath::vfoManager.setColor(name, IM_COL32((int)roundf(r), (int)roundf(g), (int)roundf(b), 50));
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate existing VFOs and set their color if in the config, if not set to default
|
||||
for (auto& [name, vfo] : gui::waterfall.vfos) {
|
||||
if (vfoColors.find(name) == vfoColors.end()) {
|
||||
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
vfo->color = IM_COL32(255, 255, 255, 50);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
vfoAddHndl.handler = vfoAddHandler;
|
||||
sigpath::vfoManager.onVfoCreated.bindHandler(&vfoAddHndl);
|
||||
core::configManager.release(modified);
|
||||
}
|
||||
|
||||
void draw(void* ctx) {
|
||||
ImGui::BeginTable("VFO Color Buttons Table", 2);
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
if (ImGui::Button("Auto Color##vfo_color", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) {
|
||||
float delta = 1.0f / (float)gui::waterfall.vfos.size();
|
||||
float hue = 0;
|
||||
for (auto& [name, vfo] : gui::waterfall.vfos) {
|
||||
float r, g, b;
|
||||
ImGui::ColorConvertHSVtoRGB(hue, 0.5f, 1.0f, r, g, b);
|
||||
vfoColors[name] = ImVec4(r, g, b, 1.0f);
|
||||
vfo->color = IM_COL32((int)roundf(r * 255), (int)roundf(g * 255), (int)roundf(b * 255), 50);
|
||||
hue += delta;
|
||||
core::configManager.acquire();
|
||||
char buf[16];
|
||||
sprintf(buf, "#%02X%02X%02X", (int)roundf(r * 255), (int)roundf(g * 255), (int)roundf(b * 255));
|
||||
core::configManager.conf["vfoColors"][name] = buf;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
if (ImGui::Button("Clear All##vfo_color", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) {
|
||||
for (auto& [name, vfo] : gui::waterfall.vfos) {
|
||||
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
vfo->color = IM_COL32(255, 255, 255, 50);
|
||||
core::configManager.acquire();
|
||||
char buf[16];
|
||||
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
|
||||
core::configManager.release(true);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
|
||||
ImGui::BeginTable("VFO Color table", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
|
||||
for (auto& [name, vfo] : gui::waterfall.vfos) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImVec4 col(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
if (vfoColors.find(name) != vfoColors.end()) {
|
||||
col = vfoColors[name];
|
||||
}
|
||||
if (ImGui::ColorEdit3(("##vfo_color_"+name).c_str(), (float*)&col, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
|
||||
vfoColors[name] = col;
|
||||
vfo->color = IM_COL32((int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255), 50);
|
||||
core::configManager.acquire();
|
||||
char buf[16];
|
||||
sprintf(buf, "#%02X%02X%02X", (int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255));
|
||||
core::configManager.conf["vfoColors"][name] = buf;
|
||||
core::configManager.release(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::Text(name.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
6
core/src/gui/menus/vfo_color.h
Normal file
6
core/src/gui/menus/vfo_color.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace vfo_color_menu {
|
||||
void init();
|
||||
void draw(void* ctx);
|
||||
}
|
||||
46
core/src/gui/style.cpp
Normal file
46
core/src/gui/style.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <gui/style.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <config.h>
|
||||
#include <options.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
|
||||
namespace style {
|
||||
ImFont* baseFont;
|
||||
ImFont* bigFont;
|
||||
ImFont* hugeFont;
|
||||
|
||||
bool loadFonts(std::string resDir) {
|
||||
if (!std::filesystem::is_directory(resDir)) {
|
||||
spdlog::error("Inavlid resource directory: {0}", resDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
|
||||
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f);
|
||||
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void beginDisabled() {
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
auto& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
ImVec4 btnCol = colors[ImGuiCol_Button];
|
||||
ImVec4 frameCol = colors[ImGuiCol_FrameBg];
|
||||
ImVec4 textCol = colors[ImGuiCol_Text];
|
||||
btnCol.w = 0.15f;
|
||||
frameCol.w = 0.30f;
|
||||
textCol.w = 0.65f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, btnCol);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, frameCol);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, textCol);
|
||||
}
|
||||
|
||||
void endDisabled() {
|
||||
ImGui::PopItemFlag();
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
}
|
||||
15
core/src/gui/style.h
Normal file
15
core/src/gui/style.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
namespace style {
|
||||
extern ImFont* baseFont;
|
||||
extern ImFont* bigFont;
|
||||
extern ImFont* hugeFont;
|
||||
|
||||
bool setDefaultStyle(std::string resDir);
|
||||
bool loadFonts(std::string resDir);
|
||||
void beginDisabled();
|
||||
void endDisabled();
|
||||
void testtt();
|
||||
}
|
||||
237
core/src/gui/theme_manager.cpp
Normal file
237
core/src/gui/theme_manager.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
#include <json.hpp>
|
||||
#include <gui/theme_manager.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
bool ThemeManager::loadThemesFromDir(std::string path) {
|
||||
// // TEST JUST TO DUMP THE ORIGINAL THEME
|
||||
// auto& style = ImGui::GetStyle();
|
||||
// ImVec4* colors = style.Colors;
|
||||
|
||||
// printf("\n\n");
|
||||
// for (auto [name, id] : IMGUI_COL_IDS) {
|
||||
// ImVec4 col = colors[id];
|
||||
// uint8_t r = 255 - (col.x * 255.0f);
|
||||
// uint8_t g = 255 - (col.y * 255.0f);
|
||||
// uint8_t b = 255 - (col.z * 255.0f);
|
||||
// uint8_t a = col.w * 255.0f;
|
||||
// printf("\"%s\": \"#%02X%02X%02X%02X\",\n", name.c_str(), r, g, b, a);
|
||||
// }
|
||||
// printf("\n\n");
|
||||
|
||||
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
spdlog::error("Theme directory doesn't exist: {0}", path);
|
||||
return false;
|
||||
}
|
||||
themes.clear();
|
||||
for (const auto & file : std::filesystem::directory_iterator(path)) {
|
||||
std::string _path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
loadTheme(_path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThemeManager::loadTheme(std::string path) {
|
||||
if (!std::filesystem::is_regular_file(path)) {
|
||||
spdlog::error("Theme file doesn't exist: {0}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load defaults in theme
|
||||
Theme thm;
|
||||
thm.author = "--";
|
||||
|
||||
// Load JSON
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
// Load theme name
|
||||
if (!data.contains("name")) {
|
||||
spdlog::error("Theme {0} is missing the name parameter", path);
|
||||
return false;
|
||||
}
|
||||
if (!data["name"].is_string()) {
|
||||
spdlog::error("Theme {0} contains invalid name field. Expected string", path);
|
||||
return false;
|
||||
}
|
||||
std::string name = data["name"];
|
||||
|
||||
if (themes.find(name) != themes.end()) {
|
||||
spdlog::error("A theme named '{0}' already exists", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load theme author if available
|
||||
if (data.contains("author")) {
|
||||
if (!data["author"].is_string()) {
|
||||
spdlog::error("Theme {0} contains invalid author field. Expected string", path);
|
||||
return false;
|
||||
}
|
||||
thm.author = data["author"];
|
||||
}
|
||||
|
||||
// Iterate through all parameters and check their contents
|
||||
std::map<std::string, std::string> params = data;
|
||||
for (auto const& [param, val] : params) {
|
||||
if (param == "name" || param == "author") { continue; }
|
||||
|
||||
// Exception for non-imgu colors
|
||||
if (param == "WaterfallBackground" || param == "ClearColor") {
|
||||
if (val[0] != '#' || !std::all_of(val.begin() + 1, val.end(), ::isxdigit) || val.length() != 9) {
|
||||
spdlog::error("Theme {0} contains invalid {1} field. Expected hex RGBA color", path, param);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isValid = false;
|
||||
|
||||
// If param is a color, check that it's a valid RGBA hex value
|
||||
if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) {
|
||||
if (val[0] != '#' || !std::all_of(val.begin() + 1, val.end(), ::isxdigit) || val.length() != 9) {
|
||||
spdlog::error("Theme {0} contains invalid {1} field. Expected hex RGBA color", path, param);
|
||||
return false;
|
||||
}
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
spdlog::error("Theme {0} contains unknown {1} field.", path, param);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
thm.data = data;
|
||||
themes[name] = thm;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThemeManager::applyTheme(std::string name) {
|
||||
if (themes.find(name) == themes.end()) {
|
||||
spdlog::error("Unknown theme: {0}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
auto& style = ImGui::GetStyle();
|
||||
|
||||
style.WindowRounding = 0.0f;
|
||||
style.ChildRounding = 0.0f;
|
||||
style.FrameRounding = 0.0f;
|
||||
style.GrabRounding = 0.0f;
|
||||
style.PopupRounding = 0.0f;
|
||||
style.ScrollbarRounding = 0.0f;
|
||||
|
||||
ImVec4* colors = style.Colors;
|
||||
Theme thm = themes[name];
|
||||
|
||||
uint8_t ret[4];
|
||||
std::map<std::string, std::string> params = thm.data;
|
||||
for (auto const& [param, val] : params) {
|
||||
if (param == "name" || param == "author") { continue; }
|
||||
|
||||
if (param == "WaterfallBackground") {
|
||||
decodeRGBA(val, ret);
|
||||
waterfallBg = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == "ClearColor") {
|
||||
decodeRGBA(val, ret);
|
||||
clearColor = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If param is a color, check that it's a valid RGBA hex value
|
||||
if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) {
|
||||
decodeRGBA(val, ret);
|
||||
colors[IMGUI_COL_IDS[param]] = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThemeManager::decodeRGBA(std::string str, uint8_t out[4]) {
|
||||
if (str[0] != '#' || !std::all_of(str.begin() + 1, str.end(), ::isxdigit) || str.length() != 9) {
|
||||
return false;
|
||||
}
|
||||
out[0] = std::stoi(str.substr(1, 2), NULL, 16);
|
||||
out[1] = std::stoi(str.substr(3, 2), NULL, 16);
|
||||
out[2] = std::stoi(str.substr(5, 2), NULL, 16);
|
||||
out[3] = std::stoi(str.substr(7, 2), NULL, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> ThemeManager::getThemeNames() {
|
||||
std::vector<std::string> names;
|
||||
for (auto [name, theme] : themes) { names.push_back(name); }
|
||||
return names;
|
||||
}
|
||||
|
||||
std::map<std::string, int> ThemeManager::IMGUI_COL_IDS = {
|
||||
{"Text", ImGuiCol_Text},
|
||||
{"TextDisabled", ImGuiCol_TextDisabled},
|
||||
{"WindowBg", ImGuiCol_WindowBg},
|
||||
{"ChildBg", ImGuiCol_ChildBg},
|
||||
{"PopupBg", ImGuiCol_PopupBg},
|
||||
{"Border", ImGuiCol_Border},
|
||||
{"BorderShadow", ImGuiCol_BorderShadow},
|
||||
{"FrameBg", ImGuiCol_FrameBg},
|
||||
{"FrameBgHovered", ImGuiCol_FrameBgHovered},
|
||||
{"FrameBgActive", ImGuiCol_FrameBgActive},
|
||||
{"TitleBg", ImGuiCol_TitleBg},
|
||||
{"TitleBgActive", ImGuiCol_TitleBgActive},
|
||||
{"TitleBgCollapsed", ImGuiCol_TitleBgCollapsed},
|
||||
{"MenuBarBg", ImGuiCol_MenuBarBg},
|
||||
{"ScrollbarBg", ImGuiCol_ScrollbarBg},
|
||||
{"ScrollbarGrab", ImGuiCol_ScrollbarGrab},
|
||||
{"ScrollbarGrabHovered", ImGuiCol_ScrollbarGrabHovered},
|
||||
{"ScrollbarGrabActive", ImGuiCol_ScrollbarGrabActive},
|
||||
{"CheckMark", ImGuiCol_CheckMark},
|
||||
{"SliderGrab", ImGuiCol_SliderGrab},
|
||||
{"SliderGrabActive", ImGuiCol_SliderGrabActive},
|
||||
{"Button", ImGuiCol_Button},
|
||||
{"ButtonHovered", ImGuiCol_ButtonHovered},
|
||||
{"ButtonActive", ImGuiCol_ButtonActive},
|
||||
{"Header", ImGuiCol_Header},
|
||||
{"HeaderHovered", ImGuiCol_HeaderHovered},
|
||||
{"HeaderActive", ImGuiCol_HeaderActive},
|
||||
{"Separator", ImGuiCol_Separator},
|
||||
{"SeparatorHovered", ImGuiCol_SeparatorHovered},
|
||||
{"SeparatorActive", ImGuiCol_SeparatorActive},
|
||||
{"ResizeGrip", ImGuiCol_ResizeGrip},
|
||||
{"ResizeGripHovered", ImGuiCol_ResizeGripHovered},
|
||||
{"ResizeGripActive", ImGuiCol_ResizeGripActive},
|
||||
{"Tab", ImGuiCol_Tab},
|
||||
{"TabHovered", ImGuiCol_TabHovered},
|
||||
{"TabActive", ImGuiCol_TabActive},
|
||||
{"TabUnfocused", ImGuiCol_TabUnfocused},
|
||||
{"TabUnfocusedActive", ImGuiCol_TabUnfocusedActive},
|
||||
{"PlotLines", ImGuiCol_PlotLines},
|
||||
{"PlotLinesHovered", ImGuiCol_PlotLinesHovered},
|
||||
{"PlotHistogram", ImGuiCol_PlotHistogram},
|
||||
{"PlotHistogramHovered", ImGuiCol_PlotHistogramHovered},
|
||||
{"TableHeaderBg", ImGuiCol_TableHeaderBg},
|
||||
{"TableBorderStrong", ImGuiCol_TableBorderStrong},
|
||||
{"TableBorderLight", ImGuiCol_TableBorderLight},
|
||||
{"TableRowBg", ImGuiCol_TableRowBg},
|
||||
{"TableRowBgAlt", ImGuiCol_TableRowBgAlt},
|
||||
{"TextSelectedBg", ImGuiCol_TextSelectedBg},
|
||||
{"DragDropTarget", ImGuiCol_DragDropTarget},
|
||||
{"NavHighlight", ImGuiCol_NavHighlight},
|
||||
{"NavWindowingHighlight", ImGuiCol_NavWindowingHighlight},
|
||||
{"NavWindowingDimBg", ImGuiCol_NavWindowingDimBg},
|
||||
{"ModalWindowDimBg", ImGuiCol_ModalWindowDimBg}
|
||||
};
|
||||
33
core/src/gui/theme_manager.h
Normal file
33
core/src/gui/theme_manager.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <imgui.h>
|
||||
#include <vector>
|
||||
#include <json.hpp>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
struct Theme {
|
||||
std::string author;
|
||||
json data;
|
||||
};
|
||||
|
||||
class ThemeManager {
|
||||
public:
|
||||
bool loadThemesFromDir(std::string path);
|
||||
bool loadTheme(std::string path);
|
||||
bool applyTheme(std::string name);
|
||||
|
||||
std::vector<std::string> getThemeNames();
|
||||
|
||||
ImVec4 waterfallBg = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);;
|
||||
ImVec4 clearColor = ImVec4(0.0666f, 0.0666f, 0.0666f, 1.0f);
|
||||
|
||||
private:
|
||||
static bool decodeRGBA(std::string str, uint8_t out[4]);
|
||||
|
||||
static std::map<std::string, int> IMGUI_COL_IDS;
|
||||
|
||||
std::map<std::string, Theme> themes;
|
||||
|
||||
};
|
||||
139
core/src/gui/tuner.cpp
Normal file
139
core/src/gui/tuner.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include <signal_path/signal_path.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/tuner.h>
|
||||
#include <string>
|
||||
|
||||
namespace tuner {
|
||||
|
||||
void centerTuning(std::string vfoName, double freq) {
|
||||
if (vfoName != "") {
|
||||
if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; }
|
||||
sigpath::vfoManager.setOffset(vfoName, 0);
|
||||
}
|
||||
double BW = gui::waterfall.getBandwidth();
|
||||
double viewBW = gui::waterfall.getViewBandwidth();
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
gui::waterfall.setCenterFrequency(freq);
|
||||
gui::waterfall.setViewOffset(0);
|
||||
gui::freqSelect.setFrequency(freq);
|
||||
sigpath::sourceManager.tune(freq);
|
||||
}
|
||||
|
||||
void normalTuning(std::string vfoName, double freq) {
|
||||
if (vfoName == "") {
|
||||
centerTuning(vfoName, freq);
|
||||
return;
|
||||
}
|
||||
if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; }
|
||||
|
||||
double viewBW = gui::waterfall.getViewBandwidth();
|
||||
double BW = gui::waterfall.getBandwidth();
|
||||
|
||||
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[vfoName];
|
||||
|
||||
double currentOff = vfo->centerOffset;
|
||||
double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
|
||||
double delta = freq - currentTune;
|
||||
|
||||
double newVFO = currentOff + delta;
|
||||
double vfoBW = vfo->bandwidth;
|
||||
double vfoBottom = newVFO - (vfoBW / 2.0);
|
||||
double vfoTop = newVFO + (vfoBW / 2.0);
|
||||
|
||||
double view = gui::waterfall.getViewOffset();
|
||||
double viewBottom = view - (viewBW / 2.0);
|
||||
double viewTop = view + (viewBW / 2.0);
|
||||
|
||||
double wholeFreq = gui::waterfall.getCenterFrequency();
|
||||
double bottom = -(BW / 2.0);
|
||||
double top = (BW / 2.0);
|
||||
|
||||
// VFO still fints in the view
|
||||
if (vfoBottom > viewBottom && vfoTop < viewTop) {
|
||||
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too low for current SDR tuning
|
||||
if (vfoBottom < bottom) {
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
|
||||
sigpath::vfoManager.setOffset(vfoName, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO too high for current SDR tuning
|
||||
if (vfoTop > top) {
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
|
||||
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
|
||||
sigpath::vfoManager.setOffset(vfoName, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
// VFO is still without the SDR's bandwidth
|
||||
if (delta < 0) {
|
||||
double newViewOff = vfoTop - (viewBW / 2.0) + (viewBW / 10.0);
|
||||
double newViewBottom = newViewOff - (viewBW / 2.0);
|
||||
double newViewTop = newViewOff + (viewBW / 2.0);
|
||||
|
||||
if (newViewBottom > bottom) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
|
||||
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
|
||||
sigpath::vfoManager.setCenterOffset(vfoName, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
}
|
||||
else {
|
||||
double newViewOff = vfoBottom + (viewBW / 2.0) - (viewBW / 10.0);
|
||||
double newViewBottom = newViewOff - (viewBW / 2.0);
|
||||
double newViewTop = newViewOff + (viewBW / 2.0);
|
||||
|
||||
if (newViewTop < top) {
|
||||
gui::waterfall.setViewOffset(newViewOff);
|
||||
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
|
||||
return;
|
||||
}
|
||||
|
||||
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
|
||||
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
|
||||
sigpath::vfoManager.setCenterOffset(vfoName, newVFOOffset);
|
||||
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
|
||||
sigpath::sourceManager.tune(freq - newVFOOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void iqTuning(double freq) {
|
||||
gui::waterfall.setCenterFrequency(freq);
|
||||
gui::waterfall.centerFreqMoved = true;
|
||||
sigpath::sourceManager.tune(freq);
|
||||
}
|
||||
|
||||
void tune(int mode, std::string vfoName, double freq) {
|
||||
switch (mode) {
|
||||
case TUNER_MODE_CENTER:
|
||||
centerTuning(vfoName, freq);
|
||||
break;
|
||||
case TUNER_MODE_NORMAL:
|
||||
normalTuning(vfoName, freq);
|
||||
break;
|
||||
case TUNER_MODE_LOWER_HALF:
|
||||
normalTuning(vfoName, freq);
|
||||
break;
|
||||
case TUNER_MODE_UPPER_HALF:
|
||||
normalTuning(vfoName, freq);
|
||||
break;
|
||||
case TUNER_MODE_IQ_ONLY:
|
||||
iqTuning(freq);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
core/src/gui/tuner.h
Normal file
20
core/src/gui/tuner.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <module.h>
|
||||
|
||||
namespace tuner {
|
||||
void centerTuning(std::string vfoName, double freq);
|
||||
void normalTuning(std::string vfoName, double freq);
|
||||
void iqTuning(double freq);
|
||||
|
||||
enum {
|
||||
TUNER_MODE_CENTER,
|
||||
TUNER_MODE_NORMAL,
|
||||
TUNER_MODE_LOWER_HALF,
|
||||
TUNER_MODE_UPPER_HALF,
|
||||
TUNER_MODE_IQ_ONLY,
|
||||
_TUNER_MODE_COUNT
|
||||
};
|
||||
|
||||
void tune(int mode, std::string vfoName, double freq);
|
||||
}
|
||||
114
core/src/gui/widgets/bandplan.cpp
Normal file
114
core/src/gui/widgets/bandplan.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <gui/widgets/bandplan.h>
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace bandplan {
|
||||
std::map<std::string, BandPlan_t> bandplans;
|
||||
std::vector<std::string> bandplanNames;
|
||||
std::string bandplanNameTxt;
|
||||
std::map<std::string, BandPlanColor_t> colorTable;
|
||||
|
||||
void generateTxt() {
|
||||
bandplanNameTxt = "";
|
||||
for (int i = 0; i < bandplanNames.size(); i++) {
|
||||
bandplanNameTxt += bandplanNames[i];
|
||||
bandplanNameTxt += '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const Band_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"type", b.type},
|
||||
{"start", b.start},
|
||||
{"end", b.end},
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, Band_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("type").get_to(b.type);
|
||||
j.at("start").get_to(b.start);
|
||||
j.at("end").get_to(b.end);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b) {
|
||||
j = json{
|
||||
{"name", b.name},
|
||||
{"country_name", b.countryName},
|
||||
{"country_code", b.countryCode},
|
||||
{"author_name", b.authorName},
|
||||
{"author_url", b.authorURL},
|
||||
{"bands", b.bands}
|
||||
};
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlan_t& b) {
|
||||
j.at("name").get_to(b.name);
|
||||
j.at("country_name").get_to(b.countryName);
|
||||
j.at("country_code").get_to(b.countryCode);
|
||||
j.at("author_name").get_to(b.authorName);
|
||||
j.at("author_url").get_to(b.authorURL);
|
||||
j.at("bands").get_to(b.bands);
|
||||
}
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct) {
|
||||
spdlog::error("ImGui color to JSON not implemented!!!");
|
||||
}
|
||||
|
||||
void from_json(const json& j, BandPlanColor_t& ct) {
|
||||
std::string col = j.get<std::string>();
|
||||
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
|
||||
return;
|
||||
}
|
||||
uint8_t r, g, b, a;
|
||||
r = std::stoi(col.substr(1, 2), NULL, 16);
|
||||
g = std::stoi(col.substr(3, 2), NULL, 16);
|
||||
b = std::stoi(col.substr(5, 2), NULL, 16);
|
||||
a = std::stoi(col.substr(7, 2), NULL, 16);
|
||||
ct.colorValue = IM_COL32(r, g, b, a);
|
||||
ct.transColorValue = IM_COL32(r, g, b, 100);
|
||||
}
|
||||
|
||||
void loadBandPlan(std::string path) {
|
||||
std::ifstream file(path.c_str());
|
||||
json data;
|
||||
file >> data;
|
||||
file.close();
|
||||
|
||||
BandPlan_t plan = data.get<BandPlan_t>();
|
||||
if (bandplans.find(plan.name) != bandplans.end()) {
|
||||
spdlog::error("Duplicate band plan name ({0}), not loading.", plan.name);
|
||||
return;
|
||||
}
|
||||
bandplans[plan.name] = plan;
|
||||
bandplanNames.push_back(plan.name);
|
||||
generateTxt();
|
||||
}
|
||||
|
||||
void loadFromDir(std::string path) {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
spdlog::error("Band Plan directory does not exist");
|
||||
return;
|
||||
}
|
||||
if (!std::filesystem::is_directory(path)) {
|
||||
spdlog::error("Band Plan directory isn't a directory...");
|
||||
return;
|
||||
}
|
||||
bandplans.clear();
|
||||
for (const auto & file : std::filesystem::directory_iterator(path)) {
|
||||
std::string path = file.path().generic_string();
|
||||
if (file.path().extension().generic_string() != ".json") {
|
||||
continue;
|
||||
}
|
||||
loadBandPlan(path);
|
||||
}
|
||||
}
|
||||
|
||||
void loadColorTable(json table) {
|
||||
colorTable = table.get<std::map<std::string, BandPlanColor_t>>();
|
||||
}
|
||||
};
|
||||
47
core/src/gui/widgets/bandplan.h
Normal file
47
core/src/gui/widgets/bandplan.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <json.hpp>
|
||||
#include <imgui/imgui.h>
|
||||
#include <stdint.h>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace bandplan {
|
||||
struct Band_t {
|
||||
std::string name;
|
||||
std::string type;
|
||||
double start;
|
||||
double end;
|
||||
};
|
||||
|
||||
void to_json(json& j, const Band_t& b);
|
||||
void from_json(const json& j, Band_t& b);
|
||||
|
||||
struct BandPlan_t {
|
||||
std::string name;
|
||||
std::string countryName;
|
||||
std::string countryCode;
|
||||
std::string authorName;
|
||||
std::string authorURL;
|
||||
std::vector<Band_t> bands;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlan_t& b);
|
||||
void from_json(const json& j, BandPlan_t& b);
|
||||
|
||||
struct BandPlanColor_t {
|
||||
uint32_t colorValue;
|
||||
uint32_t transColorValue;
|
||||
};
|
||||
|
||||
void to_json(json& j, const BandPlanColor_t& ct);
|
||||
void from_json(const json& j, BandPlanColor_t& ct);
|
||||
|
||||
void loadBandPlan(std::string path);
|
||||
void loadFromDir(std::string path);
|
||||
void loadColorTable(json table);
|
||||
|
||||
extern std::map<std::string, BandPlan_t> bandplans;
|
||||
extern std::vector<std::string> bandplanNames;
|
||||
extern std::string bandplanNameTxt;
|
||||
extern std::map<std::string, BandPlanColor_t> colorTable;
|
||||
};
|
||||
42
core/src/gui/widgets/constellation_diagram.cpp
Normal file
42
core/src/gui/widgets/constellation_diagram.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <gui/widgets/constellation_diagram.h>
|
||||
|
||||
namespace ImGui {
|
||||
ConstellationDiagram::ConstellationDiagram() {
|
||||
memset(buffer, 0, 1024 * sizeof(dsp::complex_t));
|
||||
}
|
||||
|
||||
void ConstellationDiagram::draw(const ImVec2& size_arg) {
|
||||
std::lock_guard<std::mutex> lck(bufferMtx);
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
ImGuiStyle& style = GetStyle();
|
||||
float pad = style.FramePadding.y;
|
||||
ImVec2 min = window->DC.CursorPos;
|
||||
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), CalcItemWidth());
|
||||
ImRect bb(min, ImVec2(min.x+size.x, min.y+size.y));
|
||||
float lineHeight = size.y;
|
||||
|
||||
ItemSize(size, style.FramePadding.y);
|
||||
if (!ItemAdd(bb, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
window->DrawList->AddRectFilled(min, ImVec2(min.x+size.x, min.y+size.y), IM_COL32(0,0,0,255));
|
||||
ImU32 col = ImGui::GetColorU32(ImGuiCol_CheckMark, 0.7f);
|
||||
float increment = size.x / 1024.0f;
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
if (buffer[i].re > 1.5f || buffer[i].re < -1.5f) { continue; }
|
||||
if (buffer[i].im > 1.5f || buffer[i].im < -1.5f) { continue; }
|
||||
window->DrawList->AddCircleFilled(ImVec2((((buffer[i].re / 1.5f) + 1) * (size.x*0.5f)) + min.x, (((buffer[i].im / 1.5f) + 1) * (size.y*0.5f)) + min.y), 2, col);
|
||||
}
|
||||
}
|
||||
|
||||
dsp::complex_t* ConstellationDiagram::acquireBuffer() {
|
||||
bufferMtx.lock();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void ConstellationDiagram::releaseBuffer() {
|
||||
bufferMtx.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
25
core/src/gui/widgets/constellation_diagram.h
Normal file
25
core/src/gui/widgets/constellation_diagram.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <dsp/stream.h>
|
||||
#include <mutex>
|
||||
#include <dsp/types.h>
|
||||
|
||||
namespace ImGui {
|
||||
class ConstellationDiagram {
|
||||
public:
|
||||
ConstellationDiagram();
|
||||
|
||||
void draw(const ImVec2& size_arg = ImVec2(0, 0));
|
||||
|
||||
dsp::complex_t* acquireBuffer();
|
||||
|
||||
void releaseBuffer();
|
||||
|
||||
private:
|
||||
std::mutex bufferMtx;
|
||||
dsp::complex_t buffer[1024];
|
||||
|
||||
};
|
||||
}
|
||||
77
core/src/gui/widgets/file_select.cpp
Normal file
77
core/src/gui/widgets/file_select.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <gui/widgets/file_select.h>
|
||||
#include <regex>
|
||||
#include <options.h>
|
||||
#include <filesystem>
|
||||
#include <gui/file_dialogs.h>
|
||||
|
||||
FileSelect::FileSelect(std::string defaultPath, std::vector<std::string> filter) {
|
||||
_filter = filter;
|
||||
setPath(defaultPath);
|
||||
}
|
||||
|
||||
bool FileSelect::render(std::string id) {
|
||||
bool _pathChanged = false;
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
float buttonWidth = ImGui::CalcTextSize("...").x + 20.0f;
|
||||
bool lastPathValid = pathValid;
|
||||
if (!lastPathValid) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
ImGui::SetNextItemWidth(menuColumnWidth - buttonWidth);
|
||||
if (ImGui::InputText(id.c_str(), strPath, 2047)) {
|
||||
path = std::string(strPath);
|
||||
std::string expandedPath = expandString(strPath);
|
||||
if (!std::filesystem::is_regular_file(expandedPath)) {
|
||||
pathValid = false;
|
||||
}
|
||||
else {
|
||||
pathValid = true;
|
||||
_pathChanged = true;
|
||||
}
|
||||
}
|
||||
if (!lastPathValid) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("..." + id + "_winselect").c_str(), ImVec2(buttonWidth - 8.0f, 0)) && !dialogOpen) {
|
||||
dialogOpen = true;
|
||||
if (workerThread.joinable()) { workerThread.join(); }
|
||||
workerThread = std::thread(&FileSelect::worker, this);
|
||||
}
|
||||
|
||||
_pathChanged |= pathChanged;
|
||||
pathChanged = false;
|
||||
return _pathChanged;
|
||||
}
|
||||
|
||||
void FileSelect::setPath(std::string path, bool markChanged) {
|
||||
this->path = path;
|
||||
std::string expandedPath = expandString(path);
|
||||
pathValid = std::filesystem::is_regular_file(expandedPath);
|
||||
if (markChanged) { pathChanged = true; }
|
||||
strcpy(strPath, path.c_str());
|
||||
}
|
||||
|
||||
std::string FileSelect::expandString(std::string input) {
|
||||
input = std::regex_replace(input, std::regex("%ROOT%"), options::opts.root);
|
||||
return std::regex_replace(input, std::regex("//"), "/");
|
||||
}
|
||||
|
||||
bool FileSelect::pathIsValid() {
|
||||
return pathValid;
|
||||
}
|
||||
|
||||
void FileSelect::worker() {
|
||||
auto file = pfd::open_file("Open File", pathValid ? std::filesystem::path(expandString(path)).parent_path().string() : "", _filter);
|
||||
std::vector<std::string> res = file.result();
|
||||
|
||||
if (res.size() > 0) {
|
||||
path = res[0];
|
||||
strcpy(strPath, path.c_str());
|
||||
pathChanged = true;
|
||||
}
|
||||
|
||||
pathValid = std::filesystem::is_regular_file(expandString(path));
|
||||
dialogOpen = false;
|
||||
}
|
||||
30
core/src/gui/widgets/file_select.h
Normal file
30
core/src/gui/widgets/file_select.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class FileSelect {
|
||||
public:
|
||||
FileSelect(std::string defaultPath, std::vector<std::string> filter = {"All Files", "*"});
|
||||
bool render(std::string id);
|
||||
void setPath(std::string path, bool markChanged = false);
|
||||
bool pathIsValid();
|
||||
|
||||
std::string expandString(std::string input);
|
||||
|
||||
std::string path = "";
|
||||
|
||||
|
||||
private:
|
||||
void worker();
|
||||
std::thread workerThread;
|
||||
std::vector<std::string> _filter;
|
||||
|
||||
bool pathValid = false;
|
||||
bool dialogOpen = false;
|
||||
char strPath[2048];
|
||||
bool pathChanged = false;
|
||||
};
|
||||
76
core/src/gui/widgets/folder_select.cpp
Normal file
76
core/src/gui/widgets/folder_select.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <gui/widgets/folder_select.h>
|
||||
#include <regex>
|
||||
#include <options.h>
|
||||
#include <filesystem>
|
||||
#include <gui/file_dialogs.h>
|
||||
|
||||
FolderSelect::FolderSelect(std::string defaultPath) {
|
||||
setPath(defaultPath);
|
||||
}
|
||||
|
||||
bool FolderSelect::render(std::string id) {
|
||||
bool _pathChanged = false;
|
||||
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
|
||||
|
||||
float buttonWidth = ImGui::CalcTextSize("...").x + 20.0f;
|
||||
bool lastPathValid = pathValid;
|
||||
if (!lastPathValid) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
ImGui::SetNextItemWidth(menuColumnWidth - buttonWidth);
|
||||
if (ImGui::InputText(id.c_str(), strPath, 2047)) {
|
||||
path = std::string(strPath);
|
||||
std::string expandedPath = expandString(strPath);
|
||||
if (!std::filesystem::is_directory(expandedPath)) {
|
||||
pathValid = false;
|
||||
}
|
||||
else {
|
||||
pathValid = true;
|
||||
_pathChanged = true;
|
||||
}
|
||||
}
|
||||
if (!lastPathValid) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("..." + id + "_winselect").c_str(), ImVec2(buttonWidth - 8.0f, 0)) && !dialogOpen) {
|
||||
dialogOpen = true;
|
||||
if (workerThread.joinable()) { workerThread.join(); }
|
||||
workerThread = std::thread(&FolderSelect::worker, this);
|
||||
}
|
||||
|
||||
_pathChanged |= pathChanged;
|
||||
pathChanged = false;
|
||||
return _pathChanged;
|
||||
}
|
||||
|
||||
void FolderSelect::setPath(std::string path, bool markChanged) {
|
||||
this->path = path;
|
||||
std::string expandedPath = expandString(path);
|
||||
pathValid = std::filesystem::is_directory(expandedPath);
|
||||
if (markChanged) { pathChanged = true; }
|
||||
strcpy(strPath, path.c_str());
|
||||
}
|
||||
|
||||
std::string FolderSelect::expandString(std::string input) {
|
||||
input = std::regex_replace(input, std::regex("%ROOT%"), options::opts.root);
|
||||
return std::regex_replace(input, std::regex("//"), "/");
|
||||
}
|
||||
|
||||
bool FolderSelect::pathIsValid() {
|
||||
return pathValid;
|
||||
}
|
||||
|
||||
void FolderSelect::worker() {
|
||||
auto fold = pfd::select_folder("Select Folder", pathValid ? std::filesystem::path(expandString(path)).parent_path().string() : "");
|
||||
std::string res = fold.result();
|
||||
|
||||
if (res != "") {
|
||||
path = res;
|
||||
strcpy(strPath, path.c_str());
|
||||
pathChanged = true;
|
||||
}
|
||||
|
||||
pathValid = std::filesystem::is_directory(expandString(path));
|
||||
dialogOpen = false;
|
||||
}
|
||||
28
core/src/gui/widgets/folder_select.h
Normal file
28
core/src/gui/widgets/folder_select.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class FolderSelect {
|
||||
public:
|
||||
FolderSelect(std::string defaultPath);
|
||||
bool render(std::string id);
|
||||
void setPath(std::string path, bool markChanged = false);
|
||||
bool pathIsValid();
|
||||
|
||||
std::string expandString(std::string input);
|
||||
|
||||
std::string path = "";
|
||||
|
||||
|
||||
private:
|
||||
void worker();
|
||||
std::thread workerThread;
|
||||
|
||||
bool pathValid = false;
|
||||
bool dialogOpen = false;
|
||||
char strPath[2048];
|
||||
bool pathChanged = false;
|
||||
};
|
||||
244
core/src/gui/widgets/frequency_select.cpp
Normal file
244
core/src/gui/widgets/frequency_select.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
#include <gui/widgets/frequency_select.h>
|
||||
#include <config.h>
|
||||
#include <gui/style.h>
|
||||
#include <gui/gui.h>
|
||||
#include <glfw_window.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#endif
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
|
||||
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
|
||||
}
|
||||
|
||||
FrequencySelect::FrequencySelect() {
|
||||
|
||||
}
|
||||
|
||||
void FrequencySelect::init() {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digits[i] = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencySelect::onPosChange() {
|
||||
ImVec2 digitSz = ImGui::CalcTextSize("0");
|
||||
ImVec2 commaSz = ImGui::CalcTextSize(".");
|
||||
int digitHeight = digitSz.y;
|
||||
int digitWidth = digitSz.x;
|
||||
int commaOffset = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
digitTopMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y);
|
||||
digitBottomMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y + (digitHeight / 2));
|
||||
|
||||
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + (digitHeight / 2));
|
||||
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + digitHeight);
|
||||
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += commaSz.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrequencySelect::onResize() {
|
||||
|
||||
}
|
||||
|
||||
void FrequencySelect::incrementDigit(int i) {
|
||||
if (i < 0) {
|
||||
return;
|
||||
}
|
||||
if (digits[i] < 9) {
|
||||
digits[i]++;
|
||||
}
|
||||
else {
|
||||
digits[i] = 0;
|
||||
incrementDigit(i - 1);
|
||||
}
|
||||
frequencyChanged = true;
|
||||
}
|
||||
|
||||
void FrequencySelect::decrementDigit(int i) {
|
||||
if (i < 0) {
|
||||
return;
|
||||
}
|
||||
if (digits[i] > 0) {
|
||||
digits[i]--;
|
||||
}
|
||||
else {
|
||||
if (i == 0) { return; }
|
||||
|
||||
// Check if there are non zero digits afterwards
|
||||
bool otherNoneZero = false;
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
if (digits[j] > 0) {
|
||||
otherNoneZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!otherNoneZero) { return; }
|
||||
|
||||
digits[i] = 9;
|
||||
decrementDigit(i - 1);
|
||||
}
|
||||
frequencyChanged = true;
|
||||
}
|
||||
|
||||
void FrequencySelect::moveCursorToDigit(int i) {
|
||||
double xpos, ypos;
|
||||
glfwGetCursorPos(core::window, &xpos, &ypos);
|
||||
float nxpos = (digitTopMaxs[i].x + digitTopMins[i].x) / 2.0f;
|
||||
glfwSetCursorPos(core::window, nxpos, ypos);
|
||||
}
|
||||
|
||||
void FrequencySelect::draw() {
|
||||
window = ImGui::GetCurrentWindow();
|
||||
widgetPos = ImGui::GetWindowContentRegionMin();
|
||||
widgetEndPos = ImGui::GetWindowContentRegionMax();
|
||||
ImVec2 cursorPos = ImGui::GetCursorPos();
|
||||
widgetPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetPos.y += window->Pos.y - 3;
|
||||
widgetEndPos.x += window->Pos.x + cursorPos.x;
|
||||
widgetEndPos.y += window->Pos.y - 3;
|
||||
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
|
||||
|
||||
ImGui::PushFont(style::bigFont);
|
||||
|
||||
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
|
||||
lastWidgetPos = widgetPos;
|
||||
onPosChange();
|
||||
}
|
||||
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
|
||||
lastWidgetSize = widgetSize;
|
||||
onResize();
|
||||
}
|
||||
|
||||
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
|
||||
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
|
||||
|
||||
ImVec2 digitSz = ImGui::CalcTextSize("0");
|
||||
ImVec2 commaSz = ImGui::CalcTextSize(".");
|
||||
int digitHeight = digitSz.y;
|
||||
int digitWidth = digitSz.x;
|
||||
int commaOffset = 0;
|
||||
bool zeros = true;
|
||||
|
||||
ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y)));
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (digits[i] != 0) {
|
||||
zeros = false;
|
||||
}
|
||||
sprintf(buf, "%d", digits[i]);
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, buf);
|
||||
if ((i + 1) % 3 == 0 && i < 11) {
|
||||
commaOffset += commaSz.x;
|
||||
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y),
|
||||
zeros ? disabledColor : textColor, ".");
|
||||
}
|
||||
}
|
||||
|
||||
if (!gui::mainWindow.lockWaterfallControls) {
|
||||
ImVec2 mousePos = ImGui::GetMousePos();
|
||||
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
int mw = ImGui::GetIO().MouseWheel;
|
||||
bool onDigit = false;
|
||||
bool hovered = false;
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
onDigit = false;
|
||||
if (isInArea(mousePos, digitTopMins[i], digitTopMaxs[i])) {
|
||||
window->DrawList->AddRectFilled(digitTopMins[i], digitTopMaxs[i], IM_COL32(255, 0, 0, 75));
|
||||
if (leftClick) {
|
||||
incrementDigit(i);
|
||||
}
|
||||
onDigit = true;
|
||||
}
|
||||
if (isInArea(mousePos, digitBottomMins[i], digitBottomMaxs[i])) {
|
||||
window->DrawList->AddRectFilled(digitBottomMins[i], digitBottomMaxs[i], IM_COL32(0, 0, 255, 75));
|
||||
if (leftClick) {
|
||||
decrementDigit(i);
|
||||
}
|
||||
onDigit = true;
|
||||
}
|
||||
if (onDigit) {
|
||||
hovered = true;
|
||||
if (rightClick || (ImGui::IsKeyPressed(GLFW_KEY_DELETE) || ImGui::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER))) {
|
||||
for (int j = i; j < 12; j++) {
|
||||
digits[j] = 0;
|
||||
}
|
||||
|
||||
frequencyChanged = true;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_UP)) {
|
||||
incrementDigit(i);
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_DOWN)) {
|
||||
decrementDigit(i);
|
||||
}
|
||||
if ((ImGui::IsKeyPressed(GLFW_KEY_LEFT) || ImGui::IsKeyPressed(GLFW_KEY_BACKSPACE)) && i > 0) {
|
||||
moveCursorToDigit(i - 1);
|
||||
}
|
||||
if (ImGui::IsKeyPressed(GLFW_KEY_RIGHT) && i < 11) {
|
||||
moveCursorToDigit(i + 1);
|
||||
}
|
||||
|
||||
auto chars = ImGui::GetIO().InputQueueCharacters;
|
||||
|
||||
// For each keyboard characters, type it
|
||||
for (int j = 0; j < chars.Size; j++) {
|
||||
if (chars[j] >= '0' && chars[j] <= '9') {
|
||||
digits[i + j] = chars[j] - '0';
|
||||
if ((i + j) < 11) { moveCursorToDigit(i + j + 1); }
|
||||
frequencyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mw != 0) {
|
||||
int count = abs(mw);
|
||||
for (int j = 0; j < count; j++) {
|
||||
mw > 0 ? incrementDigit(i) : decrementDigit(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
digitHovered = hovered;
|
||||
}
|
||||
|
||||
uint64_t freq = 0;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
freq += digits[i] * pow(10, 11 - i);
|
||||
}
|
||||
|
||||
uint64_t orig = freq;
|
||||
freq = std::clamp<uint64_t>(freq, minFreq, maxFreq);
|
||||
if (freq != orig && limitFreq) {
|
||||
setFrequency(freq);
|
||||
}
|
||||
else {
|
||||
frequency = orig;
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17);
|
||||
|
||||
//ImGui::NewLine();
|
||||
}
|
||||
|
||||
void FrequencySelect::setFrequency(int64_t freq) {
|
||||
freq = std::max<int64_t>(0, freq);
|
||||
int i = 11;
|
||||
for (uint64_t f = freq; i >= 0; i--) {
|
||||
digits[i] = f % 10;
|
||||
f -= digits[i];
|
||||
f /= 10;
|
||||
}
|
||||
frequency = freq;
|
||||
}
|
||||
@@ -1,22 +1,29 @@
|
||||
#pragma once
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <stdint.h>
|
||||
|
||||
class FrequencySelect {
|
||||
public:
|
||||
FrequencySelect();
|
||||
void init();
|
||||
void draw();
|
||||
void setFrequency(long freq);
|
||||
void setFrequency(int64_t freq);
|
||||
|
||||
long frequency;
|
||||
uint64_t frequency;
|
||||
bool frequencyChanged = false;
|
||||
bool digitHovered = false;
|
||||
|
||||
bool limitFreq;
|
||||
uint64_t minFreq;
|
||||
uint64_t maxFreq;
|
||||
|
||||
private:
|
||||
void onPosChange();
|
||||
void onResize();
|
||||
void incrementDigit(int i);
|
||||
void decrementDigit(int i);
|
||||
void moveCursorToDigit(int i);
|
||||
|
||||
ImVec2 widgetPos;
|
||||
ImVec2 widgetEndPos;
|
||||
@@ -26,7 +33,6 @@ private:
|
||||
ImVec2 lastWidgetSize;
|
||||
|
||||
ImGuiWindow* window;
|
||||
ImFont* font;
|
||||
|
||||
int digits[12];
|
||||
ImVec2 digitBottomMins[12];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user