423 Commits

Author SHA1 Message Date
c488d72ce2 pushing work for the future new source system 2023-03-04 14:18:52 +01:00
5b47f900a6 more android bullshit 2023-02-25 20:59:12 +01:00
82fd3732a9 hopefully the last fix for android, this is getting annoying 2023-02-25 20:33:03 +01:00
8d6ed17e01 more android fixes 2023-02-25 20:19:34 +01:00
aa2071e2df more android fixes, I hate this OS so much 2023-02-25 20:00:04 +01:00
b32618708f fixed android one more time 2023-02-25 19:45:13 +01:00
ac875aee3b bugfix macos and android 2023-02-25 19:30:58 +01:00
df83d65271 changes 2023-02-25 18:14:35 +01:00
7723d15e8f Switched to a custom logging lib instead of that spdlog junk 2023-02-25 18:12:34 +01:00
a8cbc37e0d removed unecessary include 2023-02-23 10:05:04 +01:00
0d149f997f CMake install prefix bugfix when generating several times 2023-02-23 09:31:27 +01:00
7e80bbd02c added missing rigctl client 2023-02-22 21:45:19 +01:00
edb4ac45b2 more work on the rigctl module but removed tracking for now 2023-02-22 20:55:23 +01:00
63416fe93a added missing files 2023-02-21 20:46:28 +01:00
0fedcf8745 Added audio source 2023-02-21 19:38:31 +01:00
fe821fb830 Removed debugging junk 2023-02-19 18:58:39 +01:00
ea882cb285 Changed M17 modulation parameters 2023-02-19 18:54:33 +01:00
13e81c9f6b attempt at enabling the plutosdr in android build 2023-02-18 20:22:29 +01:00
290c989451 attempt at re-enabling M17 on android 2023-02-18 18:22:53 +01:00
b7ca19583a the macos CI is wasting more of my time again 2023-02-18 16:56:41 +01:00
1f97e9e10b Improved detection of M17 frames to avoid spurious decodes 2023-02-18 16:08:57 +01:00
93e985ab54 bugfix 2023-02-16 07:59:01 +01:00
8f972ee0b2 Added proper cmake debug support 2023-02-15 20:37:13 +01:00
570b8dbd7c Merge pull request #991 from AlexandreRouma/better_cmake
Better cmake
2023-02-15 19:34:41 +01:00
c0c5b1186c Fixed bug with audio sink 2023-02-15 17:28:02 +01:00
d5a9538d0c bugfix 2023-02-15 15:39:34 +01:00
7cfc30ee6e new cmake stuff 2023-02-14 16:25:05 +01:00
a1b6cbb38a Addded label to bandwidth selection of SDRplay Source 2023-02-13 09:38:26 +01:00
a3b13e572b Merge pull request #946 from CircuitChaos/fix-kg-sstv-name
Fixed KG SSTV (KG-STV) decoder name
2023-02-12 16:32:18 +01:00
16eaa0cf59 Fixed network library which also fixes RTL-TCP source 2023-02-09 16:55:37 +01:00
fd65984762 fixed build again 2023-02-08 01:40:44 +01:00
7094368113 fixed build 2023-02-08 01:19:11 +01:00
208851ebc5 cleanup 2023-02-07 12:04:29 +01:00
0a6fbdb393 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-02-07 08:33:54 +01:00
3451edb131 refactoring 2023-02-07 08:33:47 +01:00
bbf0c17cb8 Fixed missing include 2023-02-03 04:23:43 +01:00
7758c40bd7 Added missing files 2023-02-03 02:38:19 +01:00
8a2d0fe56b Recorder bugfix + rewrote RTL-TCP source to use new network lib 2023-02-03 02:38:08 +01:00
31ff7f3224 fixed build 2023-01-26 03:05:32 +01:00
e59d804b31 more work 2023-01-26 02:55:11 +01:00
66bbc93535 cleanup 2023-01-26 02:54:29 +01:00
ab5d7a73c1 USRP samplerate selection fix attempt 2023-01-21 00:16:31 +01:00
7a1a37fbf6 Fixed broken bandwidth options 2023-01-20 23:43:46 +01:00
a58683f748 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-01-20 17:52:42 +01:00
d020dcc4a3 Added bandwidth and clock selection to USRP source 2023-01-20 17:52:35 +01:00
691ab585e2 Merge pull request #964 from KentuckyFriedData/patch-3
Fix wrong target name
2023-01-13 22:44:28 +01:00
bad8ecba49 Fixed needless config saves 2023-01-13 22:43:42 +01:00
d21a6af5a9 Fix wrong target name 2023-01-13 13:05:01 -08:00
a53dc1a6ae Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-01-13 17:11:23 +01:00
b3222003b1 bugfixes 2023-01-13 17:11:16 +01:00
e3914ebdc6 more macos CI fixes 2023-01-09 16:32:23 +01:00
1ae1cc0e77 Fixed missing samplerate update in new recorder 2023-01-09 05:51:58 +01:00
55db98d1df Update readme.md 2023-01-06 00:30:32 +01:00
90ce82190b updated readme 2023-01-06 00:29:33 +01:00
a1cbc69a65 temporarily removed the ignore silence option since it doesn't work properly 2023-01-02 23:01:45 +01:00
ffefd9cce8 fixed stereo option not persistent and added todos for ignore silence 2023-01-02 21:00:15 +01:00
4d890e78ed MacOS is wasting so much of my time omfg 2023-01-02 18:23:55 +01:00
4eabc829fa even more fixes 2023-01-02 18:06:49 +01:00
ee79bf9f81 more macos ci fix 2023-01-02 17:54:05 +01:00
447d0e969e MacOS CI fix 2023-01-02 17:16:12 +01:00
637d683c0a Merge pull request #920 from AlexandreRouma/new_recorder
New recorder
2023-01-02 02:37:15 +01:00
5773419257 added missing include 2023-01-02 02:09:41 +01:00
385b34a0a1 bugfix 2023-01-02 01:24:30 +01:00
8771e4bf09 final fixes 2023-01-01 03:20:49 +01:00
0ba0a3ab97 Fixed KG SSTV (KG-STV) decoder name 2022-12-26 02:57:57 +01:00
9b33d8d63b Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-12-12 00:10:56 +01:00
bd99210fd3 more work on the spectran 2022-12-12 00:10:39 +01:00
a426f7b3ab Merge pull request #934 from RAD750/mmds_converter
Added MMDS converter offset preset
2022-12-10 20:15:44 +01:00
3d4c7550be fixed 2022-12-10 20:11:49 +01:00
d14fc3805c Added MMDS converter offset preset 2022-12-10 13:43:14 +01:00
ea23c44266 More work on the spectran source 2022-12-06 22:28:58 +01:00
fa2e13f3ea more work on the spectran 2022-12-06 20:54:23 +01:00
643d47fe47 stream fixes 2022-12-06 16:10:11 +01:00
5eba556605 Merge pull request #931 from hb9fxq/patch-2
Update spectran_source CMakeLists.txt
2022-12-06 14:53:09 +01:00
5c690c9753 more work 2022-12-06 14:52:42 +01:00
643cad3581 Update spectran_source CMakeLists.txt 2022-12-06 14:33:18 +01:00
bd854b590e Work on spectran sources 2022-12-06 08:23:58 +01:00
f163e926c7 spectran http source + hermes source cleanup 2022-12-04 02:10:34 +01:00
d069fb3af8 more fixes 2022-11-25 20:32:12 +01:00
f97ca9ac05 almost done with the new recorder 2022-11-23 10:22:29 +01:00
b104e82874 Merge pull request #915 from jeffrizzo/riz-usa-bandplan
Correct USA amateur allocations below 2GHz
2022-11-23 10:03:27 +01:00
3520424208 Update usa.json 2022-11-23 08:57:50 +01:00
9ae6a74408 fixed CI again 2022-11-22 13:27:56 +01:00
5eb42b61e9 disabled automatic CI for pull requests 2022-11-22 13:16:40 +01:00
a1259d7d32 undid it, it was stupid 2022-11-22 12:41:16 +01:00
31ac8f7b70 disabled CI for certain releases 2022-11-22 12:40:49 +01:00
9f472330f8 fixed CI 2022-11-22 11:56:57 +01:00
df91f56283 maybe better way to save release 2022-11-22 11:14:31 +01:00
f022c21f11 fix nightly release 2022-11-22 09:16:14 +01:00
5aac9d66e0 fixed typo 2022-11-22 08:39:46 +01:00
a9a0798d7d update nightly release automagically 2022-11-22 08:34:59 +01:00
bd947c2669 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-11-21 22:37:31 +01:00
6adf0baaa7 fixed CI warnings 2022-11-21 22:37:25 +01:00
fb10d0f917 Merge pull request #708 from hb9fxq/patch-1
Allow user defined CMAKE_INSTALL_PREFIX
2022-11-21 17:23:04 +01:00
61f19f44a2 Correct USA amateur allocations below 2GHz
- add 2200m band
 - add 630m band
 - fix 80m band - in US, it's 3.5-4.0 MHz
 - 60m band is 5 discrete "channels"
 - 40m band in US is 7.0-7.3 MHz
 - 10m band in US is 28.0-29.7 MHz
 - 1.25m band is in two parts; 219-220MHz and 222-225 MHz

Source: ARRL, FCC
2022-11-15 10:24:10 -08:00
638306e2da added hermes module to android build and cleanup 2022-11-12 18:00:25 +01:00
2ac1a38ea4 bugfix to hermes band filters 2022-11-12 17:17:19 +01:00
1c373e9cdb bunch of bugfix and new features 2022-11-12 02:19:53 +01:00
c4bac3b298 twinrx fix 2022-10-23 21:11:04 +02:00
0a4c191054 windows CI fix 2022-10-23 20:13:14 +02:00
c72c8d056d fixed USRP channel name bug 2022-10-23 20:11:35 +02:00
d74ea13878 fixed missing USRP source build 2022-10-23 18:05:10 +02:00
d16c08c8b2 random fixes 2022-10-23 17:41:10 +02:00
ebc9911f18 fixes 2022-10-23 16:52:33 +02:00
aa06db4d7a Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-10-23 16:41:56 +02:00
9ccc14848b USRP source 2022-10-23 16:41:45 +02:00
09d814e845 Fixed typo in readme 2022-10-19 11:02:23 +02:00
d4a6ada56e added missing include 2022-10-18 11:43:44 +02:00
c4fd285f7b fixed MacOS instructions 2022-10-18 11:36:35 +02:00
cc6b239d22 added MacOS instructions 2022-10-18 11:34:11 +02:00
a9e8db2a24 cleanup + beginning of hermes source 2022-10-18 11:09:22 +02:00
8eed0fcc9c fixed not init variable 2022-10-11 17:49:51 +02:00
68ba6d7d19 Merge pull request #886 from AlexandreRouma/rigctl_client
Beginning of rigctl client (not yet usable)
2022-10-11 17:43:41 +02:00
a85bae9527 Update CMakeLists.txt 2022-10-11 17:43:20 +02:00
34b0577f3b more work on the new recorder 2022-10-11 17:40:38 +02:00
52a4143cf5 code cleanup and fixes 2022-09-28 17:15:38 +02:00
b65da2fd49 work 2022-09-28 15:55:50 +02:00
c95c7b18af added invert iq option 2022-09-27 15:43:33 +02:00
f4bd483410 more work 2022-09-26 17:04:24 +02:00
869b3e0abd thread safety + stop recorder on audio stream deselect 2022-09-26 15:08:01 +02:00
3421aae9a2 more work on recorder 2022-09-26 14:57:06 +02:00
f49fbd2a73 fixed RSP1A DAB notch 2022-09-25 01:03:06 +02:00
5b8b344142 added missing files 2022-09-25 01:01:43 +02:00
0c8f6ab836 new recorder stuff 2022-09-25 01:01:07 +02:00
4047d3bcc2 Removed outdated MacOS instructions 2022-09-05 15:22:07 +02:00
dcb6321531 Fixed android text bug and updated readme 2022-09-04 18:00:39 +02:00
bd9df6ecf9 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-09-04 01:17:29 +02:00
81ac817639 added dummy debug keystore 2022-09-04 01:17:20 +02:00
fccd08a6e0 Merge pull request #860 from rs1729/fix_fprintf_percent
core/src/dsp/multirate/rational_resampler.h
2022-09-03 20:58:52 +02:00
e97dcecc6e Disabled bionic CI 2022-09-03 20:13:14 +02:00
54db4b41a4 still not working.. 2022-09-03 20:08:54 +02:00
70ff76e553 more fixes to the bionic ci (update your distros ffs) 2022-09-03 19:53:05 +02:00
0f7c17f007 fix again 2022-09-03 19:49:50 +02:00
6cf474144b ubuntu bionic CI fix 2022-09-03 19:48:55 +02:00
7ae59681d5 save APK 2022-09-03 19:41:39 +02:00
c7a1bda364 fixed android CI 2022-09-03 19:05:01 +02:00
1c02f4edca fix 2022-09-03 18:54:08 +02:00
4479170cc2 attempt at adding android CI 2022-09-03 18:53:14 +02:00
cd71becd8a android fix 2022-09-03 00:38:18 +02:00
72b895fc67 android fixes 2022-09-02 20:08:16 +02:00
6c93d9b2fa fix fprintf % error 2022-09-02 19:59:29 +02:00
ce3fea3747 fixed lowpass setting not presistent for NFM 2022-09-01 18:12:59 +02:00
9f0daf7d45 added scanner status indicator and enabled the scanner on android 2022-08-31 14:59:22 +02:00
6d784dfe27 enabled scanner 2022-08-30 16:07:49 +02:00
efcf26f0ac Merge pull request #850 from EmstallZ/master
Fix typos
2022-08-29 19:36:48 +02:00
14e37b41d4 Now showing the serial number on the RTL-SDR 2022-08-27 00:31:05 +02:00
ad36ede26b hackrf code cleanup 2022-08-27 00:03:46 +02:00
18a924d3a9 Fix typos 2022-08-22 14:41:39 +02:00
fb85177987 Fixed recording bug when decimation is enabled (issue #828) 2022-08-03 00:59:44 +02:00
90a8f617e9 Removed deprecated distros and fixed macos CI (hopefully) 2022-07-28 17:33:55 +02:00
575a941e24 More DSP cleanup + Remove FastFFT option because it should never be used 2022-07-27 21:35:36 +02:00
8efd5cd01a Update issue templates 2022-07-24 04:00:20 +02:00
50d7e5f86d Update issue templates 2022-07-24 03:45:02 +02:00
dcda49aea8 Update issue templates 2022-07-24 03:39:46 +02:00
3262f54100 Update issue templates 2022-07-24 03:35:27 +02:00
63bb9ad9af Fixed SDRplay FM/MW notch control 2022-07-16 22:42:10 +02:00
277d399e48 fixed limesdr antenna not set on startup and worked around limesuite bug 2022-07-16 02:36:03 +02:00
25f09a355e Added meteor recovery option 2022-07-16 00:08:25 +02:00
5bf5a10a12 bugfix + work on scanner 2022-07-15 17:17:53 +02:00
8b7afd88f9 fixed PCL bug 2022-07-11 16:18:07 +02:00
c57482771b Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-07-10 18:27:36 +02:00
e03e95cc54 Fixed autostart for networked sources 2022-07-10 18:03:17 +02:00
76b41cb9ab Merge pull request #802 from dforsi/fix/typos
Fix spelling errors
2022-07-10 00:05:40 +02:00
55b468a02e Fix spelling errors
Fixed with
codespell --ignore-words-list=hist,parm,sur --skip=./core/src/imgui,./core/src/json.hpp,./core/libcorrect,./core/src/spdlog,./misc_modules/discord_integration/discord-rpc,./misc_modules/discord_integration/discord-rpc/include/rapidjson,./source_modules/sddc_source/src/libsddc --write-changes
2022-07-09 22:16:05 +02:00
039ef5eae3 removed SDDC option 2022-07-09 15:55:32 +02:00
d3e9ebef72 removed broken SDDC source 2022-07-09 15:54:39 +02:00
dbedc0d352 fixed package version not matching 2022-07-08 20:31:53 +02:00
d422230ae9 Merge pull request #713 from Ozies/master
Update Australia Band Plan
2022-07-08 19:15:12 +02:00
db5914a4d1 Merge pull request #722 from CamK06/master
Update Canada bandplan ...again
2022-07-08 19:15:00 +02:00
164970abf3 Merge pull request #736 from Pytlicek/master
Add Slovakia Band Plan
2022-07-08 19:14:20 +02:00
76c744a1bb Merge pull request #779 from cropinghigh/patch-3
Fix PMR frequencies + remove broken t.c. band
2022-07-08 19:13:52 +02:00
e5f73e36d9 Merge pull request #798 from RAD750/italy-bandplan
Added Italian bandplan
2022-07-08 19:13:36 +02:00
381c9d0e4b Merge pull request #801 from AlexandreRouma/new_dsp
New dsp + code cleanups
2022-07-08 19:13:03 +02:00
30b6c2b1da Code cleanup and optimisation$ 2022-07-08 17:30:43 +02:00
3ab8badb6a Fixed waterfall being cleared for no reason 2022-07-08 16:02:50 +02:00
96f5b37f76 Added RDS error correction (still needs better clock recovery) 2022-07-08 01:02:36 +02:00
bdd4998b1b fixed blury icon and recorder level meter 2022-07-07 22:14:40 +02:00
ae149b256b Fixed recorder bug 2022-07-07 20:07:13 +02:00
06210ae825 Fixed RDS not enabled when in mono even if RDS checkbox checked 2022-07-07 00:45:15 +02:00
b5857e2078 bugfix because std::chrono is stupid 2022-07-06 22:32:27 +02:00
edf22ccfe8 Added basic RDS support, no error correction yet 2022-07-06 22:11:49 +02:00
1af234e379 Added Italian bandplan based on Piano Nazionale della Ripartizione delle Frequenze (PNRF) updated October 5th, 2018 2022-07-03 17:15:02 +02:00
46f17019a7 cleanup + bugfix 2022-07-02 18:30:13 +02:00
74fd30f08c Added back the digital demodulators 2022-07-02 16:53:09 +02:00
ff6754099f More cleanup 2022-06-26 23:36:31 +02:00
3f6687659e Prototype noise blanker 2022-06-26 03:42:22 +02:00
c3ddffb3a9 UI fix 2022-06-23 23:37:12 +02:00
7d64d78d67 rollback 2022-06-23 22:25:39 +02:00
fb0e0d732b Attempted cmake cleanup 2022-06-23 22:21:25 +02:00
db034527e9 cleanup and better defaultzs 2022-06-23 21:30:38 +02:00
97643edf2f Fixed crash on radio disable/enable 2022-06-23 01:10:24 +02:00
01e1430847 final attempt at a fix 2022-06-22 05:28:03 +02:00
218fd6ccf4 another attempt at a fix 2022-06-22 05:20:40 +02:00
f2b8418a25 hopefully a fix for the linux crash 2022-06-22 04:58:03 +02:00
b943e6e869 Code cleanup, bugfix and optimisation 2022-06-21 19:54:03 +02:00
f7c566f652 More work 2022-06-21 17:24:48 +02:00
834890b69a maybe macOS fixes and other 2022-06-20 19:55:13 +02:00
53afaeda9e Fixes 2022-06-20 18:15:35 +02:00
ce1b0d0170 MacOS do be wasting my time 2022-06-19 23:50:34 +02:00
927bbab330 another dumb macos fix 2022-06-19 23:30:02 +02:00
fd5970b35a maybe a macos compilation fix idk 2022-06-19 23:03:55 +02:00
1dddbadd04 More work 2022-06-18 00:33:08 +02:00
ccc57cddc7 hopefully the final bugfix 2022-06-17 19:43:16 +02:00
9b1ec79d61 more stuff idk at this point$ 2022-06-17 19:14:55 +02:00
e06646367b another synthax error 2022-06-17 18:47:09 +02:00
b9e269f9dc roses are red, violets are blue, missing ';' on line 32 2022-06-17 18:37:02 +02:00
ed3f87da29 If one more build fails because of a missing namespace imma mc-fucking lose it... 2022-06-17 18:12:58 +02:00
4f601405a1 More dumb linux bugfix 2022-06-17 18:01:27 +02:00
cb59b04b17 more linux bugfix 2022-06-17 17:53:07 +02:00
c5f30f6d6a bugfix 2022-06-17 17:40:52 +02:00
36adc102ee attemt at a CI build with new DSP 2022-06-17 17:34:23 +02:00
d1318d3a0f even more stuff 2022-06-15 16:08:54 +02:00
343ec6ca1c lots o shit 2022-06-15 16:08:28 +02:00
e4926c236f Fix PMR frequencies + remove broken t.c. band 2022-06-05 14:12:37 +03:00
61614da910 Update some frequencies 2022-04-25 07:30:22 +02:00
5712db9bf8 #1 Add Slovakia Band Plan 2022-04-23 20:25:34 +02:00
79a15ed186 New DSP filters 2022-04-21 20:25:44 +02:00
527d40c95e Update Canada bandplan 2022-04-09 19:37:50 -04:00
6f747dad0d Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-04-07 15:55:42 +02:00
663dd8d887 added autostart feature 2022-04-07 15:55:22 +02:00
2836292d58 marked irc channel as no longer active 2022-04-05 21:54:58 +02:00
09c8346d4b Update Australia Band Plan
Added in Shortwave bands
2022-04-02 17:51:11 +11:00
8dbae4e307 Merge pull request #707 from Ozies/master
Add Australia Band Plan
2022-04-01 16:23:08 +02:00
4c004d16a2 Update CMakeLists.txt 2022-04-01 16:03:43 +02:00
747b6bfbc6 Added FFT hold feature 2022-03-31 20:16:21 +02:00
83da29e80b Beginning of FFT Hold feature 2022-03-31 15:13:42 +02:00
28dccee34b Add Australia Band Plan 2022-03-31 21:19:20 +11:00
54dd3a77db Added menu order locked tooltip for distracted people 2022-03-31 01:13:12 +02:00
8d78eb301c Added option to lock the menu order 2022-03-31 01:03:31 +02:00
03f173a3ac Fixed M17 module on android 2022-03-30 20:15:27 +02:00
b8bc942b84 cmake bugfix 2022-03-30 17:33:47 +02:00
80d244003a Merge pull request #649 from AlexandreRouma/backend_abstraction
Backend abstraction and android support
2022-03-28 22:27:38 +02:00
50416b3319 Added the new patrons 2022-03-28 22:25:26 +02:00
ea59bc5cc0 UI issue fixed 2022-03-27 19:49:31 +02:00
47b54743bd more android fixes 2022-03-24 20:23:07 +01:00
58fcbbc5d2 A few fixes 2022-03-24 16:03:02 +01:00
842b23b2f4 More UI fixes 2022-03-21 21:35:05 +01:00
f58c7dd62f Merge pull request #691 from CamK06/master
Improve Canada bandplan
2022-03-16 21:46:47 +01:00
6958e46875 Improve Canada bandplan 2022-03-15 20:45:14 -04:00
e82e89a87c Fixed minor UI glitch 2022-03-04 09:59:36 +01:00
763807565c More DPI fixes 2022-02-25 17:32:03 +01:00
8a603c5420 bugfix 2022-02-24 22:44:37 +01:00
9999693c0d Added missing includes 2022-02-24 21:24:46 +01:00
81692fa910 More fixes 2022-02-24 21:11:02 +01:00
9bbc50ff3c Fixed issues hopefully 2022-02-24 21:01:51 +01:00
2779516378 Switched to new cleaner argument system 2022-02-24 20:49:53 +01:00
5c138aa4a5 More work on the new param system 2022-02-21 18:10:01 +01:00
7de9f3f497 Merge pull request #661 from dforsi/fix/typos
Fix spelling errors
2022-02-20 16:25:25 +01:00
98f4f560ad Fix spelling errors
Fixed with:
codespell --ignore-words-list=hist,parm,sur --skip=./core/src/imgui,./core/src/json.hpp,./core/libcorrect,./core/src/spdlog,./misc_modules/discord_integration/discord-rpc,./misc_modules/discord_integration/discord-rpc/include/rapidjson,./source_modules/sddc_source/src/libsddc
2022-02-20 16:19:05 +01:00
a87aedabb8 bugfix + part of the new command arg system 2022-02-18 19:21:02 +01:00
f46fa2157b Hopefully fixed MacOS cursor not calling ImGui when moved through software 2022-02-17 23:27:40 +01:00
b5cbafb01d Android cleanup 2022-02-16 17:06:46 +01:00
51c940acd4 Updated to ImGui 1.87 to cleanup UI code 2022-02-14 23:33:52 +01:00
04e54a2d57 Partial unicode support (the proper way), only cyrillic at the moment 2022-02-14 20:35:39 +01:00
6eb8bfc29a Fixed close not working 2022-02-14 19:36:49 +01:00
96da404149 Fixed text 2022-02-13 17:41:29 +01:00
e9cb7fda42 Backend abstraction + added android support + partial high DPI scaling + added missing files 2022-02-13 17:25:43 +01:00
e5f3d1672c Backend abstraction + added android support + partial high DPI scaling 2022-02-13 17:25:23 +01:00
71c95af711 Merge pull request #631 from orryverducci/macos-icon
Add macOS specific icon
2022-02-03 15:04:46 +01:00
94cf720cfd Add macOS specific icon 2022-02-03 12:57:38 +00:00
9969ce018b Backend abstraction 2022-01-29 20:35:08 +01:00
3c19081561 Bugfix in sdr++ server source 2022-01-29 17:42:53 +01:00
3ab669ecda Radio CW demod bugfix + more cleanup 2022-01-29 17:27:38 +01:00
860121dad2 Added CW tone option plus clean up 2022-01-29 16:55:31 +01:00
5e5c575e93 Added SDRplay source to the Raspberry Pi CI 2022-01-29 16:31:59 +01:00
8dcf17bef7 Fixed ever-expanding apply button in sdrplay source 2022-01-29 16:20:45 +01:00
4fcd06eff6 Added missing CI files 2022-01-29 14:51:53 +01:00
dcc0fef235 Added ubuntu jammy CI 2022-01-29 14:47:28 +01:00
260651fb5c Updated prebuilt debs 2022-01-27 19:21:30 +01:00
97346e6621 Merge pull request #616 from AlexandreRouma/zstd_compression
Optional ZSTD compression in SDR++ server
2022-01-27 17:29:32 +01:00
80dcf2d968 Fixed support for older distro (update ffs) 2022-01-26 20:51:59 +01:00
963c5c6581 Added missing deps to CI 2022-01-26 20:27:55 +01:00
a7b0b52da9 Fixed depencies 2022-01-26 20:20:04 +01:00
03f0704dff Switched to ImGui::TextUnfort 2022-01-26 14:50:16 +01:00
e158eabbf4 Merge pull request #615 from hewittc/format-security
Satisfy GCC -Wformat-security with string literals
2022-01-26 13:29:32 +01:00
4c0220a228 compression 2022-01-26 13:23:55 +01:00
aa265ea312 Satisfy GCC -Wformat-security with TextUnformatted 2022-01-25 14:41:09 -07:00
2c3b603b88 Satisfy GCC -Wformat-security with string literals 2022-01-25 14:05:12 -07:00
75da59833a Fixed server bug 2022-01-24 01:02:27 +01:00
fa0967313f Merge pull request #609 from AlexandreRouma/drop_glew
Drop glew
2022-01-23 18:22:27 +01:00
6fba9d7904 removed all trace of glew from the codebase 2022-01-22 20:16:48 +01:00
c1685a441c Fixed typo 2022-01-22 19:22:58 +01:00
9d7c1369ca Fix bad OpenGL bu 2022-01-22 18:13:46 +01:00
6dc97de57b Dropping glew and using ImGui's loader instead 2022-01-22 18:03:14 +01:00
0dc2f5f7c9 Fixed rigctl server 2022-01-22 16:36:48 +01:00
983c4c0f87 Fix for me being stupid (again...) 2022-01-22 16:15:48 +01:00
0ac85e0daf Linux Bugfix 2022-01-22 05:37:19 +01:00
da06fc39db Merge pull request #591 from kistlin/fix/unused_variables_warnings
fix warnings about unused/maybe uninitialized variables
2022-01-22 04:29:00 +01:00
4c1b50a3ac Major bugfix in server 2022-01-22 02:30:08 +01:00
34fc32dcf7 fixed fault config issues 2022-01-21 20:41:39 +01:00
070e27505b Fixed new networking typo 2022-01-21 20:28:31 +01:00
74b9d13360 SDR++ server beta :) 2022-01-21 20:22:13 +01:00
81d23967b6 revert some changes after the review 2022-01-17 06:53:14 +01:00
174158233b fix a lot of warnings about unused variables, or maybe uninitialized variables 2022-01-16 08:28:57 +01:00
1185e4e114 Added persistent config for the rfspace source's hostname and port 2022-01-02 19:56:27 +01:00
290bf5e32a Fixed invalid default config 2022-01-02 19:02:12 +01:00
4db485e209 Enabled the RFspace source by default 2022-01-02 18:34:06 +01:00
a58ae2bd98 Added persistent config for RFspace source 2022-01-02 15:34:55 +01:00
aab9d9e6ad Fixed RFspace source missing from windows build 2022-01-01 21:38:15 +01:00
7cc06b7a7b Fixed RFspace client on MacOS 2022-01-01 05:54:23 +01:00
efecd14281 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2022-01-01 05:27:19 +01:00
33914a7316 More work on RFSpace source 2022-01-01 05:27:10 +01:00
608979a0b7 Merge pull request #567 from visat1/master
Bandplan for austria
2021-12-30 19:46:20 +01:00
3a815d94b9 Merge pull request #1 from visat1/visat1-bandplan-at
Share bandplan for austria
2021-12-30 15:28:38 +01:00
7b77bb3d45 Share bandplan for austria 2021-12-30 15:27:53 +01:00
201a612c19 Only showing filename of modules in loading screen 2021-12-26 17:26:45 +01:00
5a0c1ab5d0 Added new patron 2021-12-26 16:49:50 +01:00
470e748e4a Fixed weird bug where config files spointainously appear everywhere (wtf...) 2021-12-26 02:09:37 +01:00
66269659c5 Lowered the number of threads for the rpi CI 2021-12-26 01:38:58 +01:00
6ded678048 Made sure to clean build 2021-12-26 01:01:15 +01:00
f34614e169 Saving build artifacts from raspberry pi CI 2021-12-26 00:59:25 +01:00
9d3f0f4f7e hopefully the final fix, i'm loosing my patience 2021-12-26 00:32:05 +01:00
25d585a35a Fixing raspberrypi ci 3 2021-12-26 00:30:06 +01:00
34bd09a92e Fixed raspberripi CI again 2021-12-26 00:26:37 +01:00
e5bbd0fdb3 Fixed raspberry pi ci 2021-12-26 00:23:38 +01:00
b141c4b2a5 Start of notch control + added raspberry pi CI 2021-12-26 00:22:16 +01:00
5a19829068 Removed ubuntu groovy from the CI 2021-12-25 16:51:39 +01:00
51f84566c4 Radio Bugfix 2021-12-25 02:17:21 +01:00
db8f736d58 fixes 2021-12-24 21:51:18 +01:00
07294034f6 Added preset system for FM IF NR 2021-12-24 21:42:04 +01:00
ad2ddc6ad3 Rolled back PFD 2021-12-19 22:18:35 +01:00
b736f83993 Fixed CI 2021-12-19 22:13:58 +01:00
d893eaae32 Formatted the entire codebase and added a CI check for formatting 2021-12-19 22:11:53 +01:00
ea587db0cb Formatted the entire codebase and added a CI check for formatting 2021-12-19 22:11:44 +01:00
8644957881 Fixed clang format file 2021-12-19 21:52:21 +01:00
cc2d73202e Added clang format files for future linting 2021-12-19 05:37:23 +01:00
fe0b63a275 Removed option 2021-12-19 02:35:40 +01:00
ef01205fb6 Fixed missing argument 2021-12-19 02:33:55 +01:00
c1cb3d5a1c bugfix 2021-12-18 21:48:03 +01:00
f1a231b791 Added new deemphasis mode + new image widget 2021-12-18 21:23:23 +01:00
5483268f8f Added error dialog to module manager and fixed typo 2021-12-16 16:49:19 +01:00
6c7e952be3 Further APT tuning 2021-12-13 18:12:17 +01:00
20b44403b2 Added missing file 2021-12-13 03:27:26 +01:00
964fd467f8 Added new dialog helpers and added Are You Sure dialogs in a few places 2021-12-13 03:01:06 +01:00
b4924a8fae CPU performance improvements for IF noise reduction 2021-12-12 21:55:00 +01:00
cee0f75870 bugfix + improved NFM IF noise reduction (less distortion) 2021-12-12 20:06:21 +01:00
15010cff01 Added a noise blanker for SSB and DSB 2021-12-10 22:25:22 +01:00
241632288e Fixed IF reduction not working with multivfo + Added beginning of IF notch code 2021-12-09 19:43:57 +01:00
f8ff67c5b0 beginning of notch filter implementation 2021-12-08 11:59:04 +01:00
c1942ac51d Fixed wrong default deemphasis mode on WFM 2021-12-08 04:15:05 +01:00
91370993a2 Further NR tuning 2021-12-08 03:07:52 +01:00
2a5671878f new FM IF noise reduction + bugfix 2021-12-08 02:10:41 +01:00
1594051a5d Switch old radio module to new radio module 2021-12-07 23:56:11 +01:00
355a6352da New radio logic system 2021-12-07 02:13:23 +01:00
7208028c01 Fixed VFO bandwidth not adjustable after disabling new radio module 2021-12-05 19:28:41 +01:00
cdc060aa2a Fixes to the new radio module 2021-12-04 19:59:30 +01:00
31b2cdd284 fixed macos missing new radio module 2021-12-04 19:10:31 +01:00
636bb214e8 Fixed windows CI 2021-12-04 18:09:43 +01:00
8accb531b0 New radio module 2 2021-12-04 17:46:55 +01:00
fe285c71ff New radio module 2021-12-04 17:46:48 +01:00
62d2dfafd7 More work on the new radio 2021-12-04 04:49:51 +01:00
2748c13142 new radio code + fixes 2021-12-03 19:46:09 +01:00
2daaf00cb3 Disabled raspios CI + added start of noise reduction code 2021-11-28 03:17:08 +01:00
b148fc8a28 Testing fixes for raspberry pi CI 2 2021-11-24 20:49:34 +01:00
407efef935 Testing fixes for raspberry pi CI 2021-11-24 20:46:45 +01:00
8c35639767 fixed raspberry pi CI 2 2021-11-24 20:09:54 +01:00
5e89c52007 fixed raspberry pi CI 2021-11-24 20:07:02 +01:00
2261b0fc2b Added raspberry pi CI 2021-11-24 20:02:16 +01:00
e394b64ad3 Added persistent recorder volume 2021-11-23 22:35:09 +01:00
fc7d902c1d Merge pull request #521 from aosync/master
readme.md: add IRC channel
2021-11-23 20:52:55 +01:00
2f5a8c38a1 Update readme.md 2021-11-23 20:52:49 +01:00
f3e987fb7e Fixed SDRplay AGC and added hardware donators to the credits 2021-11-23 17:05:40 +01:00
abd718b024 readme.md: add IRC channel
readme.md: add link to libera.chat
2021-11-22 22:17:23 +01:00
28d056b776 Update CMakeLists.txt 2021-11-18 22:26:25 +01:00
8c631f40c7 minor cleanup 2021-11-18 14:40:59 +01:00
a84d525252 another fix 2021-11-18 13:52:06 +01:00
4ee677154f Fixed bad libraries being included 2021-11-18 12:06:43 +01:00
098b78dcd1 Fixed libiio and libad9361 missing from MacOS bundle 2021-11-18 11:46:53 +01:00
46a9ecee72 Refactored bundle script 2021-11-18 11:27:47 +01:00
c320a486b3 MacOS CI fix again 2021-11-18 10:58:48 +01:00
21b405d2bb Added LimeSDR support on MacOS 2021-11-18 10:03:45 +01:00
9c2cb26376 Final plutosdr macos fix 2021-11-18 09:38:56 +01:00
3ca7dc87e7 MacOS CI fix 2021-11-18 08:56:06 +01:00
916c95a75e Enabled plutosdr source in MacOS CI 2021-11-18 08:37:55 +01:00
dde95019ea CI bugfix and refactoring 2021-11-17 15:37:21 -06:00
170a48f83f Cleaned up the MacOS tools and enabled the plutosdr_source in the MacOS CI 2021-11-17 15:04:20 -06:00
a08f5c35af Enabled SDRplay source in MacOS CI 2021-11-17 12:27:11 -06:00
1fe799b445 Fixed signing on MacOS 2021-11-17 11:16:20 -06:00
7474241e01 Added missing file 2021-11-17 10:22:27 -06:00
7faaa21ac1 Switched tonew dotapp buildscript 2021-11-17 10:03:59 -06:00
a6e6c93a50 Final MacOS .app fix 2021-11-16 14:18:33 -06:00
10733f7a5d Added pip command and fixed waterfall bug 2021-11-16 13:23:52 -06:00
7f6c555310 More fixes 2021-11-16 13:08:20 -06:00
c659c4b007 FSwitched to self built volk for Mac CI 2021-11-16 12:55:03 -06:00
66dcb5eb79 Added check for rpath in MacOS dotapp script 2021-11-16 12:19:50 -06:00
6f8fc86236 Disabled option wrongly enabled by default 2021-11-16 05:29:05 +01:00
1bbb1eea0e CI Fix 2021-11-16 03:55:49 +01:00
b81d0c47cf Final work on MacOS compatibility 2021-11-15 20:33:09 -06:00
49cf6944f0 Added missing file 2021-11-15 18:26:33 +01:00
ca657c8ca8 More MacOS work 2021-11-15 18:25:32 +01:00
2620d688ec More MacOS work 6 2021-11-15 18:04:57 +01:00
799e8c7e02 More MacOS work 5 2021-11-15 18:01:08 +01:00
f5b07f6a60 More MacOS work 4 2021-11-15 17:59:52 +01:00
599a53b49b More MacOS work 3 2021-11-15 17:56:18 +01:00
0e5384b8ad More MacOS work 2 2021-11-15 17:55:33 +01:00
715129566d More MacOS work 2021-11-15 17:53:32 +01:00
106e0ada80 Added more MacOS tools 2021-11-15 17:28:12 +01:00
7ad616d62b finished set_library_path for MacOS .app build 2021-11-15 17:20:06 +01:00
40e2564ef9 More work on MacOS and the scheduler 2021-11-15 17:15:59 +01:00
0ab4d16f9d Beginning of scheduler code 2021-11-14 18:30:58 +01:00
d20b41401f Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2021-11-11 17:11:48 +01:00
cca18f3a70 More work on scanner 2021-11-11 17:09:37 +01:00
07a4ff3e9f Incremented version 2021-11-09 17:38:27 +01:00
a414b75ef4 Fixed windows CI 2021-11-09 13:55:52 +01:00
12d16fe9ee Work on RFspace support 2 2021-11-09 00:00:43 +01:00
07419275ff Work on RFspace support 2021-11-09 00:00:13 +01:00
927115b50b Added start/stop commands to the rigctl server 2021-11-05 19:42:49 +01:00
a938324886 Saved fullscreen state in the config 2021-11-05 18:57:50 +01:00
894700cbab Fixed Impish CI 2 2021-11-05 16:15:08 +01:00
6ea4f5fd68 Fixed Impish CI 2021-11-05 15:51:46 +01:00
ca185bb416 Added Ubuntu Impish CI 2021-11-05 13:55:00 +01:00
a3dc20b384 Merge pull request #506 from cropinghigh/patch-2
Update Russian Bandplan
2021-11-05 12:19:22 +01:00
fd643c37f5 Update Russian Bandplan
fix overlaps, bring OIRT back, add LTE-450, add train communications frequencies, add L to X satellite bands
2021-11-05 04:36:27 +03:00
c784630345 Fixed the default fft size 2021-11-03 18:12:43 +01:00
e6377d4f3d Fixed waterfall recursive lock bug 2021-11-03 16:30:32 +01:00
d5becc5fc2 Merge pull request #487 from DJRaov/master
Finish the Russian bandplan
2021-10-31 17:51:36 +01:00
9d44f91f45 Finish the Russian bandplan
took me long enough i know
2021-10-31 19:02:19 +05:00
c7db2d2b6a Merge pull request #479 from sikmir/cmake
Fixed sdrpp.desktop installation path
2021-10-31 01:39:34 +02:00
c8bdd2e52b Removed install target from included libcorrect 2021-10-29 17:58:15 +02:00
2c7c93b64c Fixed sdrpp.desktop installation path 2021-10-29 03:17:53 +03:00
312c80b355 Fixes for Archlinux 2021-10-28 10:05:31 +02:00
a11b74a595 Added new patron 2021-10-26 16:44:59 +02:00
0f5398b064 Added new bandplan 2021-10-25 18:24:33 +02:00
606 changed files with 39700 additions and 51938 deletions

131
.clang-format Normal file
View File

@ -0,0 +1,131 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 0
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.'
Priority: 2
SortPriority: 2
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
ReflowComments: true
SortIncludes: false
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
...

41
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,41 @@
---
name: Bug report
about: Report crashes or unexpected behavior
title: ''
labels: bug
assignees: ''
---
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
**Hardware**
- CPU:
- RAM:
- GPU:
- SDR: (Remote or local? If remote, what protocol?)
**Software**
- Operating System: Name + Exact version (eg. Windows 10 x64, Ubuntu 22.04, MacOS 10.15)
- SDR++: Version + Build date (available either in the window title or in the credits menu which you can access by clicking on the SDR++ icon in the top right corner of the software).
**Bug Description**
A clear description of the bug.
**Steps To Reproduce**
1. ...
2. ...
3. ...
**Only If SDR++ fails to lauch or the SDR fails to start:**
Run SDR++ from a command line window with special parameters:
* On Windows, open a terminal and `cd` to SDR++'s directory and run `.\sdrpp.exe -c` (if running SDR++ version 1.0.4 or older, use `-s` instead, though you should probably update SDR++ instead...)
* On Linux: Open a terminal and run `sdrpp -c`
* On MacOS: Open a terminal and run `/path/to/the/SDR++.app/Contents/MacOS/sdrpp -c`
Then, post the **entire** logs from start to after the issue. **DOT NOT truncate to where you *think* the error is...**
**Screenshots**
Add any screenshot that is relevant to the bug (GUI error messages, strange behavior, graphics glitch, etc...).
**Additional info**
Add any other relevant information.

View File

@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Feature description**

View File

@ -1,17 +1,24 @@
name: Build Binaries
on: [push, pull_request]
on:
push:
branches-ignore:
- nightly
pull_request:
branches-ignore:
- nightly
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -36,7 +43,7 @@ jobs:
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
- name: Download codec2
run: git clone https://github.com/drowe67/codec2
run: git clone https://github.com/AlexandreRouma/codec2
- name: Prepare MinGW
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
@ -51,7 +58,7 @@ jobs:
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
- name: Install vcpkg dependencies
run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows portaudio:x64-windows
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows
- name: Install rtaudio
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
@ -69,16 +76,16 @@ jobs:
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos:
runs-on: macos-latest
runs-on: macos-11
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -86,12 +93,30 @@ jobs:
- name: Update brew repositories
run: brew update
- name: Fix stuff
run: rm -f /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3* /usr/local/bin/python3-config* && brew reinstall gettext
- name: Install dependencies
run: brew install fftw glew glfw volk airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 zstd && pip3 install mako
- name: Install volk
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install SDRplay API
run: wget https://www.sdrplay.com/software/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.pkg -target /
- name: Install libiio
run: git clone https://github.com/analogdevicesinc/libiio && cd libiio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -99,19 +124,19 @@ jobs:
- name: Create Archive
working-directory: ${{runner.workspace}}
run: sh $GITHUB_WORKSPACE/make_macos_package.sh ${{runner.workspace}}/build
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
- name: Save Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_macos_amd64
path: ${{runner.workspace}}/sdrpp_macos_amd64.pkg
name: sdrpp_macos_intel
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
build_debian_buster:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
@ -124,7 +149,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -133,7 +158,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
@ -146,7 +171,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -155,7 +180,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
@ -168,38 +193,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_bionic:
runs-on: ubuntu-latest
# build_ubuntu_bionic:
# runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# steps:
# - uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
# - name: Create Docker Image
# run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
# - name: 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: Recover Deb Archive
# working-directory: ${{runner.workspace}}
# run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_ubuntu_bionic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
# - name: Save Deb Archive
# uses: actions/upload-artifact@v3
# with:
# name: sdrpp_ubuntu_bionic_amd64
# path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
@ -212,19 +237,19 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_groovy:
build_ubuntu_jammy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_groovy && docker build . --tag sdrpp_build
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
@ -234,67 +259,120 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: sdrpp_ubuntu_groovy_amd64
name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_hirsute:
build_raspios_bullseye_armhf:
runs-on: ARM
steps:
- uses: actions/checkout@v3
- name: Create Build Environment
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON
- name: Build
working-directory: ${{runner.workspace}}/build
run: make VERBOSE=1 -j3
- name: Create Dev Archive
working-directory: ${{runner.workspace}}
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
- name: Save Deb Archive
uses: actions/upload-artifact@v3
with:
name: sdrpp_raspios_bullseye_armhf
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
build_android:
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
- uses: actions/checkout@v3
- 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
- name: Fetch container
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
run: git clone https://github.com/AlexandreRouma/android-sdr-kit
- name: Build container
working-directory: ${{runner.workspace}}/android-sdr-kit
run: docker build --progress=plain -t android-sdr-kit .
- name: Save Deb Archive
uses: actions/upload-artifact@v2
- name: Build
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus android-sdr-kit /bin/bash -l -c "cd /root/SDRPlusPlus/android && gradle --info assembleDebug"
- name: Recover APK
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
- name: Save APK
uses: actions/upload-artifact@v3
with:
name: sdrpp_ubuntu_hirsute_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
name: sdrpp_android
path: ${{runner.workspace}}/sdrpp.apk
create_full_archive:
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_bionic', 'build_ubuntu_focal', 'build_ubuntu_groovy', 'build_ubuntu_hirsute']
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest
steps:
- name: Download All Builds
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
- name: Create Archive
run: >
mkdir sdrpp_all &&
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
mv sdrpp_macos_amd64/sdrpp_macos_amd64.pkg sdrpp_all/ &&
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
mv sdrpp_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_bionic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_bionic_amd64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_groovy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_groovy_amd64.deb &&
mv sdrpp_ubuntu_hirsute_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_hirsute_amd64.deb
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: sdrpp_all
path: sdrpp_all/
update_nightly_release:
needs: [create_full_archive]
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
steps:
- name: Download All Builds
uses: actions/download-artifact@v3
- name: Update Nightly
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
check_spelling:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install codespell
run: sudo apt update -y && sudo apt install -y codespell
- name: Running codespell
run: cd $GITHUB_WORKSPACE && codespell -q 2 || true
check_formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run check_clang_format
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true

6
.gitignore vendored
View File

@ -9,7 +9,11 @@ build/
*.wav
.DS_Store
root_dev/
root_dev_srv/
Folder.DotSettings.user
CMakeSettings.json
poggers_decoder
m17_decoder/libcorrect
m17_decoder/libcorrect
SDR++.app
android/deps
android/app/assets

View File

@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.13)
project(sdrpp)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_INSTALL_PREFIX "/usr/local")
else()
set(CMAKE_INSTALL_PREFIX "/usr")
endif()
# Backends
option(OPT_BACKEND_GLFW "Use the GLFW backend" ON)
option(OPT_BACKEND_ANDROID "Use the Android backend" OFF)
# Compatibility Options
option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on systems that don't have it yet" OFF)
@ -13,36 +11,99 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
# Sources
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
option(OPT_BUILD_SDDC_SOURCE "Build SDDC Source Module (Dependencies: libusb-1.0)" OFF)
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON)
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON)
option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF)
option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
# Sinks
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF)
# Decoders
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (no dependencies required)" OFF)
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" ON)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
option(OPT_BUILD_SCANNER "Frequency scanner" ON)
option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
# Other options
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
# Module cmake path
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
# Root source folder
set(SDRPP_CORE_ROOT "${CMAKE_SOURCE_DIR}/core/src/")
# Compiler flags
if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
# Debug Flags
if (MSVC)
set(SDRPP_COMPILER_FLAGS /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(SDRPP_COMPILER_FLAGS -g -Og -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
set(SDRPP_COMPILER_FLAGS -g -Og -std=c++17)
endif ()
else()
# Normal Flags
if (MSVC)
set(SDRPP_COMPILER_FLAGS /O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(SDRPP_COMPILER_FLAGS -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
set(SDRPP_COMPILER_FLAGS -O3 -std=c++17)
endif ()
endif()
set(SDRPP_MODULE_COMPILER_FLAGS ${SDRPP_COMPILER_FLAGS})
# Set a default install prefix
if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "..." FORCE)
else()
set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "..." FORCE)
endif()
endif()
# Configure toolchain for android
if (ANDROID)
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate"
)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX14_EXTENSION_COMPILE_OPTION "-std=c++17")
endif (ANDROID)
# Core of SDR++
add_subdirectory("core")
@ -56,6 +117,10 @@ if (OPT_BUILD_AIRSPYHF_SOURCE)
add_subdirectory("source_modules/airspyhf_source")
endif (OPT_BUILD_AIRSPYHF_SOURCE)
if (OPT_BUILD_AUDIO_SOURCE)
add_subdirectory("source_modules/audio_source")
endif (OPT_BUILD_AUDIO_SOURCE)
if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("source_modules/bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE)
@ -68,13 +133,21 @@ if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("source_modules/hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)
if (OPT_BUILD_SDDC_SOURCE)
add_subdirectory("source_modules/sddc_source")
endif (OPT_BUILD_SDDC_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
if (OPT_BUILD_RFSPACE_SOURCE)
add_subdirectory("source_modules/rfspace_source")
endif (OPT_BUILD_RFSPACE_SOURCE)
if (OPT_BUILD_RTL_SDR_SOURCE)
add_subdirectory("source_modules/rtl_sdr_source")
@ -92,6 +165,14 @@ if (OPT_BUILD_SOAPY_SOURCE)
add_subdirectory("source_modules/soapy_source")
endif (OPT_BUILD_SOAPY_SOURCE)
if (OPT_BUILD_SPECTRAN_SOURCE)
add_subdirectory("source_modules/spectran_source")
endif (OPT_BUILD_SPECTRAN_SOURCE)
if (OPT_BUILD_SPECTRAN_HTTP_SOURCE)
add_subdirectory("source_modules/spectran_http_source")
endif (OPT_BUILD_SPECTRAN_HTTP_SOURCE)
if (OPT_BUILD_SPYSERVER_SOURCE)
add_subdirectory("source_modules/spyserver_source")
endif (OPT_BUILD_SPYSERVER_SOURCE)
@ -100,8 +181,16 @@ if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("source_modules/plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_USRP_SOURCE)
add_subdirectory("source_modules/usrp_source")
endif (OPT_BUILD_USRP_SOURCE)
# Sink modules
if (OPT_BUILD_ANDROID_AUDIO_SINK)
add_subdirectory("sink_modules/android_audio_sink")
endif (OPT_BUILD_ANDROID_AUDIO_SINK)
if (OPT_BUILD_AUDIO_SINK)
add_subdirectory("sink_modules/audio_sink")
endif (OPT_BUILD_AUDIO_SINK)
@ -120,10 +209,18 @@ endif (OPT_BUILD_NEW_PORTAUDIO_SINK)
# Decoders
if (OPT_BUILD_ATV_DECODER)
add_subdirectory("decoder_modules/atv_decoder")
endif (OPT_BUILD_ATV_DECODER)
if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("decoder_modules/falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER)
if (OPT_BUILD_KG_SSTV_DECODER)
add_subdirectory("decoder_modules/kg_sstv_decoder")
endif (OPT_BUILD_KG_SSTV_DECODER)
if (OPT_BUILD_M17_DECODER)
add_subdirectory("decoder_modules/m17_decoder")
endif (OPT_BUILD_M17_DECODER)
@ -154,22 +251,27 @@ if (OPT_BUILD_RECORDER)
add_subdirectory("misc_modules/recorder")
endif (OPT_BUILD_RECORDER)
if (OPT_BUILD_RIGCTL_CLIENT)
add_subdirectory("misc_modules/rigctl_client")
endif (OPT_BUILD_RIGCTL_CLIENT)
if (OPT_BUILD_RIGCTL_SERVER)
add_subdirectory("misc_modules/rigctl_server")
endif (OPT_BUILD_RIGCTL_SERVER)
if (OPT_BUILD_SCANNER)
add_subdirectory("misc_modules/scanner")
endif (OPT_BUILD_SCANNER)
if (OPT_BUILD_SCHEDULER)
add_subdirectory("misc_modules/scheduler")
endif (OPT_BUILD_SCHEDULER)
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Compiler arguments for each platform
if (MSVC)
target_compile_options(sdrpp PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(sdrpp PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
target_compile_options(sdrpp PRIVATE -O3 -std=c++17)
endif ()
# Compiler arguments
target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
# Copy dynamic libs over
if (MSVC)
@ -195,7 +297,10 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
# Create module cmake file
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
# Install directives
install(TARGETS sdrpp DESTINATION bin)
@ -207,9 +312,9 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/themes DESTINATION share/sdrpp)
configure_file(${CMAKE_SOURCE_DIR}/sdrpp.desktop ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop @ONLY)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION /usr/share/applications)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION share/applications)
endif ()
# Create uninstall target
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)

12
android/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.cxx
.externalNativeBuild
build/
*.iml
.idea
.gradle
local.properties
# Android Studio puts a Gradle wrapper here, that we don't want:
gradle/
gradlew*

76
android/app/build.gradle Normal file
View File

@ -0,0 +1,76 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 28
buildToolsVersion "30.0.3"
ndkVersion "25.1.8937393"
defaultConfig {
applicationId "org.sdrpp.sdrpp"
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.1.0"
externalNativeBuild {
cmake {
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF"
}
}
}
signingConfigs {
debug {
storeFile file("debug.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
}
debug {
signingConfig signingConfigs.debug
}
}
externalNativeBuild {
cmake {
version "3.18.1"
path "../../CMakeLists.txt"
}
}
sourceSets {
main {
assets.srcDirs += ['assets']
}
}
}
task deleteTempAssets (type: Delete) {
delete 'assets'
}
task copyResources(type: Copy) {
description = 'Copy resources...'
from '../../root/'
into 'assets/'
include('**/*')
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
}
copyResources.dependsOn deleteTempAssets
preBuild.dependsOn copyResources

BIN
android/app/debug.keystore Normal file

Binary file not shown.

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sdrpp.sdrpp">
<application
android:label="SDR++"
android:allowBackup="false"
android:fullBackupContent="false"
android:hasCode="true">
<activity
android:name="org.sdrpp.sdrpp.MainActivity"
android:icon="@mipmap/ic_launcher"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:configChanges="orientation|keyboardHidden|screenSize">
<meta-data android:name="android.app.lib_name" android:value="sdrpp_core" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,32 @@
package org.sdrpp.sdrpp;
import android.app.NativeActivity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.usb.*;
import android.Manifest;
import android.os.Bundle;
import android.view.View;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.util.Log;
import android.content.res.AssetManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.PermissionChecker;
import java.util.concurrent.LinkedBlockingQueue;
import java.io.*;
class DeviceManager {
public fun init() {
}
}

View File

@ -0,0 +1,192 @@
package org.sdrpp.sdrpp;
import android.app.NativeActivity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.hardware.usb.*;
import android.Manifest;
import android.os.Bundle;
import android.view.View;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.util.Log;
import android.content.res.AssetManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.PermissionChecker;
import java.util.concurrent.LinkedBlockingQueue;
import java.io.*;
private const val ACTION_USB_PERMISSION = "org.sdrpp.sdrpp.USB_PERMISSION";
private val usbReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ACTION_USB_PERMISSION == intent.action) {
synchronized(this) {
var _this = context as MainActivity;
_this.SDR_device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
_this.SDR_conn = _this.usbManager!!.openDevice(_this.SDR_device);
// Save SDR info
_this.SDR_VID = _this.SDR_device!!.getVendorId();
_this.SDR_PID = _this.SDR_device!!.getProductId()
_this.SDR_FD = _this.SDR_conn!!.getFileDescriptor();
}
// Whatever the hell this does
context.unregisterReceiver(this);
// Hide again the system bars
_this.hideSystemBars();
}
}
}
}
class MainActivity : NativeActivity() {
private val TAG : String = "SDR++";
public var usbManager : UsbManager? = null;
public var SDR_device : UsbDevice? = null;
public var SDR_conn : UsbDeviceConnection? = null;
public var SDR_VID : Int = -1;
public var SDR_PID : Int = -1;
public var SDR_FD : Int = -1;
fun checkAndAsk(permission: String) {
if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission), 1);
}
}
public fun hideSystemBars() {
val decorView = getWindow().getDecorView();
val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
}
public override fun onCreate(savedInstanceState: Bundle?) {
// Hide bars
hideSystemBars();
// Ask for required permissions, without these the app cannot run.
checkAndAsk(Manifest.permission.WRITE_EXTERNAL_STORAGE);
checkAndAsk(Manifest.permission.READ_EXTERNAL_STORAGE);
// TODO: Have the main code wait until these two permissions are available
// Register events
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager;
val permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)
// Get permission for all USB devices
val devList = usbManager!!.getDeviceList();
for ((name, dev) in devList) {
usbManager!!.requestPermission(dev, permissionIntent);
}
// Ask for internet permission
checkAndAsk(Manifest.permission.INTERNET);
super.onCreate(savedInstanceState)
}
public override fun onResume() {
// Hide bars again
hideSystemBars();
super.onResume();
}
fun showSoftInput() {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
inputMethodManager.showSoftInput(window.decorView, 0);
}
fun hideSoftInput() {
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
inputMethodManager.hideSoftInputFromWindow(window.decorView.windowToken, 0);
hideSystemBars();
}
// Queue for the Unicode characters to be polled from native code (via pollUnicodeChar())
private var unicodeCharacterQueue: LinkedBlockingQueue<Int> = LinkedBlockingQueue()
// We assume dispatchKeyEvent() of the NativeActivity is actually called for every
// KeyEvent and not consumed by any View before it reaches here
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
unicodeCharacterQueue.offer(event.getUnicodeChar(event.metaState))
}
return super.dispatchKeyEvent(event)
}
fun pollUnicodeChar(): Int {
return unicodeCharacterQueue.poll() ?: 0
}
public fun createIfDoesntExist(path: String) {
// This is a directory, create it in the filesystem
var folder = File(path);
var success = true;
if (!folder.exists()) {
success = folder.mkdirs();
}
if (!success) {
Log.e(TAG, "Could not create folder with path " + path);
}
}
public fun extractDir(aman: AssetManager, local: String, rsrc: String): Int {
val flist = aman.list(rsrc);
var ecount = 0;
for (fp in flist) {
val lpath = local + "/" + fp;
val rpath = rsrc + "/" + fp;
Log.w(TAG, "Extracting '" + rpath + "' to '" + lpath + "'");
// Create local path if non-existent
createIfDoesntExist(local);
// Create if directory
val ext = extractDir(aman, lpath, rpath);
// Extract if file
if (ext == 0) {
// This is a file, extract it
val _os = FileOutputStream(lpath);
val _is = aman.open(rpath);
val ilen = _is.available();
var fbuf = ByteArray(ilen);
_is.read(fbuf, 0, ilen);
_os.write(fbuf);
_os.close();
_is.close();
}
ecount++;
}
return ecount;
}
public fun getAppDir(): String {
val fdir = getFilesDir().getAbsolutePath();
// Extract all resources to the app directory
val aman = getAssets();
extractDir(aman, fdir + "/res", "res");
createIfDoesntExist(fdir + "/modules");
return fdir;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

22
android/build.gradle Normal file
View File

@ -0,0 +1,22 @@
buildscript {
ext.kotlin_version = '1.4.31'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1 @@
android.useAndroidX=true

1
android/settings.gradle Normal file
View File

@ -0,0 +1 @@
include ':app'

20
check_clang_format.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/bash
echo Searching directories...
CODE_FILES=$(find . -iregex '.*\.\(h\|hpp\|c\|cpp\)$')
while read -r CPP_FILE_PATH; do
# Skip unwanted files
if [[ "$CPP_FILE_PATH" == "./.old"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./build"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./core/libcorrect"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./core/std_replacement"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./core/src/imgui"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./misc_modules/discord_integration/discord-rpc"* ]]; then continue; fi
if [[ "$CPP_FILE_PATH" == "./source_modules/sddc_source/src/libsddc"* ]]; then continue; fi
if [ "$CPP_FILE_PATH" = ./core/src/json.hpp ]; then continue; fi
if [ "$CPP_FILE_PATH" = ./core/src/gui/file_dialogs.h ]; then continue; fi
echo Checking $CPP_FILE_PATH
clang-format --style=file -i -n -Werror $CPP_FILE_PATH
done <<< "$CODE_FILES"

View File

@ -56,7 +56,7 @@ If the module meets the code quality requirements, it may be added to the offici
# JSON Formatting
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSOn files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
@ -75,13 +75,13 @@ Please follow this guide to properly format the JSON files for custom radio band
// Bands in this array must be sorted by their starting frequency
{
"name": "Name of the band",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type declared in config.json)",
"start": 148500, //In Hz, must be an integer
"end": 283500 //In Hz, must be an integer
},
{
"name": "Name of the band",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type declared in config.json)",
"start": 526500, //In Hz, must be an integer
"end": 1606500 //In Hz, must be an integer
}
@ -98,7 +98,7 @@ Please follow this guide to properly format the JSON files for custom color maps
"name": "Short name (has to fit in the menu)",
"author": "Name of the original/main creator of the color map",
"map": [
// These are the color codes, in hexadecimal (#RRGGBB) format, for the custom color scales for the waterfall. They must be entered as strings, not integers, with the hastag/pound-symbol proceeding the 6 digit number.
// These are the color codes, in hexadecimal (#RRGGBB) format, for the custom color scales for the waterfall. They must be entered as strings, not integers, with the hashtag/pound-symbol proceeding the 6 digit number.
"#000020",
"#000030",
"#000050",

View File

@ -1,7 +1,13 @@
cmake_minimum_required(VERSION 3.13)
project(sdrpp_core)
add_subdirectory("libcorrect/")
if (USE_INTERNAL_LIBCORRECT)
add_subdirectory("libcorrect/")
endif (USE_INTERNAL_LIBCORRECT)
if (USE_BUNDLE_DEFAULTS)
add_definitions(-DIS_MACOS_BUNDLE)
endif (USE_BUNDLE_DEFAULTS)
# Main code
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
@ -11,18 +17,20 @@ if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif ()
# Configure backend sources
if (OPT_BACKEND_GLFW)
file(GLOB_RECURSE BACKEND_SRC "backends/glfw/*.cpp" "backends/glfw/*.c")
endif (OPT_BACKEND_GLFW)
if (OPT_BACKEND_ANDROID)
file(GLOB_RECURSE BACKEND_SRC "backends/android/*.cpp" "backends/android/*.c")
set(BACKEND_SRC ${BACKEND_SRC} ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
endif (OPT_BACKEND_ANDROID)
# Add code to dyn lib
add_library(sdrpp_core SHARED ${SRC})
add_library(sdrpp_core SHARED ${SRC} ${BACKEND_SRC})
# Set compiler options
if (MSVC)
target_compile_options(sdrpp_core PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(sdrpp_core PRIVATE -O3 -std=c++17)
else ()
target_compile_options(sdrpp_core PRIVATE -O3 -std=c++17)
endif ()
target_compile_options(sdrpp_core PRIVATE ${SDRPP_COMPILER_FLAGS})
# Set the install prefix
target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
@ -31,12 +39,37 @@ target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PRE
target_include_directories(sdrpp_core PUBLIC "src/")
target_include_directories(sdrpp_core PUBLIC "src/imgui")
# Link to linkcorrect
target_include_directories(sdrpp_core PUBLIC "libcorrect/include")
target_link_libraries(sdrpp_core PUBLIC correct_static)
# Configure backend includes and libraries
if (OPT_BACKEND_GLFW)
target_include_directories(sdrpp_core PUBLIC "backends/glfw")
target_include_directories(sdrpp_core PUBLIC "backends/glfw/imgui")
if (MSVC)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC glfw)
else()
find_package(PkgConfig)
pkg_check_modules(GLFW3 REQUIRED glfw3)
target_include_directories(sdrpp_core PUBLIC ${GLFW3_INCLUDE_DIRS})
target_link_directories(sdrpp_core PUBLIC ${GLFW3_LIBRARY_DIRS})
target_link_libraries(sdrpp_core PUBLIC ${GLFW3_LIBRARIES})
endif()
endif (OPT_BACKEND_GLFW)
if (OPT_BACKEND_ANDROID)
target_include_directories(sdrpp_core PUBLIC "backends/android")
target_include_directories(sdrpp_core PUBLIC "backends/android/imgui")
endif (OPT_BACKEND_ANDROID)
# Link to libcorrect
if (USE_INTERNAL_LIBCORRECT)
target_include_directories(sdrpp_core PUBLIC "libcorrect/include")
target_link_libraries(sdrpp_core PUBLIC correct_static)
endif (USE_INTERNAL_LIBCORRECT)
if (OPT_OVERRIDE_STD_FILESYSTEM)
target_include_directories(sdrpp_core PUBLIC "std_replacement")
target_include_directories(sdrpp_core PUBLIC "std_replacement")
endif (OPT_OVERRIDE_STD_FILESYSTEM)
if (MSVC)
@ -49,9 +82,9 @@ if (MSVC)
# Volk
target_link_libraries(sdrpp_core PUBLIC volk)
# Glew
find_package(GLEW REQUIRED)
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
# OpenGL
find_package(OpenGL REQUIRED)
target_link_libraries(sdrpp_core PUBLIC OpenGL::GL)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
@ -62,39 +95,67 @@ if (MSVC)
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
# WinSock2
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32)
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32 iphlpapi)
# ZSTD
find_package(zstd CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC zstd::libzstd_shared)
elseif (ANDROID)
target_include_directories(sdrpp_core PUBLIC
/sdr-kit/${ANDROID_ABI}/include
${ANDROID_NDK}/sources/android/native_app_glue
)
target_link_libraries(sdrpp_core PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcpu_features.a
/sdr-kit/${ANDROID_ABI}/lib/libvolk.so
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so
android
EGL
GLESv3
log
)
else()
find_package(PkgConfig)
find_package(OpenGL REQUIRED)
pkg_check_modules(GLEW REQUIRED glew)
pkg_check_modules(FFTW3 REQUIRED fftw3f)
pkg_check_modules(VOLK REQUIRED volk)
pkg_check_modules(GLFW3 REQUIRED glfw3)
pkg_check_modules(LIBZSTD REQUIRED libzstd)
target_include_directories(sdrpp_core PUBLIC
${GLEW_INCLUDE_DIRS}
${OPENGL_INCLUDE_DIRS}
${FFTW3_INCLUDE_DIRS}
${GLFW3_INCLUDE_DIRS}
${VOLK_INCLUDE_DIRS}
${LIBZSTD_INCLUDE_DIRS}
)
target_link_directories(sdrpp_core PUBLIC
${GLEW_LIBRARY_DIRS}
${OPENGL_LIBRARY_DIRS}
${FFTW3_LIBRARY_DIRS}
${GLFW3_LIBRARY_DIRS}
${VOLK_LIBRARY_DIRS}
${LIBZSTD_LIBRARY_DIRS}
)
target_link_libraries(sdrpp_core PUBLIC
${OPENGL_LIBRARIES}
${GLEW_LIBRARIES}
${FFTW3_LIBRARIES}
${GLFW3_LIBRARIES}
${VOLK_LIBRARIES}
${LIBZSTD_LIBRARIES}
)
if (NOT USE_INTERNAL_LIBCORRECT)
pkg_check_modules(CORRECT REQUIRED libcorrect)
target_include_directories(sdrpp_core PUBLIC ${CORRECT_INCLUDE_DIRS})
target_link_directories(sdrpp_core PUBLIC ${CORRECT_LIBRARY_DIRS})
target_link_libraries(sdrpp_core PUBLIC ${CORRECT_LIBRARIES})
endif (NOT USE_INTERNAL_LIBCORRECT)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
endif ()

View File

@ -0,0 +1,17 @@
#pragma once
#include <vector>
#include <stdint.h>
namespace backend {
struct DevVIDPID {
uint16_t vid;
uint16_t pid;
};
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);
}

View File

@ -0,0 +1,483 @@
#include <backend.h>log
#include "android_backend.h"
#include <core.h>
#include <gui/gui.h>
#include "imgui.h"
#include "imgui_impl_android.h"
#include "imgui_impl_opengl3.h"
#include <android/log.h>
#include <android_native_app_glue.h>
#include <android/asset_manager.h>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <stdint.h>
#include <gui/icons.h>
#include <gui/style.h>
#include <gui/menus/theme.h>
#include <filesystem>
// Credit to the ImGui android OpenGL3 example for a lot of this code!
namespace backend {
struct android_app* app = NULL;
EGLDisplay _EglDisplay = EGL_NO_DISPLAY;
EGLSurface _EglSurface = EGL_NO_SURFACE;
EGLContext _EglContext = EGL_NO_CONTEXT;
bool _Initialized = false;
char _LogTag[] = "SDR++";
bool initialized = false;
bool pauseRendering = false;
bool exited = false;
// Forward declaration
int ShowSoftKeyboardInput();
int PollUnicodeChars();
void doPartialInit() {
std::string root = (std::string)core::args["root"];
backend::init();
style::loadFonts(root + "/res"); // TODO: Don't hardcode, use config
icons::load(root + "/res");
thememenu::applyTheme();
ImGui::GetStyle().ScaleAllSizes(style::uiScale);
gui::mainWindow.setFirstMenuRender();
}
void handleAppCmd(struct android_app* app, int32_t appCmd) {
switch (appCmd) {
case APP_CMD_SAVE_STATE:
flog::warn("APP_CMD_SAVE_STATE");
break;
case APP_CMD_INIT_WINDOW:
flog::warn("APP_CMD_INIT_WINDOW");
if (pauseRendering && !exited) {
doPartialInit();
pauseRendering = false;
}
exited = false;
break;
case APP_CMD_TERM_WINDOW:
flog::warn("APP_CMD_TERM_WINDOW");
pauseRendering = true;
backend::end();
break;
case APP_CMD_GAINED_FOCUS:
flog::warn("APP_CMD_GAINED_FOCUS");
break;
case APP_CMD_LOST_FOCUS:
flog::warn("APP_CMD_LOST_FOCUS");
break;
}
}
int32_t handleInputEvent(struct android_app* app, AInputEvent* inputEvent) {
return ImGui_ImplAndroid_HandleInputEvent(inputEvent);
}
int aquireWindow() {
while (!app->window) {
flog::warn("Waiting on the shitty window thing"); std::this_thread::sleep_for(std::chrono::milliseconds(30));
int out_events;
struct android_poll_source* out_data;
while (ALooper_pollAll(0, NULL, &out_events, (void**)&out_data) >= 0) {
// Process one event
if (out_data != NULL) { out_data->process(app, out_data); }
// Exit the app by returning from within the infinite loop
if (app->destroyRequested != 0) {
return -1;
}
}
}
ANativeWindow_acquire(app->window);
return 0;
}
int init(std::string resDir) {
flog::warn("Backend init");
// Get window
aquireWindow();
// EGL Init
{
_EglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (_EglDisplay == EGL_NO_DISPLAY)
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglGetDisplay(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY");
if (eglInitialize(_EglDisplay, 0, 0) != EGL_TRUE)
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglInitialize() returned with an error");
const EGLint egl_attributes[] = { EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE };
EGLint num_configs = 0;
if (eglChooseConfig(_EglDisplay, egl_attributes, nullptr, 0, &num_configs) != EGL_TRUE)
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglChooseConfig() returned with an error");
if (num_configs == 0)
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglChooseConfig() returned 0 matching config");
// Get the first matching config
EGLConfig egl_config;
eglChooseConfig(_EglDisplay, egl_attributes, &egl_config, 1, &num_configs);
EGLint egl_format;
eglGetConfigAttrib(_EglDisplay, egl_config, EGL_NATIVE_VISUAL_ID, &egl_format);
ANativeWindow_setBuffersGeometry(app->window, 0, 0, egl_format);
const EGLint egl_context_attributes[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };
_EglContext = eglCreateContext(_EglDisplay, egl_config, EGL_NO_CONTEXT, egl_context_attributes);
if (_EglContext == EGL_NO_CONTEXT)
__android_log_print(ANDROID_LOG_ERROR, _LogTag, "%s", "eglCreateContext() returned EGL_NO_CONTEXT");
_EglSurface = eglCreateWindowSurface(_EglDisplay, egl_config, app->window, NULL);
eglMakeCurrent(_EglDisplay, _EglSurface, _EglSurface, _EglContext);
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
// Disable loading/saving of .ini file from disk.
// FIXME: Consider using LoadIniSettingsFromMemory() / SaveIniSettingsToMemory() to save in appropriate location for Android.
io.IniFilename = NULL;
// Setup Platform/Renderer backends
ImGui_ImplAndroid_Init(app->window);
ImGui_ImplOpenGL3_Init("#version 300 es");
return 0;
}
void beginFrame() {
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplAndroid_NewFrame();
ImGui::NewFrame();
}
void render(bool vsync) {
// Rendering
ImGui::Render();
auto dSize = ImGui::GetIO().DisplaySize;
glViewport(0, 0, dSize.x, dSize.y);
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
eglSwapBuffers(_EglDisplay, _EglSurface);
}
// No screen pos to detect
void getMouseScreenPos(double& x, double& y) { x = 0; y = 0; }
void setMouseScreenPos(double x, double y) {}
int renderLoop() {
while (true) {
int out_events;
struct android_poll_source* out_data;
while (ALooper_pollAll(0, NULL, &out_events, (void**)&out_data) >= 0) {
// Process one event
if (out_data != NULL) { out_data->process(app, out_data); }
// Exit the app by returning from within the infinite loop
if (app->destroyRequested != 0) {
flog::warn("ASKED TO EXIT");
exited = true;
// Stop SDR
gui::mainWindow.setPlayState(false);
return 0;
}
}
if (_EglDisplay == EGL_NO_DISPLAY) { continue; }
if (!pauseRendering) {
// Initiate a new frame
ImGuiIO& io = ImGui::GetIO();
auto dsize = io.DisplaySize;
// Poll Unicode characters via JNI
// FIXME: do not call this every frame because of JNI overhead
PollUnicodeChars();
// Open on-screen (soft) input if requested by Dear ImGui
static bool WantTextInputLast = false;
if (io.WantTextInput && !WantTextInputLast)
ShowSoftKeyboardInput();
WantTextInputLast = io.WantTextInput;
// Render
beginFrame();
if (dsize.x > 0 && dsize.y > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(dsize.x, dsize.y));
gui::mainWindow.draw();
}
render();
}
else {
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
}
return 0;
}
int end() {
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplAndroid_Shutdown();
ImGui::DestroyContext();
// Destroy all
if (_EglDisplay != EGL_NO_DISPLAY) {
eglMakeCurrent(_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (_EglContext != EGL_NO_CONTEXT) { eglDestroyContext(_EglDisplay, _EglContext); }
if (_EglSurface != EGL_NO_SURFACE) { eglDestroySurface(_EglDisplay, _EglSurface); }
eglTerminate(_EglDisplay);
}
_EglDisplay = EGL_NO_DISPLAY;
_EglContext = EGL_NO_CONTEXT;
_EglSurface = EGL_NO_SURFACE;
if (app->window) { ANativeWindow_release(app->window); }
return 0;
}
int ShowSoftKeyboardInput() {
JavaVM* java_vm = app->activity->vm;
JNIEnv* java_env = NULL;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
return -1;
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
if (jni_return != JNI_OK)
return -2;
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
if (native_activity_clazz == NULL)
return -3;
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "showSoftInput", "()V");
if (method_id == NULL)
return -4;
java_env->CallVoidMethod(app->activity->clazz, method_id);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
return -5;
return 0;
}
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids) {
JavaVM* java_vm = app->activity->vm;
JNIEnv* java_env = NULL;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
return -1;
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
if (jni_return != JNI_OK)
return -1;
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
if (native_activity_clazz == NULL)
return -1;
jfieldID fd_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_FD", "I");
jfieldID vid_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_VID", "I");
jfieldID pid_field_id = java_env->GetFieldID(native_activity_clazz, "SDR_PID", "I");
if (!vid_field_id || !vid_field_id || !pid_field_id)
return -1;
int fd = java_env->GetIntField(app->activity->clazz, fd_field_id);
vid = java_env->GetIntField(app->activity->clazz, vid_field_id);
pid = java_env->GetIntField(app->activity->clazz, pid_field_id);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
return -1;
// If no vid/pid was given, just return successfully
if (allowedVidPids.empty()) {
return fd;
}
// Otherwise, check that the vid/pid combo is allowed
for (auto const& vp : allowedVidPids) {
if (vp.vid != vid || vp.pid != pid) { continue; }
return fd;
}
return -1;
}
// Unfortunately, the native KeyEvent implementation has no getUnicodeChar() function.
// Therefore, we implement the processing of KeyEvents in MainActivity.kt and poll
// the resulting Unicode characters here via JNI and send them to Dear ImGui.
int PollUnicodeChars() {
JavaVM* java_vm = app->activity->vm;
JNIEnv* java_env = NULL;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
return -1;
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
if (jni_return != JNI_OK)
return -2;
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
if (native_activity_clazz == NULL)
return -3;
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "pollUnicodeChar", "()I");
if (method_id == NULL)
return -4;
// Send the actual characters to Dear ImGui
ImGuiIO& io = ImGui::GetIO();
jint unicode_character;
while ((unicode_character = java_env->CallIntMethod(app->activity->clazz, method_id)) != 0)
io.AddInputCharacter(unicode_character);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
return -5;
return 0;
}
std::string getAppFilesDir() {
JavaVM* java_vm = app->activity->vm;
JNIEnv* java_env = NULL;
jint jni_return = java_vm->GetEnv((void**)&java_env, JNI_VERSION_1_6);
if (jni_return == JNI_ERR)
throw std::runtime_error("Could not get JNI environment");
jni_return = java_vm->AttachCurrentThread(&java_env, NULL);
if (jni_return != JNI_OK)
throw std::runtime_error("Could not attach to thread");
jclass native_activity_clazz = java_env->GetObjectClass(app->activity->clazz);
if (native_activity_clazz == NULL)
throw std::runtime_error("Could not get MainActivity class");
jmethodID method_id = java_env->GetMethodID(native_activity_clazz, "getAppDir", "()Ljava/lang/String;");
if (method_id == NULL)
throw std::runtime_error("Could not get method ID");
jstring jstr = (jstring)java_env->CallObjectMethod(app->activity->clazz, method_id);
const char* _str = java_env->GetStringUTFChars(jstr, NULL);
std::string str(_str);
java_env->ReleaseStringUTFChars(jstr, _str);
jni_return = java_vm->DetachCurrentThread();
if (jni_return != JNI_OK)
throw std::runtime_error("Could not detach from thread");
return str;
}
const std::vector<DevVIDPID> AIRSPY_VIDPIDS = {
{ 0x1d50, 0x60a1 }
};
const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS = {
{ 0x03EB, 0x800C }
};
const std::vector<DevVIDPID> HACKRF_VIDPIDS = {
{ 0x1d50, 0x604b },
{ 0x1d50, 0x6089 },
{ 0x1d50, 0xcc15 }
};
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
{ 0x0bda, 0x2832 },
{ 0x0bda, 0x2838 },
{ 0x0413, 0x6680 },
{ 0x0413, 0x6f0f },
{ 0x0458, 0x707f },
{ 0x0ccd, 0x00a9 },
{ 0x0ccd, 0x00b3 },
{ 0x0ccd, 0x00b4 },
{ 0x0ccd, 0x00b5 },
{ 0x0ccd, 0x00b7 },
{ 0x0ccd, 0x00b8 },
{ 0x0ccd, 0x00b9 },
{ 0x0ccd, 0x00c0 },
{ 0x0ccd, 0x00c6 },
{ 0x0ccd, 0x00d3 },
{ 0x0ccd, 0x00d7 },
{ 0x0ccd, 0x00e0 },
{ 0x1554, 0x5020 },
{ 0x15f4, 0x0131 },
{ 0x15f4, 0x0133 },
{ 0x185b, 0x0620 },
{ 0x185b, 0x0650 },
{ 0x185b, 0x0680 },
{ 0x1b80, 0xd393 },
{ 0x1b80, 0xd394 },
{ 0x1b80, 0xd395 },
{ 0x1b80, 0xd397 },
{ 0x1b80, 0xd398 },
{ 0x1b80, 0xd39d },
{ 0x1b80, 0xd3a4 },
{ 0x1b80, 0xd3a8 },
{ 0x1b80, 0xd3af },
{ 0x1b80, 0xd3b0 },
{ 0x1d19, 0x1101 },
{ 0x1d19, 0x1102 },
{ 0x1d19, 0x1103 },
{ 0x1d19, 0x1104 },
{ 0x1f4d, 0xa803 },
{ 0x1f4d, 0xb803 },
{ 0x1f4d, 0xc803 },
{ 0x1f4d, 0xd286 },
{ 0x1f4d, 0xd803 }
};
}
extern "C" {
void android_main(struct android_app* app) {
// Save app instance
app->onAppCmd = backend::handleAppCmd;
app->onInputEvent = backend::handleInputEvent;
backend::app = app;
// Check if this is the first time we run or not
if (backend::initialized) {
flog::warn("android_main called again");
backend::doPartialInit();
backend::pauseRendering = false;
backend::renderLoop();
return;
}
backend::initialized = true;
// Grab files dir
std::string appdir = backend::getAppFilesDir();
// Call main
char* rootpath = new char[appdir.size() + 1];
strcpy(rootpath, appdir.c_str());
char* dummy[] = { "", "-r", rootpath };
sdrpp_main(3, dummy);
}
}

View File

@ -0,0 +1,278 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
// Implemented features:
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// Missing features:
// [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// Important:
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago)with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
// 2021-03-04: Initial version.
#include "imgui.h"
#include "imgui_impl_android.h"
#include <time.h>
#include <android/native_window.h>
#include <android/input.h>
#include <android/keycodes.h>
#include <android/log.h>
// Android data
static double g_Time = 0.0;
static ANativeWindow* g_Window;
static char g_LogTag[] = "ImGuiExample";
static ImGuiKey ImGui_ImplAndroid_KeyCodeToImGuiKey(int32_t key_code)
{
switch (key_code)
{
case AKEYCODE_TAB: return ImGuiKey_Tab;
case AKEYCODE_DPAD_LEFT: return ImGuiKey_LeftArrow;
case AKEYCODE_DPAD_RIGHT: return ImGuiKey_RightArrow;
case AKEYCODE_DPAD_UP: return ImGuiKey_UpArrow;
case AKEYCODE_DPAD_DOWN: return ImGuiKey_DownArrow;
case AKEYCODE_PAGE_UP: return ImGuiKey_PageUp;
case AKEYCODE_PAGE_DOWN: return ImGuiKey_PageDown;
case AKEYCODE_MOVE_HOME: return ImGuiKey_Home;
case AKEYCODE_MOVE_END: return ImGuiKey_End;
case AKEYCODE_INSERT: return ImGuiKey_Insert;
case AKEYCODE_FORWARD_DEL: return ImGuiKey_Delete;
case AKEYCODE_DEL: return ImGuiKey_Backspace;
case AKEYCODE_SPACE: return ImGuiKey_Space;
case AKEYCODE_ENTER: return ImGuiKey_Enter;
case AKEYCODE_ESCAPE: return ImGuiKey_Escape;
case AKEYCODE_APOSTROPHE: return ImGuiKey_Apostrophe;
case AKEYCODE_COMMA: return ImGuiKey_Comma;
case AKEYCODE_MINUS: return ImGuiKey_Minus;
case AKEYCODE_PERIOD: return ImGuiKey_Period;
case AKEYCODE_SLASH: return ImGuiKey_Slash;
case AKEYCODE_SEMICOLON: return ImGuiKey_Semicolon;
case AKEYCODE_EQUALS: return ImGuiKey_Equal;
case AKEYCODE_LEFT_BRACKET: return ImGuiKey_LeftBracket;
case AKEYCODE_BACKSLASH: return ImGuiKey_Backslash;
case AKEYCODE_RIGHT_BRACKET: return ImGuiKey_RightBracket;
case AKEYCODE_GRAVE: return ImGuiKey_GraveAccent;
case AKEYCODE_CAPS_LOCK: return ImGuiKey_CapsLock;
case AKEYCODE_SCROLL_LOCK: return ImGuiKey_ScrollLock;
case AKEYCODE_NUM_LOCK: return ImGuiKey_NumLock;
case AKEYCODE_SYSRQ: return ImGuiKey_PrintScreen;
case AKEYCODE_BREAK: return ImGuiKey_Pause;
case AKEYCODE_NUMPAD_0: return ImGuiKey_Keypad0;
case AKEYCODE_NUMPAD_1: return ImGuiKey_Keypad1;
case AKEYCODE_NUMPAD_2: return ImGuiKey_Keypad2;
case AKEYCODE_NUMPAD_3: return ImGuiKey_Keypad3;
case AKEYCODE_NUMPAD_4: return ImGuiKey_Keypad4;
case AKEYCODE_NUMPAD_5: return ImGuiKey_Keypad5;
case AKEYCODE_NUMPAD_6: return ImGuiKey_Keypad6;
case AKEYCODE_NUMPAD_7: return ImGuiKey_Keypad7;
case AKEYCODE_NUMPAD_8: return ImGuiKey_Keypad8;
case AKEYCODE_NUMPAD_9: return ImGuiKey_Keypad9;
case AKEYCODE_NUMPAD_DOT: return ImGuiKey_KeypadDecimal;
case AKEYCODE_NUMPAD_DIVIDE: return ImGuiKey_KeypadDivide;
case AKEYCODE_NUMPAD_MULTIPLY: return ImGuiKey_KeypadMultiply;
case AKEYCODE_NUMPAD_SUBTRACT: return ImGuiKey_KeypadSubtract;
case AKEYCODE_NUMPAD_ADD: return ImGuiKey_KeypadAdd;
case AKEYCODE_NUMPAD_ENTER: return ImGuiKey_KeypadEnter;
case AKEYCODE_NUMPAD_EQUALS: return ImGuiKey_KeypadEqual;
case AKEYCODE_CTRL_LEFT: return ImGuiKey_LeftCtrl;
case AKEYCODE_SHIFT_LEFT: return ImGuiKey_LeftShift;
case AKEYCODE_ALT_LEFT: return ImGuiKey_LeftAlt;
case AKEYCODE_META_LEFT: return ImGuiKey_LeftSuper;
case AKEYCODE_CTRL_RIGHT: return ImGuiKey_RightCtrl;
case AKEYCODE_SHIFT_RIGHT: return ImGuiKey_RightShift;
case AKEYCODE_ALT_RIGHT: return ImGuiKey_RightAlt;
case AKEYCODE_META_RIGHT: return ImGuiKey_RightSuper;
case AKEYCODE_MENU: return ImGuiKey_Menu;
case AKEYCODE_0: return ImGuiKey_0;
case AKEYCODE_1: return ImGuiKey_1;
case AKEYCODE_2: return ImGuiKey_2;
case AKEYCODE_3: return ImGuiKey_3;
case AKEYCODE_4: return ImGuiKey_4;
case AKEYCODE_5: return ImGuiKey_5;
case AKEYCODE_6: return ImGuiKey_6;
case AKEYCODE_7: return ImGuiKey_7;
case AKEYCODE_8: return ImGuiKey_8;
case AKEYCODE_9: return ImGuiKey_9;
case AKEYCODE_A: return ImGuiKey_A;
case AKEYCODE_B: return ImGuiKey_B;
case AKEYCODE_C: return ImGuiKey_C;
case AKEYCODE_D: return ImGuiKey_D;
case AKEYCODE_E: return ImGuiKey_E;
case AKEYCODE_F: return ImGuiKey_F;
case AKEYCODE_G: return ImGuiKey_G;
case AKEYCODE_H: return ImGuiKey_H;
case AKEYCODE_I: return ImGuiKey_I;
case AKEYCODE_J: return ImGuiKey_J;
case AKEYCODE_K: return ImGuiKey_K;
case AKEYCODE_L: return ImGuiKey_L;
case AKEYCODE_M: return ImGuiKey_M;
case AKEYCODE_N: return ImGuiKey_N;
case AKEYCODE_O: return ImGuiKey_O;
case AKEYCODE_P: return ImGuiKey_P;
case AKEYCODE_Q: return ImGuiKey_Q;
case AKEYCODE_R: return ImGuiKey_R;
case AKEYCODE_S: return ImGuiKey_S;
case AKEYCODE_T: return ImGuiKey_T;
case AKEYCODE_U: return ImGuiKey_U;
case AKEYCODE_V: return ImGuiKey_V;
case AKEYCODE_W: return ImGuiKey_W;
case AKEYCODE_X: return ImGuiKey_X;
case AKEYCODE_Y: return ImGuiKey_Y;
case AKEYCODE_Z: return ImGuiKey_Z;
case AKEYCODE_F1: return ImGuiKey_F1;
case AKEYCODE_F2: return ImGuiKey_F2;
case AKEYCODE_F3: return ImGuiKey_F3;
case AKEYCODE_F4: return ImGuiKey_F4;
case AKEYCODE_F5: return ImGuiKey_F5;
case AKEYCODE_F6: return ImGuiKey_F6;
case AKEYCODE_F7: return ImGuiKey_F7;
case AKEYCODE_F8: return ImGuiKey_F8;
case AKEYCODE_F9: return ImGuiKey_F9;
case AKEYCODE_F10: return ImGuiKey_F10;
case AKEYCODE_F11: return ImGuiKey_F11;
case AKEYCODE_F12: return ImGuiKey_F12;
default: return ImGuiKey_None;
}
}
int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event)
{
ImGuiIO& io = ImGui::GetIO();
int32_t event_type = AInputEvent_getType(input_event);
switch (event_type)
{
case AINPUT_EVENT_TYPE_KEY:
{
int32_t event_key_code = AKeyEvent_getKeyCode(input_event);
int32_t event_scan_code = AKeyEvent_getScanCode(input_event);
int32_t event_action = AKeyEvent_getAction(input_event);
int32_t event_meta_state = AKeyEvent_getMetaState(input_event);
io.AddKeyEvent(ImGuiKey_ModCtrl, (event_meta_state & AMETA_CTRL_ON) != 0);
io.AddKeyEvent(ImGuiKey_ModShift, (event_meta_state & AMETA_SHIFT_ON) != 0);
io.AddKeyEvent(ImGuiKey_ModAlt, (event_meta_state & AMETA_ALT_ON) != 0);
io.AddKeyEvent(ImGuiKey_ModSuper, (event_meta_state & AMETA_META_ON) != 0);
switch (event_action)
{
// FIXME: AKEY_EVENT_ACTION_DOWN and AKEY_EVENT_ACTION_UP occur at once as soon as a touch pointer
// goes up from a key. We use a simple key event queue/ and process one event per key per frame in
// ImGui_ImplAndroid_NewFrame()...or consider using IO queue, if suitable: https://github.com/ocornut/imgui/issues/2787
case AKEY_EVENT_ACTION_DOWN:
case AKEY_EVENT_ACTION_UP:
{
ImGuiKey key = ImGui_ImplAndroid_KeyCodeToImGuiKey(event_key_code);
if (key != ImGuiKey_None && (event_action == AKEY_EVENT_ACTION_DOWN || event_action == AKEY_EVENT_ACTION_UP))
{
io.AddKeyEvent(key, event_action == AKEY_EVENT_ACTION_DOWN);
io.SetKeyEventNativeData(key, event_key_code, event_scan_code);
}
break;
}
default:
break;
}
break;
}
case AINPUT_EVENT_TYPE_MOTION:
{
int32_t event_action = AMotionEvent_getAction(input_event);
int32_t event_pointer_index = (event_action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
event_action &= AMOTION_EVENT_ACTION_MASK;
switch (event_action)
{
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_UP:
// Physical mouse buttons (and probably other physical devices) also invoke the actions AMOTION_EVENT_ACTION_DOWN/_UP,
// but we have to process them separately to identify the actual button pressed. This is done below via
// AMOTION_EVENT_ACTION_BUTTON_PRESS/_RELEASE. Here, we only process "FINGER" input (and "UNKNOWN", as a fallback).
if((AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_FINGER)
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_STYLUS)
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_MOUSE)
|| (AMotionEvent_getToolType(input_event, event_pointer_index) == AMOTION_EVENT_TOOL_TYPE_UNKNOWN))
{
io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
io.AddMouseButtonEvent(0, event_action == AMOTION_EVENT_ACTION_DOWN);
}
break;
case AMOTION_EVENT_ACTION_BUTTON_PRESS:
case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
{
int32_t button_state = AMotionEvent_getButtonState(input_event);
io.AddMouseButtonEvent(0, (button_state & AMOTION_EVENT_BUTTON_PRIMARY) != 0);
io.AddMouseButtonEvent(1, (button_state & AMOTION_EVENT_BUTTON_SECONDARY) != 0);
io.AddMouseButtonEvent(2, (button_state & AMOTION_EVENT_BUTTON_TERTIARY) != 0);
}
break;
case AMOTION_EVENT_ACTION_HOVER_MOVE: // Hovering: Tool moves while NOT pressed (such as a physical mouse)
case AMOTION_EVENT_ACTION_MOVE: // Touch pointer moves while DOWN
io.AddMousePosEvent(AMotionEvent_getX(input_event, event_pointer_index), AMotionEvent_getY(input_event, event_pointer_index));
break;
case AMOTION_EVENT_ACTION_SCROLL:
io.AddMouseWheelEvent(AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_HSCROLL, event_pointer_index), AMotionEvent_getAxisValue(input_event, AMOTION_EVENT_AXIS_VSCROLL, event_pointer_index));
break;
default:
break;
}
}
return 1;
default:
break;
}
return 0;
}
bool ImGui_ImplAndroid_Init(ANativeWindow* window)
{
g_Window = window;
g_Time = 0.0;
// Setup backend capabilities flags
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = "imgui_impl_android";
return true;
}
void ImGui_ImplAndroid_Shutdown()
{
}
void ImGui_ImplAndroid_NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing)
int32_t window_width = ANativeWindow_getWidth(g_Window);
int32_t window_height = ANativeWindow_getHeight(g_Window);
int display_width = window_width;
int display_height = window_height;
io.DisplaySize = ImVec2((float)window_width, (float)window_height);
if (window_width > 0 && window_height > 0)
io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height);
// Setup time step
struct timespec current_timespec;
clock_gettime(CLOCK_MONOTONIC, &current_timespec);
double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0);
io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f / 60.0f);
g_Time = current_time;
}

View File

@ -0,0 +1,28 @@
// dear imgui: Platform Binding for Android native app
// This needs to be used along with the OpenGL 3 Renderer (imgui_impl_opengl3)
// Implemented features:
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy AKEYCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// Missing features:
// [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [ ] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// Important:
// - Consider using SDL or GLFW backend on Android, which will be more full-featured than this.
// - FIXME: On-screen keyboard currently needs to be enabled by the application (see examples/ and issue #3446)
// - FIXME: Unicode character inputs needs to be passed by Dear ImGui by the application (see examples/ and issue #3446)
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
#pragma once
struct ANativeWindow;
struct AInputEvent;
IMGUI_IMPL_API bool ImGui_ImplAndroid_Init(ANativeWindow* window);
IMGUI_IMPL_API int32_t ImGui_ImplAndroid_HandleInputEvent(AInputEvent* input_event);
IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown();
IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame();

View File

@ -0,0 +1,305 @@
#include <backend.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <GLFW/glfw3.h>
#include <utils/flog.h>
#include <utils/opengl_include_code.h>
#include <version.h>
#include <core.h>
#include <filesystem>
#include <stb_image.h>
#include <stb_image_resize.h>
#include <gui/gui.h>
namespace backend {
const char* OPENGL_VERSIONS_GLSL[] = {
"#version 120",
"#version 300 es",
"#version 120"
};
const int OPENGL_VERSIONS_MAJOR[] = {
3,
3,
2
};
const int OPENGL_VERSIONS_MINOR[] = {
0,
1,
1
};
const bool OPENGL_VERSIONS_IS_ES[] = {
false,
true,
false
};
#define OPENGL_VERSION_COUNT (sizeof(OPENGL_VERSIONS_GLSL) / sizeof(char*))
bool maximized = false;
bool fullScreen = false;
int winHeight;
int winWidth;
bool _maximized = maximized;
int fsWidth, fsHeight, fsPosX, fsPosY;
int _winWidth, _winHeight;
GLFWwindow* window;
GLFWmonitor* monitor;
static void glfw_error_callback(int error, const char* description) {
flog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
int init(std::string resDir) {
// Load config
core::configManager.acquire();
winWidth = core::configManager.conf["windowSize"]["w"];
winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
fullScreen = core::configManager.conf["fullscreen"];
core::configManager.release();
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
return 1;
}
#ifdef __APPLE__
// GL 3.2 + GLSL 150
const char* glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
// Create window with graphics context
monitor = glfwGetPrimaryMonitor();
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (window == NULL)
return 1;
glfwMakeContextCurrent(window);
#else
const char* glsl_version = "#version 120";
monitor = NULL;
for (int i = 0; i < OPENGL_VERSION_COUNT; i++) {
glsl_version = OPENGL_VERSIONS_GLSL[i];
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
// Create window with graphics context
monitor = glfwGetPrimaryMonitor();
window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (window == NULL) {
flog::info("OpenGL {0}.{1} {2}was not supported", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? "ES " : "");
continue;
}
flog::info("Using OpenGL {0}.{1}{2}", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? " ES" : "");
glfwMakeContextCurrent(window);
break;
}
#endif
// Load app icon
if (!std::filesystem::is_regular_file(resDir + "/icons/sdrpp.png")) {
flog::error("Icon file '{0}' doesn't exist!", resDir + "/icons/sdrpp.png");
return 1;
}
GLFWimage icons[10];
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4);
icons[1].width = icons[1].height = 16;
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4);
icons[2].width = icons[2].height = 24;
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4);
icons[3].width = icons[3].height = 32;
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4);
icons[4].width = icons[4].height = 48;
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4);
icons[5].width = icons[5].height = 64;
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4);
icons[6].width = icons[6].height = 96;
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4);
icons[7].width = icons[7].height = 128;
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4);
icons[8].width = icons[8].height = 196;
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4);
icons[9].width = icons[9].height = 256;
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
}
// Add callback for max/min if GLFW supports it
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(window);
}
glfwSetWindowMaximizeCallback(window, maximized_callback);
#endif
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
(void)io;
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
if (!ImGui_ImplOpenGL3_Init(glsl_version)) {
// If init fail, try to fall back on GLSL 1.2
flog::warn("Could not init using OpenGL with normal GLSL version, falling back to GLSL 1.2");
if (!ImGui_ImplOpenGL3_Init("#version 120")) {
flog::error("Failed to initialize OpenGL with GLSL 1.2");
return -1;
}
}
// Set window size and fullscreen state
glfwGetWindowSize(window, &_winWidth, &_winHeight);
if (fullScreen) {
flog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(window, &fsPosX, &fsPosY);
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
}
// Everything went according to plan
return 0;
}
void beginFrame() {
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
}
void render(bool vsync) {
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapInterval(vsync);
glfwSwapBuffers(window);
}
void getMouseScreenPos(double& x, double& y) {
glfwGetCursorPos(window, &x, &y);
}
void setMouseScreenPos(double x, double y) {
// Tell GLFW to move the cursor and then manually fire the event
glfwSetCursorPos(window, x, y);
ImGui_ImplGlfw_CursorPosCallback(window, x, y);
}
int renderLoop() {
// Main loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
beginFrame();
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.acquire();
core::configManager.conf["maximized"] = _maximized;
if (!maximized) {
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
glfwGetWindowSize(window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
if (fullScreen) {
flog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(window, &fsPosX, &fsPosY);
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
core::configManager.acquire();
core::configManager.conf["fullscreen"] = true;
core::configManager.release();
}
else {
flog::info("Fullscreen: OFF");
glfwSetWindowMonitor(window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
core::configManager.acquire();
core::configManager.conf["fullscreen"] = false;
core::configManager.release();
}
}
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
winWidth = _winWidth;
winHeight = _winHeight;
core::configManager.acquire();
core::configManager.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
gui::mainWindow.draw();
}
render();
}
return 0;
}
int end() {
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0; // TODO: Int really needed?
}
}

View File

@ -0,0 +1,643 @@
// dear imgui: Platform Backend for GLFW
// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
// (Requires: GLFW 3.1+)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2022-02-07: Added ImGui_ImplGlfw_InstallCallbacks()/ImGui_ImplGlfw_RestoreCallbacks() helpers to facilitate user installing callbacks after iniitializing backend.
// 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago)with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion.
// 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[].
// 2022-01-17: Inputs: calling new io.AddMousePosEvent(), io.AddMouseButtonEvent(), io.AddMouseWheelEvent() API (1.87+).
// 2022-01-17: Inputs: always update key mods next and before key event (not in NewFrame) to fix input queue with very low framerates.
// 2022-01-12: *BREAKING CHANGE*: Now using glfwSetCursorPosCallback(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetCursorPosCallback() and forward it to the backend via ImGui_ImplGlfw_CursorPosCallback().
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
// 2022-01-05: Inputs: Converting GLFW untranslated keycodes back to translated keycodes (in the ImGui_ImplGlfw_KeyCallback() function) in order to match the behavior of every other backend, and facilitate the use of GLFW with lettered-shortcuts API.
// 2021-08-17: *BREAKING CHANGE*: Now using glfwSetWindowFocusCallback() to calling io.AddFocusEvent(). If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() and forward it to the backend via ImGui_ImplGlfw_WindowFocusCallback().
// 2021-07-29: *BREAKING CHANGE*: Now using glfwSetCursorEnterCallback(). MousePos is correctly reported when the host platform window is hovered but not focused. If you called ImGui_ImplGlfw_InitXXX() with install_callbacks = false, you MUST install glfwSetWindowFocusCallback() callback and forward it to the backend via ImGui_ImplGlfw_CursorEnterCallback().
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors.
// 2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor).
// 2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown.
// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
// 2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter().
// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
// 2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them.
// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
// 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples.
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()).
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
// 2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set.
// 2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
#include "imgui.h"
#include "imgui_impl_glfw.h"
// Clang warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#if __has_warning("-Wzero-as-null-pointer-constant")
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
#endif
// GLFW
#include <GLFW/glfw3.h>
#ifdef _WIN32
#undef APIENTRY
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h> // for glfwGetWin32Window
#endif
#ifdef GLFW_RESIZE_NESW_CURSOR // Let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released?
#define GLFW_HAS_NEW_CURSORS (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR
#else
#define GLFW_HAS_NEW_CURSORS (0)
#endif
#define GLFW_HAS_GAMEPAD_API (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetGamepadState() new api
#define GLFW_HAS_GET_KEY_NAME (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwGetKeyName()
// GLFW data
enum GlfwClientApi
{
GlfwClientApi_Unknown,
GlfwClientApi_OpenGL,
GlfwClientApi_Vulkan
};
struct ImGui_ImplGlfw_Data
{
GLFWwindow* Window;
GlfwClientApi ClientApi;
double Time;
GLFWwindow* MouseWindow;
GLFWcursor* MouseCursors[ImGuiMouseCursor_COUNT];
ImVec2 LastValidMousePos;
bool InstalledCallbacks;
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
GLFWwindowfocusfun PrevUserCallbackWindowFocus;
GLFWcursorposfun PrevUserCallbackCursorPos;
GLFWcursorenterfun PrevUserCallbackCursorEnter;
GLFWmousebuttonfun PrevUserCallbackMousebutton;
GLFWscrollfun PrevUserCallbackScroll;
GLFWkeyfun PrevUserCallbackKey;
GLFWcharfun PrevUserCallbackChar;
GLFWmonitorfun PrevUserCallbackMonitor;
ImGui_ImplGlfw_Data() { memset(this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// - Because glfwPollEvents() process all windows and some events may be called outside of it, you will need to register your own callbacks
// (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks.
// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : NULL;
}
// Functions
static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data)
{
return glfwGetClipboardString((GLFWwindow*)user_data);
}
static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
{
glfwSetClipboardString((GLFWwindow*)user_data, text);
}
static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
{
switch (key)
{
case GLFW_KEY_TAB: return ImGuiKey_Tab;
case GLFW_KEY_LEFT: return ImGuiKey_LeftArrow;
case GLFW_KEY_RIGHT: return ImGuiKey_RightArrow;
case GLFW_KEY_UP: return ImGuiKey_UpArrow;
case GLFW_KEY_DOWN: return ImGuiKey_DownArrow;
case GLFW_KEY_PAGE_UP: return ImGuiKey_PageUp;
case GLFW_KEY_PAGE_DOWN: return ImGuiKey_PageDown;
case GLFW_KEY_HOME: return ImGuiKey_Home;
case GLFW_KEY_END: return ImGuiKey_End;
case GLFW_KEY_INSERT: return ImGuiKey_Insert;
case GLFW_KEY_DELETE: return ImGuiKey_Delete;
case GLFW_KEY_BACKSPACE: return ImGuiKey_Backspace;
case GLFW_KEY_SPACE: return ImGuiKey_Space;
case GLFW_KEY_ENTER: return ImGuiKey_Enter;
case GLFW_KEY_ESCAPE: return ImGuiKey_Escape;
case GLFW_KEY_APOSTROPHE: return ImGuiKey_Apostrophe;
case GLFW_KEY_COMMA: return ImGuiKey_Comma;
case GLFW_KEY_MINUS: return ImGuiKey_Minus;
case GLFW_KEY_PERIOD: return ImGuiKey_Period;
case GLFW_KEY_SLASH: return ImGuiKey_Slash;
case GLFW_KEY_SEMICOLON: return ImGuiKey_Semicolon;
case GLFW_KEY_EQUAL: return ImGuiKey_Equal;
case GLFW_KEY_LEFT_BRACKET: return ImGuiKey_LeftBracket;
case GLFW_KEY_BACKSLASH: return ImGuiKey_Backslash;
case GLFW_KEY_RIGHT_BRACKET: return ImGuiKey_RightBracket;
case GLFW_KEY_GRAVE_ACCENT: return ImGuiKey_GraveAccent;
case GLFW_KEY_CAPS_LOCK: return ImGuiKey_CapsLock;
case GLFW_KEY_SCROLL_LOCK: return ImGuiKey_ScrollLock;
case GLFW_KEY_NUM_LOCK: return ImGuiKey_NumLock;
case GLFW_KEY_PRINT_SCREEN: return ImGuiKey_PrintScreen;
case GLFW_KEY_PAUSE: return ImGuiKey_Pause;
case GLFW_KEY_KP_0: return ImGuiKey_Keypad0;
case GLFW_KEY_KP_1: return ImGuiKey_Keypad1;
case GLFW_KEY_KP_2: return ImGuiKey_Keypad2;
case GLFW_KEY_KP_3: return ImGuiKey_Keypad3;
case GLFW_KEY_KP_4: return ImGuiKey_Keypad4;
case GLFW_KEY_KP_5: return ImGuiKey_Keypad5;
case GLFW_KEY_KP_6: return ImGuiKey_Keypad6;
case GLFW_KEY_KP_7: return ImGuiKey_Keypad7;
case GLFW_KEY_KP_8: return ImGuiKey_Keypad8;
case GLFW_KEY_KP_9: return ImGuiKey_Keypad9;
case GLFW_KEY_KP_DECIMAL: return ImGuiKey_KeypadDecimal;
case GLFW_KEY_KP_DIVIDE: return ImGuiKey_KeypadDivide;
case GLFW_KEY_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
case GLFW_KEY_KP_SUBTRACT: return ImGuiKey_KeypadSubtract;
case GLFW_KEY_KP_ADD: return ImGuiKey_KeypadAdd;
case GLFW_KEY_KP_ENTER: return ImGuiKey_KeypadEnter;
case GLFW_KEY_KP_EQUAL: return ImGuiKey_KeypadEqual;
case GLFW_KEY_LEFT_SHIFT: return ImGuiKey_LeftShift;
case GLFW_KEY_LEFT_CONTROL: return ImGuiKey_LeftCtrl;
case GLFW_KEY_LEFT_ALT: return ImGuiKey_LeftAlt;
case GLFW_KEY_LEFT_SUPER: return ImGuiKey_LeftSuper;
case GLFW_KEY_RIGHT_SHIFT: return ImGuiKey_RightShift;
case GLFW_KEY_RIGHT_CONTROL: return ImGuiKey_RightCtrl;
case GLFW_KEY_RIGHT_ALT: return ImGuiKey_RightAlt;
case GLFW_KEY_RIGHT_SUPER: return ImGuiKey_RightSuper;
case GLFW_KEY_MENU: return ImGuiKey_Menu;
case GLFW_KEY_0: return ImGuiKey_0;
case GLFW_KEY_1: return ImGuiKey_1;
case GLFW_KEY_2: return ImGuiKey_2;
case GLFW_KEY_3: return ImGuiKey_3;
case GLFW_KEY_4: return ImGuiKey_4;
case GLFW_KEY_5: return ImGuiKey_5;
case GLFW_KEY_6: return ImGuiKey_6;
case GLFW_KEY_7: return ImGuiKey_7;
case GLFW_KEY_8: return ImGuiKey_8;
case GLFW_KEY_9: return ImGuiKey_9;
case GLFW_KEY_A: return ImGuiKey_A;
case GLFW_KEY_B: return ImGuiKey_B;
case GLFW_KEY_C: return ImGuiKey_C;
case GLFW_KEY_D: return ImGuiKey_D;
case GLFW_KEY_E: return ImGuiKey_E;
case GLFW_KEY_F: return ImGuiKey_F;
case GLFW_KEY_G: return ImGuiKey_G;
case GLFW_KEY_H: return ImGuiKey_H;
case GLFW_KEY_I: return ImGuiKey_I;
case GLFW_KEY_J: return ImGuiKey_J;
case GLFW_KEY_K: return ImGuiKey_K;
case GLFW_KEY_L: return ImGuiKey_L;
case GLFW_KEY_M: return ImGuiKey_M;
case GLFW_KEY_N: return ImGuiKey_N;
case GLFW_KEY_O: return ImGuiKey_O;
case GLFW_KEY_P: return ImGuiKey_P;
case GLFW_KEY_Q: return ImGuiKey_Q;
case GLFW_KEY_R: return ImGuiKey_R;
case GLFW_KEY_S: return ImGuiKey_S;
case GLFW_KEY_T: return ImGuiKey_T;
case GLFW_KEY_U: return ImGuiKey_U;
case GLFW_KEY_V: return ImGuiKey_V;
case GLFW_KEY_W: return ImGuiKey_W;
case GLFW_KEY_X: return ImGuiKey_X;
case GLFW_KEY_Y: return ImGuiKey_Y;
case GLFW_KEY_Z: return ImGuiKey_Z;
case GLFW_KEY_F1: return ImGuiKey_F1;
case GLFW_KEY_F2: return ImGuiKey_F2;
case GLFW_KEY_F3: return ImGuiKey_F3;
case GLFW_KEY_F4: return ImGuiKey_F4;
case GLFW_KEY_F5: return ImGuiKey_F5;
case GLFW_KEY_F6: return ImGuiKey_F6;
case GLFW_KEY_F7: return ImGuiKey_F7;
case GLFW_KEY_F8: return ImGuiKey_F8;
case GLFW_KEY_F9: return ImGuiKey_F9;
case GLFW_KEY_F10: return ImGuiKey_F10;
case GLFW_KEY_F11: return ImGuiKey_F11;
case GLFW_KEY_F12: return ImGuiKey_F12;
default: return ImGuiKey_None;
}
}
static void ImGui_ImplGlfw_UpdateKeyModifiers(int mods)
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiKey_ModCtrl, (mods & GLFW_MOD_CONTROL) != 0);
io.AddKeyEvent(ImGuiKey_ModShift, (mods & GLFW_MOD_SHIFT) != 0);
io.AddKeyEvent(ImGuiKey_ModAlt, (mods & GLFW_MOD_ALT) != 0);
io.AddKeyEvent(ImGuiKey_ModSuper, (mods & GLFW_MOD_SUPER) != 0);
}
void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackMousebutton != NULL && window == bd->Window)
bd->PrevUserCallbackMousebutton(window, button, action, mods);
ImGui_ImplGlfw_UpdateKeyModifiers(mods);
ImGuiIO& io = ImGui::GetIO();
if (button >= 0 && button < ImGuiMouseButton_COUNT)
io.AddMouseButtonEvent(button, action == GLFW_PRESS);
}
void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackScroll != NULL && window == bd->Window)
bd->PrevUserCallbackScroll(window, xoffset, yoffset);
ImGuiIO& io = ImGui::GetIO();
io.AddMouseWheelEvent((float)xoffset, (float)yoffset);
}
static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode)
{
#if GLFW_HAS_GET_KEY_NAME && !defined(__EMSCRIPTEN__)
// GLFW 3.1+ attempts to "untranslate" keys, which goes the opposite of what every other framework does, making using lettered shortcuts difficult.
// (It had reasons to do so: namely GLFW is/was more likely to be used for WASD-type game controls rather than lettered shortcuts, but IHMO the 3.1 change could have been done differently)
// See https://github.com/glfw/glfw/issues/1502 for details.
// Adding a workaround to undo this (so our keys are translated->untranslated->translated, likely a lossy process).
// This won't cover edge cases but this is at least going to cover common cases.
if (key >= GLFW_KEY_KP_0 && key <= GLFW_KEY_KP_EQUAL)
return key;
const char* key_name = glfwGetKeyName(key, scancode);
if (key_name && key_name[0] != 0 && key_name[1] == 0)
{
const char char_names[] = "`-=[]\\,;\'./";
const int char_keys[] = { GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_MINUS, GLFW_KEY_EQUAL, GLFW_KEY_LEFT_BRACKET, GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_BACKSLASH, GLFW_KEY_COMMA, GLFW_KEY_SEMICOLON, GLFW_KEY_APOSTROPHE, GLFW_KEY_PERIOD, GLFW_KEY_SLASH, 0 };
IM_ASSERT(IM_ARRAYSIZE(char_names) == IM_ARRAYSIZE(char_keys));
if (key_name[0] >= '0' && key_name[0] <= '9') { key = GLFW_KEY_0 + (key_name[0] - '0'); }
else if (key_name[0] >= 'A' && key_name[0] <= 'Z') { key = GLFW_KEY_A + (key_name[0] - 'A'); }
else if (const char* p = strchr(char_names, key_name[0])) { key = char_keys[p - char_names]; }
}
// if (action == GLFW_PRESS) printf("key %d scancode %d name '%s'\n", key, scancode, key_name);
#else
IM_UNUSED(scancode);
#endif
return key;
}
void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackKey != NULL && window == bd->Window)
bd->PrevUserCallbackKey(window, keycode, scancode, action, mods);
if (action != GLFW_PRESS && action != GLFW_RELEASE)
return;
ImGui_ImplGlfw_UpdateKeyModifiers(mods);
keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode);
ImGuiIO& io = ImGui::GetIO();
ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode);
io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code)
}
void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackWindowFocus != NULL && window == bd->Window)
bd->PrevUserCallbackWindowFocus(window, focused);
ImGuiIO& io = ImGui::GetIO();
io.AddFocusEvent(focused != 0);
}
void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackCursorPos != NULL && window == bd->Window)
bd->PrevUserCallbackCursorPos(window, x, y);
ImGuiIO& io = ImGui::GetIO();
io.AddMousePosEvent((float)x, (float)y);
bd->LastValidMousePos = ImVec2((float)x, (float)y);
}
// Workaround: X11 seems to send spurious Leave/Enter events which would make us lose our position,
// so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984)
void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackCursorEnter != NULL && window == bd->Window)
bd->PrevUserCallbackCursorEnter(window, entered);
ImGuiIO& io = ImGui::GetIO();
if (entered)
{
bd->MouseWindow = window;
io.AddMousePosEvent(bd->LastValidMousePos.x, bd->LastValidMousePos.y);
}
else if (!entered && bd->MouseWindow == window)
{
bd->LastValidMousePos = io.MousePos;
bd->MouseWindow = NULL;
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
}
void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if (bd->PrevUserCallbackChar != NULL && window == bd->Window)
bd->PrevUserCallbackChar(window, c);
ImGuiIO& io = ImGui::GetIO();
io.AddInputCharacter(c);
}
void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int)
{
// Unused in 'master' branch but 'docking' branch will use this, so we declare it ahead of it so if you have to install callbacks you can install this one too.
}
void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!");
IM_ASSERT(bd->Window == window);
bd->PrevUserCallbackWindowFocus = glfwSetWindowFocusCallback(window, ImGui_ImplGlfw_WindowFocusCallback);
bd->PrevUserCallbackCursorEnter = glfwSetCursorEnterCallback(window, ImGui_ImplGlfw_CursorEnterCallback);
bd->PrevUserCallbackCursorPos = glfwSetCursorPosCallback(window, ImGui_ImplGlfw_CursorPosCallback);
bd->PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
bd->PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
bd->PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
bd->PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
bd->PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
bd->InstalledCallbacks = true;
}
void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window)
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!");
IM_ASSERT(bd->Window == window);
glfwSetWindowFocusCallback(window, bd->PrevUserCallbackWindowFocus);
glfwSetCursorEnterCallback(window, bd->PrevUserCallbackCursorEnter);
glfwSetCursorPosCallback(window, bd->PrevUserCallbackCursorPos);
glfwSetMouseButtonCallback(window, bd->PrevUserCallbackMousebutton);
glfwSetScrollCallback(window, bd->PrevUserCallbackScroll);
glfwSetKeyCallback(window, bd->PrevUserCallbackKey);
glfwSetCharCallback(window, bd->PrevUserCallbackChar);
glfwSetMonitorCallback(bd->PrevUserCallbackMonitor);
bd->InstalledCallbacks = false;
bd->PrevUserCallbackWindowFocus = NULL;
bd->PrevUserCallbackCursorEnter = NULL;
bd->PrevUserCallbackCursorPos = NULL;
bd->PrevUserCallbackMousebutton = NULL;
bd->PrevUserCallbackScroll = NULL;
bd->PrevUserCallbackKey = NULL;
bd->PrevUserCallbackChar = NULL;
bd->PrevUserCallbackMonitor = NULL;
}
static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api)
{
ImGuiIO& io = ImGui::GetIO();
IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!");
// Setup backend capabilities flags
ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)();
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_glfw";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bd->Window = window;
bd->Time = 0.0;
io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
io.ClipboardUserData = bd->Window;
// Set platform dependent data in viewport
#if defined(_WIN32)
ImGui::GetMainViewport()->PlatformHandleRaw = (void*)glfwGetWin32Window(bd->Window);
#endif
// Create mouse cursors
// (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
// GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
// Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL);
bd->MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
#if GLFW_HAS_NEW_CURSORS
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
#else
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
#endif
glfwSetErrorCallback(prev_error_callback);
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
if (install_callbacks)
ImGui_ImplGlfw_InstallCallbacks(window);
bd->ClientApi = client_api;
return true;
}
bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks)
{
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL);
}
bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks)
{
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan);
}
bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks)
{
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
}
void ImGui_ImplGlfw_Shutdown()
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
IM_ASSERT(bd != NULL && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
if (bd->InstalledCallbacks)
ImGui_ImplGlfw_RestoreCallbacks(bd->Window);
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
glfwDestroyCursor(bd->MouseCursors[cursor_n]);
io.BackendPlatformName = NULL;
io.BackendPlatformUserData = NULL;
IM_DELETE(bd);
}
static void ImGui_ImplGlfw_UpdateMouseData()
{
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
#ifdef __EMSCRIPTEN__
const bool is_app_focused = true;
#else
const bool is_app_focused = glfwGetWindowAttrib(bd->Window, GLFW_FOCUSED) != 0;
#endif
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos)
glfwSetCursorPos(bd->Window, (double)io.MousePos.x, (double)io.MousePos.y);
// (Optional) Fallback to provide mouse position when focused (ImGui_ImplGlfw_CursorPosCallback already provides this when hovered or captured)
if (is_app_focused && bd->MouseWindow == NULL)
{
double mouse_x, mouse_y;
glfwGetCursorPos(bd->Window, &mouse_x, &mouse_y);
io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y);
}
}
}
static void ImGui_ImplGlfw_UpdateMouseCursor()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(bd->Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
return;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
else
{
// Show OS mouse cursor
// FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
glfwSetCursor(bd->Window, bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow]);
glfwSetInputMode(bd->Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
}
// Update gamepad inputs
static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; }
static void ImGui_ImplGlfw_UpdateGamepads()
{
ImGuiIO& io = ImGui::GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
#if GLFW_HAS_GAMEPAD_API
GLFWgamepadstate gamepad;
if (!glfwGetGamepadState(GLFW_JOYSTICK_1, &gamepad))
return;
#define MAP_BUTTON(KEY_NO, BUTTON_NO, _UNUSED) do { io.AddKeyEvent(KEY_NO, gamepad.buttons[BUTTON_NO] != 0); } while (0)
#define MAP_ANALOG(KEY_NO, AXIS_NO, _UNUSED, V0, V1) do { float v = gamepad.axes[AXIS_NO]; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
#else
int axes_count = 0, buttons_count = 0;
const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
if (axes_count == 0 || buttons_count == 0)
return;
#define MAP_BUTTON(KEY_NO, _UNUSED, BUTTON_NO) do { io.AddKeyEvent(KEY_NO, (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS)); } while (0)
#define MAP_ANALOG(KEY_NO, _UNUSED, AXIS_NO, V0, V1) do { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); io.AddKeyAnalogEvent(KEY_NO, v > 0.10f, Saturate(v)); } while (0)
#endif
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
MAP_BUTTON(ImGuiKey_GamepadStart, GLFW_GAMEPAD_BUTTON_START, 7);
MAP_BUTTON(ImGuiKey_GamepadBack, GLFW_GAMEPAD_BUTTON_BACK, 6);
MAP_BUTTON(ImGuiKey_GamepadFaceDown, GLFW_GAMEPAD_BUTTON_A, 0); // Xbox A, PS Cross
MAP_BUTTON(ImGuiKey_GamepadFaceRight, GLFW_GAMEPAD_BUTTON_B, 1); // Xbox B, PS Circle
MAP_BUTTON(ImGuiKey_GamepadFaceLeft, GLFW_GAMEPAD_BUTTON_X, 2); // Xbox X, PS Square
MAP_BUTTON(ImGuiKey_GamepadFaceUp, GLFW_GAMEPAD_BUTTON_Y, 3); // Xbox Y, PS Triangle
MAP_BUTTON(ImGuiKey_GamepadDpadLeft, GLFW_GAMEPAD_BUTTON_DPAD_LEFT, 13);
MAP_BUTTON(ImGuiKey_GamepadDpadRight, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, 11);
MAP_BUTTON(ImGuiKey_GamepadDpadUp, GLFW_GAMEPAD_BUTTON_DPAD_UP, 10);
MAP_BUTTON(ImGuiKey_GamepadDpadDown, GLFW_GAMEPAD_BUTTON_DPAD_DOWN, 12);
MAP_BUTTON(ImGuiKey_GamepadL1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, 4);
MAP_BUTTON(ImGuiKey_GamepadR1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, 5);
MAP_ANALOG(ImGuiKey_GamepadL2, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, 4, -0.75f, +1.0f);
MAP_ANALOG(ImGuiKey_GamepadR2, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, 5, -0.75f, +1.0f);
MAP_BUTTON(ImGuiKey_GamepadL3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB, 8);
MAP_BUTTON(ImGuiKey_GamepadR3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB, 9);
MAP_ANALOG(ImGuiKey_GamepadLStickLeft, GLFW_GAMEPAD_AXIS_LEFT_X, 0, -0.25f, -1.0f);
MAP_ANALOG(ImGuiKey_GamepadLStickRight, GLFW_GAMEPAD_AXIS_LEFT_X, 0, +0.25f, +1.0f);
MAP_ANALOG(ImGuiKey_GamepadLStickUp, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, -0.25f, -1.0f);
MAP_ANALOG(ImGuiKey_GamepadLStickDown, GLFW_GAMEPAD_AXIS_LEFT_Y, 1, +0.25f, +1.0f);
MAP_ANALOG(ImGuiKey_GamepadRStickLeft, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, -0.25f, -1.0f);
MAP_ANALOG(ImGuiKey_GamepadRStickRight, GLFW_GAMEPAD_AXIS_RIGHT_X, 2, +0.25f, +1.0f);
MAP_ANALOG(ImGuiKey_GamepadRStickUp, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, -0.25f, -1.0f);
MAP_ANALOG(ImGuiKey_GamepadRStickDown, GLFW_GAMEPAD_AXIS_RIGHT_Y, 3, +0.25f, +1.0f);
#undef MAP_BUTTON
#undef MAP_ANALOG
}
void ImGui_ImplGlfw_NewFrame()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
IM_ASSERT(bd != NULL && "Did you call ImGui_ImplGlfw_InitForXXX()?");
// Setup display size (every frame to accommodate for window resizing)
int w, h;
int display_w, display_h;
glfwGetWindowSize(bd->Window, &w, &h);
glfwGetFramebufferSize(bd->Window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h);
// Setup time step
double current_time = glfwGetTime();
io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f);
bd->Time = current_time;
ImGui_ImplGlfw_UpdateMouseData();
ImGui_ImplGlfw_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplGlfw_UpdateGamepads();
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif

View File

@ -4,11 +4,12 @@
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy GLFW_KEY_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW.
// [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE).
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
@ -20,6 +21,7 @@
#include "imgui.h" // IMGUI_IMPL_API
struct GLFWwindow;
struct GLFWmonitor;
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks);
IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks);
@ -27,10 +29,18 @@ IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool ins
IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown();
IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame();
// GLFW callbacks
// - When calling Init with 'install_callbacks=true': GLFW callbacks will be installed for you. They will call user's previously installed callbacks, if any.
// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call those function yourself from your own GLFW callbacks.
// GLFW callbacks (installer)
// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any.
// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks.
IMGUI_IMPL_API void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window);
IMGUI_IMPL_API void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window);
// GLFW callbacks (individual callbacks to call if you didn't install callbacks)
IMGUI_IMPL_API void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused); // Since 1.84
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); // Since 1.84
IMGUI_IMPL_API void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y); // Since 1.87
IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);

View File

@ -17,8 +17,14 @@ if(COMPILER_SUPPORTS_WPEDANTIC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wpedantic")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O0 -fsanitize=address")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie,")
# On android, keep optimisations and don't use asan
if (ANDROID)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O3")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -O0 -fsanitize=address")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie,")
endif()
else()
if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
@ -85,9 +91,9 @@ add_subdirectory(tests)
add_subdirectory(tools)
# add_subdirectory(benchmarks)
install(TARGETS correct correct_static
DESTINATION lib)
install(FILES ${INSTALL_HEADERS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include")
# install(TARGETS correct correct_static
# DESTINATION lib)
# install(FILES ${INSTALL_HEADERS} DESTINATION "${CMAKE_INSTALL_PREFIX}/include")
add_library(fec_shim_static EXCLUDE_FROM_ALL src/fec_shim.c ${correct_obj_files})
set_target_properties(fec_shim_static PROPERTIES OUTPUT_NAME "fec")
@ -96,7 +102,7 @@ set_target_properties(fec_shim_shared PROPERTIES OUTPUT_NAME "fec")
add_custom_target(fec-shim-h COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/include/fec_shim.h ${PROJECT_BINARY_DIR}/include/fec.h)
add_custom_target(shim DEPENDS fec_shim_static fec_shim_shared fec-shim-h)
install(TARGETS fec_shim_static fec_shim_shared
DESTINATION lib
OPTIONAL)
install(FILES ${PROJECT_BINARY_DIR}/include/fec.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include" OPTIONAL)
# install(TARGETS fec_shim_static fec_shim_shared
# DESTINATION lib
# OPTIONAL)
# install(FILES ${PROJECT_BINARY_DIR}/include/fec.h DESTINATION "${CMAKE_INSTALL_PREFIX}/include" OPTIONAL)

12
core/src/backend.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace backend {
int init(std::string resDir = "");
void beginFrame();
void render(bool vsync = true);
void getMouseScreenPos(double& x, double& y);
void setMouseScreenPos(double x, double y);
int renderLoop();
int end();
}

124
core/src/command_args.cpp Normal file
View File

@ -0,0 +1,124 @@
#include "command_args.h"
#include <filesystem>
void CommandArgsParser::defineAll() {
#if defined(_WIN32)
std::string root = ".";
define('c', "con", "Show console on Windows");
#elif defined(IS_MACOS_BUNDLE)
std::string root = (std::string)getenv("HOME") + "/Library/Application Support/sdrpp";
#elif defined(__ANDROID__)
std::string root = "/storage/self/primary/sdrpp";
#else
std::string root = (std::string)getenv("HOME") + "/.config/sdrpp";
#endif
define('a', "addr", "Server mode address", "0.0.0.0");
define('h', "help", "Show help");
define('p', "port", "Server mode port", 5259);
define('r', "root", "Root directory, where all config files are stored", std::filesystem::absolute(root).string());
define('s', "server", "Run in server mode");
define('\0', "autostart", "Automatically start the SDR after loading");
}
int CommandArgsParser::parse(int argc, char* argv[]) {
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
// Check for long and short name arguments
if (!arg.rfind("--", 0)) {
arg = arg.substr(2);
}
else if (!arg.rfind("-", 0)) {
if (aliases.find(arg[1]) == aliases.end()) {
printf("Unknown argument\n");
showHelp();
return -1;
}
arg = aliases[arg[1]];
}
else {
printf("Invalid argument\n");
showHelp();
return -1;
}
// Make sure the argument exists
if (args.find(arg) == args.end()) {
printf("Unknown argument\n");
showHelp();
return -1;
}
// Parse depending on type
CLIArg& carg = args[arg];
// If not void, make sure an argument is available and retrieve it
if (carg.type != CLI_ARG_TYPE_VOID && i + 1 >= argc) {
printf("Missing argument\n");
showHelp();
return -1;
}
// Parse void since arg won't be needed
if (carg.type == CLI_ARG_TYPE_VOID) {
carg.bval = true;
continue;
}
// Parse types that require parsing
arg = argv[++i];
if (carg.type == CLI_ARG_TYPE_BOOL) {
// Enforce lower case
for (int i = 0; i < arg.size(); i++) { arg[i] = std::tolower(arg[i]); }
if (arg == "true" || arg == "on" || arg == "1") {
carg.bval = true;
}
else if (arg == "false" || arg == "off" || arg == "0") {
carg.bval = true;
}
else {
printf("Invalid argument, expected bool (true, false, on, off, 1, 0)\n");
showHelp();
return -1;
}
}
else if (carg.type == CLI_ARG_TYPE_INT) {
try {
carg.ival = std::stoi(arg);
}
catch (std::exception e) {
printf("Invalid argument, failed to parse integer\n");
showHelp();
return -1;
}
}
else if (carg.type == CLI_ARG_TYPE_FLOAT) {
try {
carg.fval = std::stod(arg);
}
catch (std::exception e) {
printf("Invalid argument, failed to parse float\n");
showHelp();
return -1;
}
}
else if (carg.type == CLI_ARG_TYPE_STRING) {
carg.sval = arg;
}
}
return 0;
}
void CommandArgsParser::showHelp() {
for (auto const& [ln, arg] : args) {
if (arg.alias) {
printf("-%c --%s\t\t%s\n", arg.alias, ln.c_str(), arg.description.c_str());
}
else {
printf(" --%s\t\t%s\n", ln.c_str(), arg.description.c_str());
}
}
}

153
core/src/command_args.h Normal file
View File

@ -0,0 +1,153 @@
#pragma once
#include <string>
#include <map>
#include <stdexcept>
enum CLIArgType {
CLI_ARG_TYPE_INVALID,
CLI_ARG_TYPE_VOID,
CLI_ARG_TYPE_BOOL,
CLI_ARG_TYPE_INT,
CLI_ARG_TYPE_FLOAT,
CLI_ARG_TYPE_STRING
};
class CommandArgsParser;
class CLIArg {
public:
CLIArg() {
type = CLI_ARG_TYPE_INVALID;
}
CLIArg(char al, std::string desc) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_VOID;
bval = false;
}
CLIArg(char al, std::string desc, bool b) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_BOOL;
bval = b;
}
CLIArg(char al, std::string desc, int i) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_INT;
ival = i;
}
CLIArg(char al, std::string desc, double f) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_FLOAT;
fval = f;
}
CLIArg(char al, std::string desc, std::string s) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_STRING;
sval = s;
}
CLIArg(char al, std::string desc, const char* s) {
alias = al;
description = desc;
type = CLI_ARG_TYPE_STRING;
sval = s;
}
operator bool() const {
if (type != CLI_ARG_TYPE_BOOL && type != CLI_ARG_TYPE_VOID) { throw std::runtime_error("Not a bool"); }
return bval;
}
operator int() const {
if (type != CLI_ARG_TYPE_INT) { throw std::runtime_error("Not an int"); }
return ival;
}
operator float() const {
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
return (float)fval;
}
operator double() const {
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
return fval;
}
operator std::string() const {
if (type != CLI_ARG_TYPE_STRING) { throw std::runtime_error("Not a string"); }
return sval;
}
bool b() {
if (type != CLI_ARG_TYPE_BOOL && type != CLI_ARG_TYPE_VOID) { throw std::runtime_error("Not a bool"); }
return bval;
}
int i() {
if (type != CLI_ARG_TYPE_INT) { throw std::runtime_error("Not an int"); }
return ival;
}
float f() {
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
return (float)fval;
}
double d() {
if (type != CLI_ARG_TYPE_FLOAT) { throw std::runtime_error("Not a float"); }
return fval;
}
const std::string& s() {
if (type != CLI_ARG_TYPE_STRING) { throw std::runtime_error("Not a string"); }
return sval;
}
friend CommandArgsParser;
CLIArgType type;
char alias;
std::string description;
private:
bool bval;
int ival;
std::string sval;
double fval;
};
class CommandArgsParser {
public:
void define(char shortName, std::string name, std::string desc) {
args[name] = CLIArg(shortName, desc);
aliases[shortName] = name;
}
void defineAll();
template<class T>
void define(char shortName, std::string name, std::string desc, T defValue) {
args[name] = CLIArg(shortName, desc, defValue);
aliases[shortName] = name;
}
int parse(int argc, char* argv[]);
void showHelp();
CLIArg operator[](std::string name) {
return args[name];
}
private:
std::map<std::string, CLIArg> args;
std::map<char, std::string> aliases;
};

View File

@ -1,11 +1,10 @@
#include <config.h>
#include <spdlog/spdlog.h>
#include <utils/flog.h>
#include <fstream>
#include <filesystem>
ConfigManager::ConfigManager() {
}
ConfigManager::~ConfigManager() {
@ -13,32 +12,32 @@ ConfigManager::~ConfigManager() {
}
void ConfigManager::setPath(std::string file) {
path = file;
path = std::filesystem::absolute(file).string();
}
void ConfigManager::load(json def, bool lock) {
if (lock) { mtx.lock(); }
if (path == "") {
spdlog::error("Config manager tried to load file with no path specified");
flog::error("Config manager tried to load file with no path specified");
return;
}
if (!std::filesystem::exists(path)) {
spdlog::warn("Config file '{0}' does not exist, creating it", path);
flog::warn("Config file '{0}' does not exist, creating it", path);
conf = def;
save(false);
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Config file '{0}' isn't a file", path);
flog::error("Config file '{0}' isn't a file", path);
return;
}
try {
std::ifstream file(path.c_str());
file >> conf;
file.close();
}
catch (std::exception e) {
spdlog::error("Config file '{0}' is corrupted, resetting it", path);
flog::error("Config file '{0}' is corrupted, resetting it", path);
conf = def;
save(false);
}
@ -83,7 +82,7 @@ void ConfigManager::release(bool modified) {
void ConfigManager::autoSaveWorker() {
while (autoSaveEnabled) {
if (!mtx.try_lock()) {
spdlog::warn("ConfigManager locked, waiting...");
flog::warn("ConfigManager locked, waiting...");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
continue;
}
@ -96,7 +95,7 @@ void ConfigManager::autoSaveWorker() {
// Sleep but listen for wakeup call
{
std::unique_lock<std::mutex> lock(termMtx);
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } );
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; });
}
}
}

View File

@ -20,7 +20,7 @@ public:
void release(bool modified = false);
json conf;
private:
void autoSaveWorker();
@ -33,5 +33,4 @@ private:
std::mutex termMtx;
std::condition_variable termCond;
volatile bool termFlag = false;
};

View File

@ -1,24 +1,19 @@
#include <server.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <gui/icons.h>
#include <version.h>
#include <spdlog/spdlog.h>
#include <utils/flog.h>
#include <gui/widgets/bandplan.h>
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <glfw_window.h>
#include <options.h>
#include <filesystem>
#include <gui/menus/theme.h>
#include <server.h>
#include <backend.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
@ -30,97 +25,81 @@
#endif
#ifndef INSTALL_PREFIX
#ifdef __APPLE__
#define INSTALL_PREFIX "/usr/local"
#else
#define INSTALL_PREFIX "/usr"
#endif
#ifdef __APPLE__
#define INSTALL_PREFIX "/usr/local"
#else
#define INSTALL_PREFIX "/usr"
#endif
#endif
const char* OPENGL_VERSIONS_GLSL[] = {
"#version 120",
"#version 300 es",
"#version 120"
};
const int OPENGL_VERSIONS_MAJOR[] = {
3,
3,
2
};
const int OPENGL_VERSIONS_MINOR[] = {
0,
1,
1
};
const bool OPENGL_VERSIONS_IS_ES[] = {
false,
true,
false
};
#define OPENGL_VERSION_COUNT (sizeof(OPENGL_VERSIONS_GLSL) / sizeof(char*))
namespace core {
ConfigManager configManager;
ModuleManager moduleManager;
ModuleComManager modComManager;
GLFWwindow* window;
CommandArgsParser args;
void setInputSampleRate(double samplerate) {
sigpath::signalPath.sourceSampleRate = samplerate;
double effectiveSr = samplerate / ((double)(1 << sigpath::signalPath.decimation));
// NOTE: Zoom controls won't work
spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
// Forward this to the server
if (args["server"].b()) { server::setInputSampleRate(samplerate); return; }
// Update IQ frontend input samplerate and get effective samplerate
sigpath::iqFrontEnd.setSampleRate(samplerate);
double effectiveSr = sigpath::iqFrontEnd.getEffectiveSamplerate();
// Reset zoom
gui::waterfall.setBandwidth(effectiveSr);
gui::waterfall.setViewOffset(0);
gui::waterfall.setViewBandwidth(effectiveSr);
sigpath::signalPath.setSampleRate(effectiveSr);
gui::mainWindow.setViewBandwidthSlider(1.0);
// Debug logs
flog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
}
};
bool maximized = false;
bool fullScreen = false;
static void glfw_error_callback(int error, const char* description) {
spdlog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
// main
int sdrpp_main(int argc, char *argv[]) {
spdlog::info("SDR++ v" VERSION_STR);
int sdrpp_main(int argc, char* argv[]) {
flog::info("SDR++ v" VERSION_STR);
// Load default options and parse command line
options::loadDefaults();
if (!options::parse(argc, argv)) { return -1; }
#ifdef IS_MACOS_BUNDLE
// If this is a MacOS .app, CD to the correct directory
auto execPath = std::filesystem::absolute(argv[0]);
chdir(execPath.parent_path().string().c_str());
#endif
// Define command line options and parse arguments
core::args.defineAll();
if (core::args.parse(argc, argv) < 0) { return -1; }
// Show help and exit if requested
if (core::args["help"].b()) {
core::args.showHelp();
return 0;
}
bool serverMode = (bool)core::args["server"];
#ifdef _WIN32
if (!options::opts.showConsole) { FreeConsole(); }
// Free console if the user hasn't asked for a console and not in server mode
if (!core::args["con"].b() && !serverMode) { FreeConsole(); }
// Set error mode to avoid abnoxious popups
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_NOGPFAULTERRORBOX | SEM_FAILCRITICALERRORS);
#endif
// Check root directory
if (!std::filesystem::exists(options::opts.root)) {
spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root);
if (!std::filesystem::create_directory(options::opts.root)) {
spdlog::error("Could not create root directory {0}", options::opts.root);
std::string root = (std::string)core::args["root"];
if (!std::filesystem::exists(root)) {
flog::warn("Root directory {0} does not exist, creating it", root);
if (!std::filesystem::create_directories(root)) {
flog::error("Could not create root directory {0}", root);
return -1;
}
}
if (!std::filesystem::is_directory(options::opts.root)) {
spdlog::error("{0} is not a directory", options::opts.root);
// Check that the path actually is a directory
if (!std::filesystem::is_directory(root)) {
flog::error("{0} is not a directory", root);
return -1;
}
@ -136,15 +115,18 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["bandPlanPos"] = 0;
defConfig["centerTuning"] = false;
defConfig["colorMap"] = "Classic";
defConfig["fftHold"] = false;
defConfig["fftHoldSpeed"] = 60;
defConfig["fastFFT"] = false;
defConfig["fftHeight"] = 300;
defConfig["fftRate"] = 20;
defConfig["fftSize"] = 65536;
defConfig["fftWindow"] = 1;
defConfig["fftWindow"] = 2;
defConfig["frequency"] = 100000000.0;
defConfig["fullWaterfallUpdate"] = false;
defConfig["max"] = 0.0;
defConfig["maximized"] = false;
defConfig["fullscreen"] = false;
// Menu
defConfig["menuElements"] = json::array();
@ -167,9 +149,6 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["menuElements"][4]["name"] = "VFO Color";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][5]["name"] = "Scripting";
defConfig["menuElements"][5]["open"] = false;
defConfig["menuElements"][6]["name"] = "Band Plan";
defConfig["menuElements"][6]["open"] = true;
@ -184,26 +163,34 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["moduleInstances"]["Airspy Source"]["enabled"] = true;
defConfig["moduleInstances"]["AirspyHF+ Source"]["module"] = "airspyhf_source";
defConfig["moduleInstances"]["AirspyHF+ Source"]["enabled"] = true;
defConfig["moduleInstances"]["Audio Source"]["module"] = "audio_source";
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
defConfig["moduleInstances"]["RTL-SDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-TCP Source"]["module"] = "rtl_tcp_source";
defConfig["moduleInstances"]["RTL-TCP Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDRplay Source"]["module"] = "sdrplay_source";
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["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"]["Network Sink"] = "network_sink";
@ -213,12 +200,22 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager";
defConfig["moduleInstances"]["Recorder"] = "recorder";
defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server";
// defConfig["moduleInstances"]["Rigctl Client"] = "rigctl_client";
// TODO: Enable rigctl_client when ready
// defConfig["moduleInstances"]["Scanner"] = "scanner";
// TODO: Enable scanner when ready
// Themes
defConfig["theme"] = "Dark";
#ifdef __ANDROID__
defConfig["uiScale"] = 3.0f;
#else
defConfig["uiScale"] = 1.0f;
#endif
defConfig["modules"] = json::array();
defConfig["offsetMode"] = (int)0; // Off
defConfig["offset"] = 0.0;
defConfig["showMenu"] = true;
@ -226,6 +223,7 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["source"] = "";
defConfig["decimationPower"] = 0;
defConfig["iqCorrection"] = false;
defConfig["invertIQ"] = false;
defConfig["streams"]["Radio"]["muted"] = false;
defConfig["streams"]["Radio"]["sink"] = "Audio";
@ -238,26 +236,66 @@ int sdrpp_main(int argc, char *argv[]) {
defConfig["vfoColors"]["Radio"] = "#FFFFFF";
#ifdef _WIN32
#ifdef __ANDROID__
defConfig["lockMenuOrder"] = true;
#else
defConfig["lockMenuOrder"] = false;
#endif
#if defined(_WIN32)
defConfig["modulesDirectory"] = "./modules";
defConfig["resourcesDirectory"] = "./res";
#elif defined(IS_MACOS_BUNDLE)
defConfig["modulesDirectory"] = "../Plugins";
defConfig["resourcesDirectory"] = "../Resources";
#elif defined(__ANDROID__)
defConfig["modulesDirectory"] = root + "/modules";
defConfig["resourcesDirectory"] = root + "/res";
#else
defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins";
defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp";
#endif
// Load config
spdlog::info("Loading config");
core::configManager.setPath(options::opts.root + "/config.json");
flog::info("Loading config");
core::configManager.setPath(root + "/config.json");
core::configManager.load(defConfig);
core::configManager.enableAutoSave();
core::configManager.acquire();
// Android can't load just any .so file. This means we have to hardcode the name of the modules
#ifdef __ANDROID__
int modCount = 0;
core::configManager.conf["modules"] = json::array();
core::configManager.conf["modules"][modCount++] = "airspy_source.so";
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
core::configManager.conf["modules"][modCount++] = "hermes_source.so";
core::configManager.conf["modules"][modCount++] = "plutosdr_source.so";
core::configManager.conf["modules"][modCount++] = "rfspace_source.so";
core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so";
core::configManager.conf["modules"][modCount++] = "rtl_tcp_source.so";
core::configManager.conf["modules"][modCount++] = "sdrpp_server_source.so";
core::configManager.conf["modules"][modCount++] = "spyserver_source.so";
core::configManager.conf["modules"][modCount++] = "network_sink.so";
core::configManager.conf["modules"][modCount++] = "audio_sink.so";
core::configManager.conf["modules"][modCount++] = "m17_decoder.so";
core::configManager.conf["modules"][modCount++] = "meteor_demodulator.so";
core::configManager.conf["modules"][modCount++] = "radio.so";
core::configManager.conf["modules"][modCount++] = "frequency_manager.so";
core::configManager.conf["modules"][modCount++] = "recorder.so";
core::configManager.conf["modules"][modCount++] = "rigctl_server.so";
core::configManager.conf["modules"][modCount++] = "scanner.so";
#endif
// Fix missing elements in config
for (auto const& item : defConfig.items()) {
if (!core::configManager.conf.contains(item.key())) {
spdlog::info("Missing key in config {0}, repairing", item.key());
flog::info("Missing key in config {0}, repairing", item.key());
core::configManager.conf[item.key()] = defConfig[item.key()];
}
}
@ -266,7 +304,7 @@ int sdrpp_main(int argc, char *argv[]) {
auto items = core::configManager.conf.items();
for (auto const& item : items) {
if (!defConfig.contains(item.key())) {
spdlog::info("Unused key in config {0}, repairing", item.key());
flog::info("Unused key in config {0}, repairing", item.key());
core::configManager.conf.erase(item.key());
}
}
@ -281,242 +319,71 @@ int sdrpp_main(int argc, char *argv[]) {
core::configManager.conf["moduleInstances"][_name] = newMod;
}
// Load UI scaling
style::uiScale = core::configManager.conf["uiScale"];
core::configManager.release(true);
if (options::opts.serverMode) { return server_main(); }
if (serverMode) { return server::main(); }
core::configManager.acquire();
int winWidth = core::configManager.conf["windowSize"]["w"];
int winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
std::string resDir = core::configManager.conf["resourcesDirectory"];
json bandColors = core::configManager.conf["bandColors"];
core::configManager.release();
// Assert that the resource directory is absolute and check existence
resDir = std::filesystem::absolute(resDir).string();
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)");
flog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)");
return 1;
}
// Initialize backend
int biRes = backend::init(resDir);
if (biRes < 0) { return biRes; }
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
return 1;
}
#ifdef __APPLE__
// GL 3.2 + GLSL 150
const char* glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
// Create window with graphics context
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (core::window == NULL)
return 1;
glfwMakeContextCurrent(core::window);
#else
const char* glsl_version = "#version 120";
GLFWmonitor* monitor = NULL;
for (int i = 0; i < OPENGL_VERSION_COUNT; i++) {
glsl_version = OPENGL_VERSIONS_GLSL[i];
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
// Create window with graphics context
monitor = glfwGetPrimaryMonitor();
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (core::window == NULL) {
spdlog::info("OpenGL {0}.{1} {2}was not supported", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? "ES ": "");
continue;
}
spdlog::info("Using OpenGL {0}.{1}{2}", OPENGL_VERSIONS_MAJOR[i], OPENGL_VERSIONS_MINOR[i], OPENGL_VERSIONS_IS_ES[i] ? " ES": "");
glfwMakeContextCurrent(core::window);
break;
}
#endif
// Add callback for max/min if GLFW supports it
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(core::window);
}
glfwSetWindowMaximizeCallback(core::window, maximized_callback);
#endif
// Load app icon
if (!std::filesystem::is_regular_file(resDir + "/icons/sdrpp.png")) {
spdlog::error("Icon file '{0}' doesn't exist!", resDir + "/icons/sdrpp.png");
return 1;
}
GLFWimage icons[10];
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(core::window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
}
if (glewInit() != GLEW_OK) {
spdlog::error("Failed to initialize OpenGL loader!");
return 1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(core::window, true);
if (!ImGui_ImplOpenGL3_Init(glsl_version)) {
// If init fail, try to fall back on GLSL 1.2
spdlog::warn("Could not init using OpenGL with normal GLSL version, falling back to GLSL 1.2");
if (!ImGui_ImplOpenGL3_Init("#version 120")) {
spdlog::error("Failed to initialize OpenGL with GLSL 1.2");
return -1;
}
}
// Initialize SmGui in normal mode
SmGui::init(false);
if (!style::loadFonts(resDir)) { return -1; }
thememenu::init(resDir);
LoadingScreen::setWindow(core::window);
LoadingScreen::init();
LoadingScreen::show("Loading icons");
spdlog::info("Loading icons");
flog::info("Loading icons");
if (!icons::load(resDir)) { return -1; }
LoadingScreen::show("Loading band plans");
spdlog::info("Loading band plans");
flog::info("Loading band plans");
bandplan::loadFromDir(resDir + "/bandplans");
LoadingScreen::show("Loading band plan colors");
spdlog::info("Loading band plans color table");
flog::info("Loading band plans color table");
bandplan::loadColorTable(bandColors);
gui::mainWindow.init();
spdlog::info("Ready.");
flog::info("Ready.");
bool _maximized = maximized;
int fsWidth, fsHeight, fsPosX, fsPosY;
// Main loop
while (!glfwWindowShouldClose(core::window)) {
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow();
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.acquire();
core::configManager.conf["maximized"]= _maximized;
if (!maximized) {
glfwSetWindowSize(core::window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
int _winWidth, _winHeight;
glfwGetWindowSize(core::window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
if (fullScreen) {
spdlog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(core::window, &fsPosX, &fsPosY);
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(core::window, monitor, 0, 0, mode->width, mode->height, 0);
}
else {
spdlog::info("Fullscreen: OFF");
glfwSetWindowMonitor(core::window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
}
}
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
winWidth = _winWidth;
winHeight = _winHeight;
core::configManager.acquire();
core::configManager.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
gui::mainWindow.draw();
}
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(core::window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
//glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapInterval(1); // Enable vsync
glfwSwapBuffers(core::window);
}
// Run render loop (TODO: CHECK RETURN VALUE)
backend::renderLoop();
// On android, none of this shutdown should happen due to the way the UI works
#ifndef __ANDROID__
// Shut down all modules
for (auto& [name, mod] : core::moduleManager.modules) {
mod.end();
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
// Terminate backend (TODO: CHECK RETURN VALUE)
backend::end();
glfwDestroyWindow(core::window);
glfwTerminate();
sigpath::signalPath.stop();
sigpath::iqFrontEnd.stop();
core::configManager.disableAutoSave();
core::configManager.save();
#endif
flog::info("Exiting successfully");
return 0;
}

View File

@ -1,15 +1,16 @@
#pragma once
#include <config.h>
#include <module.h>
#include <module.h>
#include <module_com.h>
#include "command_args.h"
namespace core {
SDRPP_EXPORT ConfigManager configManager;
SDRPP_EXPORT ModuleManager moduleManager;
SDRPP_EXPORT ModuleComManager modComManager;
SDRPP_EXPORT CommandArgsParser args;
void setInputSampleRate(double samplerate);
};
int sdrpp_main(int argc, char *argv[]);
int sdrpp_main(int argc, char* argv[]);

View File

@ -10,6 +10,7 @@ namespace sdrpp_credits {
"Cropinghigh",
"Fred F4EED",
"Howard0su",
"John Donkersley",
"Joshua Kimsey",
"Martin Hauke",
"Marvin Sinister",
@ -21,40 +22,62 @@ namespace sdrpp_credits {
"Syne Ardwin (WI9SYN)",
"Szymon Zakrent",
"Tobias Mädel",
"Youssef Touil",
"Zimm"
};
const char* libraries[] = {
"Dear ImGui (ocornut)",
"fftw3 (fftw.org)",
"glew (Nigel Stewart)",
"glfw (Camilla Löwy)",
"json (nlohmann)",
"spdlog (gabime)",
"Portable File Dialogs"
};
const char* hardwareDonators[] = {
"Aaronia AG",
"Airspy",
"Analog Devices",
"CaribouLabs",
"Ettus Research",
"Howard Su",
"MyriadRF",
"Nuand",
"RFspace",
"RTL-SDRblog",
"SDRplay"
};
const char* patrons[] = {
"Bob Logan",
"Christian Häusler",
"Croccydile",
"Dale L Puckett (K0HYD)",
"Daniele D'Agnelli",
"D. Jones",
"EB3FRN",
"Eric Johnson",
"Ernest Murphy (NH7L)",
"Flinger Films",
"gringogrigio",
"Joe Cupano",
"Kezza",
"Krys Kamieniecki",
"Lee Donaghy",
"Lee KD1SQ",
".lozenge. (Hank Hill)",
"ON4MU",
"Passion-Radio.com",
"Paul Maine",
"Scanner School",
"SignalsEverywhere",
"Syne Ardwin (WI9SYN)",
"W4IPA"
"W4IPA",
"Zipper"
};
const int contributorCount = sizeof(contributors) / sizeof(char*);
const int libraryCount = sizeof(libraries) / sizeof(char*);
const int hardwareDonatorCount = sizeof(hardwareDonators) / sizeof(char*);
const int patronCount = sizeof(patrons) / sizeof(char*);
}

View File

@ -4,8 +4,10 @@
namespace sdrpp_credits {
SDRPP_EXPORT const char* contributors[];
SDRPP_EXPORT const char* libraries[];
SDRPP_EXPORT const char* hardwareDonators[];
SDRPP_EXPORT const char* patrons[];
SDRPP_EXPORT const int contributorCount;
SDRPP_EXPORT const int libraryCount;
SDRPP_EXPORT const int hardwareDonatorCount;
SDRPP_EXPORT const int patronCount;
}

View File

@ -1,202 +0,0 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class MonoToStereo : public generic_block<MonoToStereo> {
public:
MonoToStereo() {}
MonoToStereo(stream<float>* in) { init(in); }
void init(stream<float>* in) {
_in = in;
generic_block<MonoToStereo>::registerInput(_in);
generic_block<MonoToStereo>::registerOutput(&out);
generic_block<MonoToStereo>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<MonoToStereo>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx);
generic_block<MonoToStereo>::tempStop();
generic_block<MonoToStereo>::unregisterInput(_in);
_in = in;
generic_block<MonoToStereo>::registerInput(_in);
generic_block<MonoToStereo>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, _in->readBuf, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
stream<float>* _in;
};
class ChannelsToStereo : public generic_block<ChannelsToStereo> {
public:
ChannelsToStereo() {}
ChannelsToStereo(stream<float>* in_left, stream<float>* in_right) { init(in_left, in_right); }
void init(stream<float>* in_left, stream<float>* in_right) {
_in_left = in_left;
_in_right = in_right;
nullbuf = new float[STREAM_BUFFER_SIZE];
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) { nullbuf[i] = 0; }
generic_block<ChannelsToStereo>::registerInput(_in_left);
generic_block<ChannelsToStereo>::registerInput(_in_right);
generic_block<ChannelsToStereo>::registerOutput(&out);
generic_block<ChannelsToStereo>::_block_init = true;
}
void setInput(stream<float>* in_left, stream<float>* in_right) {
assert(generic_block<ChannelsToStereo>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ChannelsToStereo>::ctrlMtx);
generic_block<ChannelsToStereo>::tempStop();
generic_block<ChannelsToStereo>::unregisterInput(_in_left);
generic_block<ChannelsToStereo>::unregisterInput(_in_right);
_in_left = in_left;
_in_right = in_right;
generic_block<ChannelsToStereo>::registerInput(_in_left);
generic_block<ChannelsToStereo>::registerInput(_in_right);
generic_block<ChannelsToStereo>::tempStart();
}
int run() {
int count_l = _in_left->read();
if (count_l < 0) { return -1; }
int count_r = _in_right->read();
if (count_r < 0) { return -1; }
if (count_l != count_r) {
spdlog::warn("ChannelsToStereo block size mismatch");
}
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in_left->readBuf, _in_right->readBuf, count_l);
_in_left->flush();
_in_right->flush();
if (!out.swap(count_l)) { return -1; }
return count_l;
}
stream<stereo_t> out;
private:
stream<float>* _in_left;
stream<float>* _in_right;
float* nullbuf;
};
class StereoToMono : public generic_block<StereoToMono> {
public:
StereoToMono() {}
StereoToMono(stream<stereo_t>* in) { init(in); }
~StereoToMono() {
if (!generic_block<StereoToMono>::_block_init) { return; }
generic_block<StereoToMono>::stop();
delete[] l_buf;
delete[] r_buf;
generic_block<StereoToMono>::_block_init = false;
}
void init(stream<stereo_t>* in) {
_in = in;
l_buf = new float[STREAM_BUFFER_SIZE];
r_buf = new float[STREAM_BUFFER_SIZE];
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::registerOutput(&out);
generic_block<StereoToMono>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
assert(generic_block<StereoToMono>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx);
generic_block<StereoToMono>::tempStop();
generic_block<StereoToMono>::unregisterInput(_in);
_in = in;
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < count; i++) {
out.writeBuf[i] = (_in->readBuf[i].l + _in->readBuf[i].r) * 0.5f;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float* l_buf, *r_buf;
stream<stereo_t>* _in;
};
class StereoToChannels : public generic_block<StereoToChannels> {
public:
StereoToChannels() {}
StereoToChannels(stream<stereo_t>* in) { init(in); }
void init(stream<stereo_t>* in) {
_in = in;
generic_block<StereoToChannels>::registerInput(_in);
generic_block<StereoToChannels>::registerOutput(&out_left);
generic_block<StereoToChannels>::registerOutput(&out_right);
generic_block<StereoToChannels>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
assert(generic_block<StereoToChannels>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<StereoToChannels>::ctrlMtx);
generic_block<StereoToChannels>::tempStop();
generic_block<StereoToChannels>::unregisterInput(_in);
_in = in;
generic_block<StereoToChannels>::registerInput(_in);
generic_block<StereoToChannels>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_32f_x2(out_left.writeBuf, out_right.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if (!out_left.swap(count)) { return -1; }
if (!out_right.swap(count)) { return -1; }
return count;
}
stream<float> out_left;
stream<float> out_right;
private:
stream<stereo_t>* _in;
};
}

View File

@ -0,0 +1,58 @@
#pragma once
#include "../processor.h"
// TODO: This block is useless and weird, get rid of it
namespace dsp::audio {
class Volume : public Processor<stereo_t, stereo_t> {
using base_type = Processor<stereo_t, stereo_t>;
public:
Volume() {}
Volume(stream<stereo_t>* in, double volume, bool muted) { init(in, volume, muted); }
void init(stream<stereo_t>* in, double volume, bool muted) {
_volume = powf(volume, 2);
_muted = muted;
base_type::init(in);
}
void setVolume(double volume) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_volume = powf(volume, 2);
}
void setMuted(bool muted) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muted = muted;
}
bool getMuted() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
return _muted;
}
inline int process(int count, const stereo_t* in, stereo_t* out) {
volk_32f_s32f_multiply_32f((float*)out, (float*)in, _muted ? 0.0f : _volume, count * 2);
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
float _volume;
bool _muted;
};
}

View File

@ -0,0 +1,72 @@
#pragma once
#include "../sink.h"
namespace dsp::bench {
template<class T>
class PeakLevelMeter : public Sink<T> {
using base_type = Sink<T>;
public:
PeakLevelMeter() {}
PeakLevelMeter(stream<T>* in) { init(in); }
void init(stream<T>* in) {
if constexpr (std::is_same_v<T, float>) {
level = 0.0f;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
level = { 0.0f, 0.0f };
}
base_type::init(in);
}
T getLevel() {
return level;
}
void resetLevel() {
assert(base_type::_block_init);
if constexpr (std::is_same_v<T, float>) {
level = 0.0f;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
level = { 0.0f, 0.0f };
}
}
int process(int count, T* in) {
for (int i = 0; i < count; i++) {
if constexpr (std::is_same_v<T, float>) {
float lvl = fabsf(in[i]);
if (lvl > level) { level = lvl; }
}
if constexpr (std::is_same_v<T, complex_t>) {
float lvlre = fabsf(in[i].re);
float lvlim = fabsf(in[i].im);
if (lvlre > level.re) { level.re = lvlre; }
if (lvlim > level.im) { level.im = lvlim; }
}
if constexpr (std::is_same_v<T, stereo_t>) {
float lvll = fabsf(in[i].l);
float lvlr = fabsf(in[i].r);
if (lvll > level.l) { level.l = lvll; }
if (lvlr > level.r) { level.r = lvlr; }
}
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf);
base_type::_in->flush();
return count;
}
protected:
T level;
};
}

View File

@ -0,0 +1,105 @@
#pragma once
#include <thread>
#include <assert.h>
#include "../stream.h"
#include "../types.h"
namespace dsp::bench {
template<class I, class O>
class SpeedTester {
public:
SpeedTester() {}
SpeedTester(stream<I>* in, stream<O>* out) { init(in, out); }
void init(stream<I>* in, stream<O>* out) {
_in = in;
_out = out;
_init = true;
}
void setInput(stream<I>* in) {
assert(_init);
_in = in;
}
void setOutput(stream<O>* out) {
assert(_init);
_out = out;
}
double benchmark(int durationMs, int bufferSize) {
assert(_init);
// Allocate and fill buffer
inCount = bufferSize;
randBuf = buffer::alloc<I>(inCount);
for (int i = 0; i < inCount; i++) {
if constexpr (std::is_same_v<I, complex_t>) {
randBuf[i].re = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
randBuf[i].im = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
}
else if constexpr (std::is_same_v<I, float>) {
randBuf[i] = (2.0f * (float)rand() / (float)RAND_MAX) - 1.0f;
}
else {
randBuf[i] = rand();
}
}
// Run test
start();
std::this_thread::sleep_for(std::chrono::milliseconds(durationMs));
stop();
buffer::free(randBuf);
return (double)sampCount * 1000.0 / (double)durationMs;
}
protected:
void start() {
if (running) { return; }
running = true;
sampCount = 0;
wthr = std::thread(&SpeedTester::writeWorker, this);
rthr = std::thread(&SpeedTester::readWorker, this);
}
void stop() {
if (!running) { return; }
running = false;
_in->stopWriter();
_out->stopReader();
if (wthr.joinable()) { wthr.join(); }
if (rthr.joinable()) { rthr.join(); }
_in->clearWriteStop();
_out->clearReadStop();
}
void writeWorker() {
while (true) {
memcpy(_in->writeBuf, randBuf, inCount * sizeof(I));
if (!_in->swap(inCount)) { return; }
sampCount += inCount;
}
}
void readWorker() {
while (true) {
int count = _out->read();
_out->flush();
if (count < 0) { return; }
}
}
bool _init = false;
bool running = false;
int inCount;
stream<I>* _in;
stream<O>* _out;
I* randBuf;
std::thread wthr;
std::thread rthr;
uint64_t sampCount;
};
}

View File

@ -1,29 +1,24 @@
#pragma once
#include <stdio.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <assert.h>
#include <thread>
#include <vector>
#include <algorithm>
#include <spdlog/spdlog.h>
#include "stream.h"
#include "types.h"
namespace dsp {
class generic_unnamed_block {
class generic_block {
public:
virtual 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 {
class block : public generic_block {
public:
virtual void init() {}
virtual ~generic_block() {
virtual ~block() {
if (!_block_init) { return; }
stop();
_block_init = false;
@ -31,7 +26,7 @@ namespace dsp {
virtual void start() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
if (running) {
return;
}
@ -41,7 +36,7 @@ namespace dsp {
virtual void stop() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
if (!running) {
return;
}
@ -51,6 +46,7 @@ namespace dsp {
void tempStart() {
assert(_block_init);
if (!tempStopDepth || --tempStopDepth) { return; }
if (tempStopped) {
doStart();
tempStopped = false;
@ -59,26 +55,45 @@ namespace dsp {
void tempStop() {
assert(_block_init);
if (tempStopDepth++) { return; }
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
virtual int calcOutSize(int inSize) {
assert(_block_init);
return inSize;
}
virtual int run() = 0;
friend BLOCK;
private:
void workerLoop() {
while (run() >= 0);
protected:
void workerLoop() {
while (run() >= 0) {}
}
virtual void doStart() {
workerThread = std::thread(&block::workerLoop, this);
}
virtual void doStop() {
for (auto& in : inputs) {
in->stopReader();
}
for (auto& out : outputs) {
out->stopWriter();
}
// TODO: Make sure this isn't needed, I don't know why it stops
if (workerThread.joinable()) {
workerThread.join();
}
for (auto& in : inputs) {
in->clearReadStop();
}
for (auto& out : outputs) {
out->clearWriteStop();
}
}
void acquire() {
ctrlMtx.lock();
}
@ -103,127 +118,16 @@ namespace dsp {
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
}
virtual void doStart() {
workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this);
}
virtual void doStop() {
for (auto& in : inputs) {
in->stopReader();
}
for (auto& out : outputs) {
out->stopWriter();
}
// TODO: Make sure this isn't needed, I don't know why it stops
if (workerThread.joinable()) {
workerThread.join();
}
for (auto& in : inputs) {
in->clearReadStop();
}
for (auto& out : outputs) {
out->clearWriteStop();
}
}
protected:
bool _block_init = false;
std::mutex ctrlMtx;
std::recursive_mutex ctrlMtx;
std::vector<untyped_stream*> inputs;
std::vector<untyped_stream*> outputs;
bool running = false;
bool tempStopped = false;
int tempStopDepth = 0;
std::thread workerThread;
};
template <class BLOCK>
class generic_hier_block {
public:
virtual void init() {}
virtual ~generic_hier_block() {
if (!_block_init) { return; }
stop();
_block_init = false;
}
virtual void start() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (running) {
return;
}
running = true;
doStart();
}
virtual void stop() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (!running) {
return;
}
doStop();
running = false;
}
void tempStart() {
assert(_block_init);
if (tempStopped) {
doStart();
tempStopped = false;
}
}
void tempStop() {
assert(_block_init);
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
virtual int calcOutSize(int inSize) {
assert(_block_init);
return inSize;
}
friend BLOCK;
private:
void registerBlock(generic_unnamed_block* block) {
blocks.push_back(block);
}
void unregisterBlock(generic_unnamed_block* block) {
blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
}
virtual void doStart() {
for (auto& block : blocks) {
block->start();
}
}
virtual void doStop() {
for (auto& block : blocks) {
block->stop();
}
}
std::vector<generic_unnamed_block*> blocks;
bool tempStopped = false;
bool running = false;
protected:
bool _block_init = false;
std::mutex ctrlMtx;
};
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <volk/volk.h>
namespace dsp::buffer {
template<class T>
inline T* alloc(int count) {
return (T*)volk_malloc(count * sizeof(T), volk_get_alignment());
}
template<class T>
inline void clear(T* buffer, int count, int offset = 0) {
memset(&buffer[offset], 0, count * sizeof(T));
}
inline void free(void* buffer) {
volk_free(buffer);
}
}

View File

@ -0,0 +1,136 @@
#pragma once
#include "../block.h"
#define TEST_BUFFER_SIZE 32
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
namespace dsp::buffer {
template <class T>
class SampleFrameBuffer : public block {
using base_type = block;
public:
SampleFrameBuffer() {}
SampleFrameBuffer(stream<T>* in) { init(in); }
~SampleFrameBuffer() {
if (!base_type::_block_init) { return; }
base_type::stop();
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
buffer::free(buffers[i]);
}
}
void init(stream<T>* in) {
_in = in;
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
buffers[i] = buffer::alloc<T>(STREAM_BUFFER_SIZE);
}
base_type::registerInput(in);
base_type::registerOutput(&out);
base_type::_block_init = true;
}
void setInput(stream<T>* in) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::unregisterInput(_in);
_in = in;
base_type::registerInput(_in);
base_type::tempStart();
}
void flush() {
std::unique_lock lck(bufMtx);
readCur = writeCur;
}
int run() {
// Wait for data
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(T));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
// Push it on the ring buffer
{
std::lock_guard<std::mutex> lck(bufMtx);
memcpy(buffers[writeCur], _in->readBuf, count * sizeof(T));
sizes[writeCur] = count;
writeCur++;
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
// flog::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() {
base_type::workerThread = std::thread(&SampleFrameBuffer<T>::workerLoop, this);
readWorkerThread = std::thread(&SampleFrameBuffer<T>::worker, this);
}
void doStop() {
_in->stopReader();
out.stopWriter();
stopWorker = true;
cnd.notify_all();
if (base_type::workerThread.joinable()) { base_type::workerThread.join(); }
if (readWorkerThread.joinable()) { readWorkerThread.join(); }
_in->clearReadStop();
out.clearWriteStop();
stopWorker = false;
}
stream<T>* _in;
std::thread readWorkerThread;
std::mutex bufMtx;
std::condition_variable cnd;
T* buffers[TEST_BUFFER_SIZE];
int sizes[TEST_BUFFER_SIZE];
bool stopWorker = false;
};
}

View File

@ -0,0 +1,69 @@
#include "../block.h"
namespace dsp::buffer {
template <class T>
class Packer : public block {
public:
Packer() {}
Packer(stream<T>* in, int count) { init(in, count); }
void init(stream<T>* in, int count) {
_in = in;
samples = count;
block::registerInput(_in);
block::registerOutput(&out);
block::_block_init = true;
}
void setInput(stream<T>* in) {
assert(block::_block_init);
std::lock_guard<std::recursive_mutex> lck(block::ctrlMtx);
block::tempStop();
block::unregisterInput(_in);
_in = in;
block::registerInput(_in);
block::tempStart();
}
void setSampleCount(int count) {
assert(block::_block_init);
std::lock_guard<std::recursive_mutex> lck(block::ctrlMtx);
block::tempStop();
samples = count;
block::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) {
read = 0;
return -1;
}
for (int i = 0; i < count; i++) {
out.writeBuf[read++] = _in->readBuf[i];
if (read >= samples) {
read = 0;
if (!out.swap(samples)) {
_in->flush();
read = 0;
return -1;
}
}
}
_in->flush();
return count;
}
stream<T> out;
private:
int samples = 1;
int read = 0;
stream<T>* _in;
};
}

View File

@ -0,0 +1,137 @@
#pragma once
#include "../block.h"
#include "ring_buffer.h"
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
namespace dsp::buffer {
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
template <class T>
class Reshaper : public block {
using base_type = block;
public:
Reshaper() {}
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
// NOTE: For some reason, the base class destructor doesn't get called.... this is a temporary fix I guess
// I also don't check for _block_init for the exact sample reason, something's weird
~Reshaper() {
if (!base_type::_block_init) { return; }
base_type::stop();
}
void init(stream<T>* in, int keep, int skip) {
_in = in;
_keep = keep;
_skip = skip;
ringBuf.init(keep * 2);
base_type::registerInput(_in);
base_type::registerOutput(&out);
base_type::_block_init = true;
}
void setInput(stream<T>* in) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::unregisterInput(_in);
_in = in;
base_type::registerInput(_in);
base_type::tempStart();
}
void setKeep(int keep) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_keep = keep;
ringBuf.setMaxLatency(keep * 2);
base_type::tempStart();
}
void setSkip(int skip) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_skip = skip;
base_type::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
ringBuf.write(_in->readBuf, count);
_in->flush();
return count;
}
stream<T> out;
private:
void doStart() override {
workThread = std::thread(&Reshaper<T>::loop, this);
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
}
void loop() {
while (run() >= 0)
;
}
void doStop() override {
_in->stopReader();
ringBuf.stopReader();
out.stopWriter();
ringBuf.stopWriter();
if (workThread.joinable()) {
workThread.join();
}
if (bufferWorkerThread.joinable()) {
bufferWorkerThread.join();
}
_in->clearReadStop();
ringBuf.clearReadStop();
out.clearWriteStop();
ringBuf.clearWriteStop();
}
void bufferWorker() {
T* buf = new T[_keep];
bool delay = _skip < 0;
int readCount = std::min<int>(_keep + _skip, _keep);
int skip = std::max<int>(_skip, 0);
int delaySize = (-_skip) * sizeof(T);
int delayCount = (-_skip);
T* start = &buf[std::max<int>(-_skip, 0)];
T* delayStart = &buf[_keep + _skip];
while (true) {
if (delay) {
memmove(buf, delayStart, delaySize);
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
for (int i = 0; i < delayCount; i++) {
buf[i].re /= 10.0f;
buf[i].im /= 10.0f;
}
}
}
if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
memcpy(out.writeBuf, buf, _keep * sizeof(T));
if (!out.swap(_keep)) { break; }
}
delete[] buf;
}
stream<T>* _in;
int _outBlockSize;
RingBuffer<T> ringBuf;
std::thread bufferWorkerThread;
std::thread workThread;
int _keep, _skip;
};
}

View File

@ -1,10 +1,11 @@
#pragma once
#include <dsp/block.h>
#include <string.h>
#include "buffer.h"
#define RING_BUF_SZ 1000000
namespace dsp {
// IMPORTANT: THIS IS TRASH AND MUST BE REWRITTEN IN THE FUTURE
namespace dsp::buffer {
template <class T>
class RingBuffer {
public:
@ -14,13 +15,12 @@ namespace dsp {
~RingBuffer() {
if (!_init) { return; }
delete _buffer;
buffer::free(_buffer);
_init = false;
}
void init(int maxLatency) {
size = RING_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
@ -28,7 +28,8 @@ namespace dsp {
readc = 0;
readable = 0;
writable = size;
memset(_buffer, 0, size * sizeof(T));
_buffer = buffer::alloc<T>(size);
buffer::clear(_buffer, size);
_init = true;
}
@ -47,7 +48,7 @@ namespace dsp {
else {
memcpy(&data[dataRead], &_buffer[readc], toRead * sizeof(T));
}
dataRead += toRead;
_readable_mtx.lock();
@ -114,7 +115,7 @@ namespace dsp {
int _r = getReadable();
if (_r != 0) { return _r; }
std::unique_lock<std::mutex> lck(_readable_mtx);
canReadVar.wait(lck, [=](){ return ((this->getReadable(false) > 0) || this->getReadStop()); });
canReadVar.wait(lck, [=]() { return ((this->getReadable(false) > 0) || this->getReadStop()); });
if (_stopReader) { return -1; }
return getReadable(false);
}
@ -152,7 +153,7 @@ namespace dsp {
writable -= toWrite;
_writable_mtx.unlock();
writec = (writec + toWrite) % size;
canReadVar.notify_one();
}
return len;
@ -164,7 +165,7 @@ namespace dsp {
int _w = getWritable();
if (_w != 0) { return _w; }
std::unique_lock<std::mutex> lck(_writable_mtx);
canWriteVar.wait(lck, [=](){ return ((this->getWritable(false) > 0) || this->getWriteStop()); });
canWriteVar.wait(lck, [=]() { return ((this->getWritable(false) > 0) || this->getWriteStop()); });
if (_stopWriter) { return -1; }
return getWritable(false);
}
@ -173,7 +174,10 @@ namespace dsp {
assert(_init);
if (lock) { _writable_mtx.lock(); };
int _w = writable;
if (lock) { _writable_mtx.unlock(); _readable_mtx.lock(); };
if (lock) {
_writable_mtx.unlock();
_readable_mtx.lock();
};
int _r = readable;
if (lock) { _readable_mtx.unlock(); };
return std::max<int>(std::min<int>(_w, maxLatency - _r), 0);
@ -232,128 +236,4 @@ namespace dsp {
std::condition_variable canReadVar;
std::condition_variable canWriteVar;
};
#define TEST_BUFFER_SIZE 32
template <class T>
class SampleFrameBuffer : public generic_block<SampleFrameBuffer<T>> {
public:
SampleFrameBuffer() {}
SampleFrameBuffer(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
buffers[i] = new T[STREAM_BUFFER_SIZE];
}
generic_block<SampleFrameBuffer<T>>::registerInput(in);
generic_block<SampleFrameBuffer<T>>::registerOutput(&out);
generic_block<SampleFrameBuffer<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<SampleFrameBuffer<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<SampleFrameBuffer<T>>::ctrlMtx);
generic_block<SampleFrameBuffer<T>>::tempStop();
generic_block<SampleFrameBuffer<T>>::unregisterInput(_in);
_in = in;
generic_block<SampleFrameBuffer<T>>::registerInput(_in);
generic_block<SampleFrameBuffer<T>>::tempStart();
}
void flush() {
std::unique_lock lck(bufMtx);
readCur = writeCur;
}
int run() {
// Wait for data
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(T));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
// Push it on the ring buffer
{
std::lock_guard<std::mutex> lck(bufMtx);
memcpy(buffers[writeCur], _in->readBuf, count * sizeof(T));
uintptr_t ptr = (uintptr_t)buffers[writeCur];
sizes[writeCur] = count;
writeCur++;
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
// spdlog::warn("Overflow");
// }
}
cnd.notify_all();
_in->flush();
return count;
}
void worker() {
while (true) {
// Wait for data
std::unique_lock lck(bufMtx);
cnd.wait(lck, [this](){ return (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) > 0) || stopWorker; });
if (stopWorker) { break; }
// Write one to output buffer and unlock in preparation to swap buffers
int count = sizes[readCur];
memcpy(out.writeBuf, buffers[readCur], count * sizeof(T));
readCur++;
readCur = ((readCur) % TEST_BUFFER_SIZE);
lck.unlock();
// Swap
if (!out.swap(count)) { break; }
}
}
stream<T> out;
int writeCur = 0;
int readCur = 0;
bool bypass = false;
private:
void doStart() {
generic_block<SampleFrameBuffer<T>>::workerThread = std::thread(&generic_block<SampleFrameBuffer<T>>::workerLoop, this);
readWorkerThread = std::thread(&SampleFrameBuffer<T>::worker, this);
}
void doStop() {
_in->stopReader();
out.stopWriter();
stopWorker = true;
cnd.notify_all();
if (generic_block<SampleFrameBuffer<T>>::workerThread.joinable()) { generic_block<SampleFrameBuffer<T>>::workerThread.join(); }
if (readWorkerThread.joinable()) { readWorkerThread.join(); }
_in->clearReadStop();
out.clearWriteStop();
stopWorker = false;
}
stream<T>* _in;
std::thread readWorkerThread;
std::mutex bufMtx;
std::condition_variable cnd;
T* buffers[TEST_BUFFER_SIZE];
int sizes[TEST_BUFFER_SIZE];
bool stopWorker = false;
};
};
}

193
core/src/dsp/chain.h Normal file
View File

@ -0,0 +1,193 @@
#pragma once
#include <vector>
#include <map>
#include "processor.h"
namespace dsp {
template<class T>
class chain {
public:
chain() {}
chain(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
out = _in;
}
template<typename Func>
void setInput(stream<T>* in, Func onOutputChange) {
_in = in;
for (auto& ln : links) {
if (states[ln]) {
ln->setInput(_in);
return;
}
}
out = _in;
onOutputChange(out);
}
void addBlock(Processor<T, T>* block, bool enabled) {
// Check if block is already part of the chain
if (blockExists(block)) {
throw std::runtime_error("[chain] Tried to add a block that is already part of the chain");
}
// Add to the list
links.push_back(block);
states[block] = false;
// Enable if needed
if (enabled) { enableBlock(block, [](stream<T>* out){}); }
}
template<typename Func>
void removeBlock(Processor<T, T>* block, Func onOutputChange) {
// Check if block is part of the chain
if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to remove a block that is not part of the chain");
}
// Disable the block
disableBlock(block, onOutputChange);
// Remove block from the list
states.erase(block);
links.erase(std::find(links.begin(), links.end(), block));
}
template<typename Func>
void enableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain
if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
}
// If already enable, don't do anything
if (states[block]) { return; }
// Gather blocks before and after the block to enable
Processor<T, T>* before = blockBefore(block);
Processor<T, T>* after = blockAfter(block);
// Update input of next block or output
if (after) {
after->setInput(&block->out);
}
else {
out = &block->out;
onOutputChange(out);
}
// Set input of the new block
block->setInput(before ? &before->out : _in);
// Start new block
if (running) { block->start(); }
states[block] = true;
}
template<typename Func>
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain
if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
}
// If already disabled, don't do anything
if (!states[block]) { return; }
// Stop disabled block
block->stop();
states[block] = false;
// Gather blocks before and after the block to disable
Processor<T, T>* before = blockBefore(block);
Processor<T, T>* after = blockAfter(block);
// Update input of next block or output
if (after) {
after->setInput(before ? &before->out : _in);
}
else {
out = before ? &before->out : _in;
onOutputChange(out);
}
}
template<typename Func>
void setBlockEnabled(Processor<T, T>* block, bool enabled, Func onOutputChange) {
if (enabled) {
enableBlock(block, onOutputChange);
}
else {
disableBlock(block, onOutputChange);
}
}
template<typename Func>
void enableAllBlocks(Func onOutputChange) {
for (auto& ln : links) {
enableBlock(ln, onOutputChange);
}
}
template<typename Func>
void disableAllBlocks(Func onOutputChange) {
for (auto& ln : links) {
disableBlock(ln, onOutputChange);
}
}
void start() {
if (running) { return; }
for (auto& ln : links) {
if (!states[ln]) { continue; }
ln->start();
}
running = true;
}
void stop() {
if (!running) { return; }
for (auto& ln : links) {
if (!states[ln]) { continue; }
ln->stop();
}
running = false;
}
stream<T>* out;
private:
Processor<T, T>* blockBefore(Processor<T, T>* block) {
for (auto& ln : links) {
if (ln == block) { return NULL; }
if (states[ln]) { return ln; }
}
}
Processor<T, T>* blockAfter(Processor<T, T>* block) {
bool blockFound = false;
for (auto& ln : links) {
if (ln == block) {
blockFound = true;
continue;
}
if (states[ln] && blockFound) { return ln; }
}
return NULL;
}
bool blockExists(Processor<T, T>* block) {
return states.find(block) != states.end();
}
stream<T>* _in;
std::vector<Processor<T, T>*> links;
std::map<Processor<T, T>*, bool> states;
bool running = false;
};
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "../processor.h"
#include "../math/hz_to_rads.h"
namespace dsp::channel {
class FrequencyXlator : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>;
public:
FrequencyXlator() {}
FrequencyXlator(stream<complex_t>* in, double offset) { init(in, offset); }
FrequencyXlator(stream<complex_t>* in, double offset, double samplerate) { init(in, offset, samplerate); }
void init(stream<complex_t>* in, double offset) {
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(cos(offset), sin(offset));
base_type::init(in);
}
void init(stream<complex_t>* in, double offset, double samplerate) {
init(in, math::hzToRads(offset, samplerate));
}
void setOffset(double offset) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
phaseDelta = lv_cmake(cos(offset), sin(offset));
}
void setOffset(double offset, double samplerate) {
setOffset(math::hzToRads(offset, samplerate));
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
tempStop();
phase = lv_cmake(1.0f, 0.0f);
tempStart();
}
inline int process(int count, const complex_t* in, complex_t* out) {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
lv_32fc_t phase;
lv_32fc_t phaseDelta;
};
}

View File

@ -0,0 +1,136 @@
#pragma once
#include "frequency_xlator.h"
#include "../multirate/rational_resampler.h"
namespace dsp::channel {
class RxVFO : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>;
public:
RxVFO() {}
RxVFO(stream<complex_t>* in, double inSamplerate, double outSamplerate, double bandwidth, double offset) { init(in, inSamplerate, outSamplerate, bandwidth, offset); }
~RxVFO() {
if (!base_type::_block_init) { return; }
base_type::stop();
taps::free(ftaps);
}
void init(stream<complex_t>* in, double inSamplerate, double outSamplerate, double bandwidth, double offset) {
_inSamplerate = inSamplerate;
_outSamplerate = outSamplerate;
_bandwidth = bandwidth;
_offset = offset;
filterNeeded = (_bandwidth != _outSamplerate);
ftaps.taps = NULL;
xlator.init(NULL, -_offset, _inSamplerate);
resamp.init(NULL, _inSamplerate, _outSamplerate);
generateTaps();
filter.init(NULL, ftaps);
base_type::init(in);
}
void setInSamplerate(double inSamplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_inSamplerate = inSamplerate;
xlator.setOffset(-_offset, _inSamplerate);
resamp.setInSamplerate(_inSamplerate);
base_type::tempStart();
}
void setOutSamplerate(double outSamplerate, double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_outSamplerate = outSamplerate;
_bandwidth = bandwidth;
filterNeeded = (_bandwidth != _outSamplerate);
resamp.setOutSamplerate(_outSamplerate);
if (filterNeeded) {
generateTaps();
filter.setTaps(ftaps);
}
base_type::tempStart();
}
void setBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
std::lock_guard<std::mutex> lck2(filterMtx);
_bandwidth = bandwidth;
filterNeeded = (_bandwidth != _outSamplerate);
if (filterNeeded) {
generateTaps();
filter.setTaps(ftaps);
}
}
void setOffset(double offset) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_offset = offset;
xlator.setOffset(-_offset, _inSamplerate);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
xlator.reset();
resamp.reset();
filter.reset();
base_type::tempStart();
}
inline int process(int count, const complex_t* in, complex_t* out) {
xlator.process(count, in, out);
if (!filterNeeded) {
return resamp.process(count, out, out);
}
count = resamp.process(count, out, out);
{
std::lock_guard<std::mutex> lck(filterMtx);
filter.process(count, out, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
void generateTaps() {
taps::free(ftaps);
double filterWidth = _bandwidth / 2.0;
ftaps = taps::lowPass(filterWidth, filterWidth * 0.1, _outSamplerate);
}
FrequencyXlator xlator;
multirate::RationalResampler<complex_t> resamp;
filter::FIR<complex_t, float> filter;
tap<float> ftaps;
bool filterNeeded;
double _inSamplerate;
double _outSamplerate;
double _bandwidth;
double _offset;
std::mutex filterMtx;
};
}

View File

@ -1,255 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <dsp/utils/macros.h>
#include <dsp/interpolation_taps.h>
namespace dsp {
class EdgeTrigClockRecovery : public generic_block<EdgeTrigClockRecovery> {
public:
EdgeTrigClockRecovery() {}
EdgeTrigClockRecovery(stream<float>* in, int omega) { init(in, omega); }
void init(stream<float>* in, int omega) {
_in = in;
samplesPerSymbol = omega;
generic_block<EdgeTrigClockRecovery>::registerInput(_in);
generic_block<EdgeTrigClockRecovery>::registerOutput(&out);
generic_block<EdgeTrigClockRecovery>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<EdgeTrigClockRecovery>::_block_init);
generic_block<EdgeTrigClockRecovery>::tempStop();
generic_block<EdgeTrigClockRecovery>::unregisterInput(_in);
_in = in;
generic_block<EdgeTrigClockRecovery>::registerInput(_in);
generic_block<EdgeTrigClockRecovery>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
int outCount = 0;
for (int i = 0; i < count; i++) {
if (DSP_SIGN(lastVal) != DSP_SIGN(_in->readBuf[i])) {
counter = samplesPerSymbol / 2;
lastVal = _in->readBuf[i];
continue;
}
if (counter >= samplesPerSymbol) {
counter = 0;
out.writeBuf[outCount] = _in->readBuf[i];
outCount++;
}
else {
counter++;
}
lastVal = _in->readBuf[i];
}
_in->flush();
if (outCount > 0 && !out.swap(outCount)) { return -1; }
return count;
}
stream<float> out;
private:
int count;
int samplesPerSymbol = 1;
int counter = 0;
float lastVal = 0;
stream<float>* _in;
};
template<class T>
class MMClockRecovery : public generic_block<MMClockRecovery<T>> {
public:
MMClockRecovery() {}
MMClockRecovery(stream<T>* in, float omega, float gainOmega, float muGain, float omegaRelLimit) {
init(in, omega, gainOmega, muGain, omegaRelLimit);
}
void init(stream<T>* in, float omega, float gainOmega, float muGain, float omegaRelLimit) {
_in = in;
_omega = omega;
_muGain = muGain;
_gainOmega = gainOmega;
_omegaRelLimit = omegaRelLimit;
omegaMin = _omega - (_omega * _omegaRelLimit);
omegaMax = _omega + (_omega * _omegaRelLimit);
_dynOmega = _omega;
memset(delay, 0, 1024 * sizeof(T));
generic_block<MMClockRecovery<T>>::registerInput(_in);
generic_block<MMClockRecovery<T>>::registerOutput(&out);
generic_block<MMClockRecovery<T>>::_block_init = true;
}
void setOmega(float omega, float omegaRelLimit) {
assert(generic_block<MMClockRecovery<T>>::_block_init);
generic_block<MMClockRecovery<T>>::tempStop();
omegaMin = _omega - (_omega * _omegaRelLimit);
omegaMax = _omega + (_omega * _omegaRelLimit);
_omega = omega;
_dynOmega = _omega;
generic_block<MMClockRecovery<T>>::tempStart();
}
void setGains(float omegaGain, float muGain) {
assert(generic_block<MMClockRecovery<T>>::_block_init);
generic_block<MMClockRecovery<T>>::tempStop();
_gainOmega = omegaGain;
_muGain = muGain;
generic_block<MMClockRecovery<T>>::tempStart();
}
void setOmegaRelLimit(float omegaRelLimit) {
assert(generic_block<MMClockRecovery<T>>::_block_init);
generic_block<MMClockRecovery<T>>::tempStop();
_omegaRelLimit = omegaRelLimit;
omegaMin = _omega - (_omega * _omegaRelLimit);
omegaMax = _omega + (_omega * _omegaRelLimit);
generic_block<MMClockRecovery<T>>::tempStart();
}
void setInput(stream<T>* in) {
assert(generic_block<MMClockRecovery<T>>::_block_init);
generic_block<MMClockRecovery<T>>::tempStop();
generic_block<MMClockRecovery<T>>::unregisterInput(_in);
_in = in;
generic_block<MMClockRecovery<T>>::registerInput(_in);
generic_block<MMClockRecovery<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
int outCount = 0;
float outVal;
float phaseError;
float roundedStep;
int maxOut = 2.0f * _omega * (float)count;
// Copy the first 7 values to the delay buffer for fast computing
memcpy(&delay[7], _in->readBuf, 7 * sizeof(T));
int i = nextOffset;
for (; i < count && outCount < maxOut;) {
if constexpr (std::is_same_v<T, float>) {
// Calculate output value
// If we still need to use the old values, calculate using delay buf
// Otherwise, use normal buffer
if (i < 7) {
volk_32f_x2_dot_prod_32f(&outVal, &delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
}
else {
volk_32f_x2_dot_prod_32f(&outVal, &_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
}
out.writeBuf[outCount++] = outVal;
// Cursed phase detect approximation (don't ask me how this approximation works)
phaseError = (DSP_STEP(lastOutput)*outVal) - (lastOutput*DSP_STEP(outVal));
lastOutput = outVal;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
// Propagate delay
_p_2T = _p_1T;
_p_1T = _p_0T;
_c_2T = _c_1T;
_c_1T = _c_0T;
// Perform interpolation the same way as for float values
if (i < 7) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&delay[i], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
}
else {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&_p_0T, (lv_32fc_t*)&_in->readBuf[i - 7], INTERP_TAPS[(int)roundf(_mu * 128.0f)], 8);
}
out.writeBuf[outCount++] = _p_0T;
// Slice output value
_c_0T = DSP_STEP_CPLX(_p_0T);
// Cursed math to calculate the phase error
phaseError = (((_p_0T - _p_2T) * _c_1T.conj()) - ((_c_0T - _c_2T) * _p_1T.conj())).re;
}
// Clamp phase error
if (phaseError > 1.0f) { phaseError = 1.0f; }
if (phaseError < -1.0f) { phaseError = -1.0f; }
// Adjust the symbol rate using the phase error approximation and clamp
// TODO: Branchless clamp
_dynOmega = _dynOmega + (_gainOmega * phaseError);
if (_dynOmega > omegaMax) { _dynOmega = omegaMax; }
else if (_dynOmega < omegaMin) { _dynOmega = omegaMin; }
// Adjust the symbol phase according to the phase error approximation
// It will now contain the phase delta needed to jump to the next symbol
// Rounded step will contain the rounded number of symbols
_mu = _mu + _dynOmega + (_muGain * phaseError);
roundedStep = floor(_mu);
// Step to where the next symbol should be, and check for bogus input
i += (int)roundedStep;
if (i < 0) { i = 0; }
// Now that we've stepped to the next symbol, keep only the offset inside the symbol
_mu -= roundedStep;
}
nextOffset = i - count;
// Save the last 7 values for the next round
memcpy(delay, &_in->readBuf[count - 7], 7 * sizeof(T));
_in->flush();
if (outCount > 0 && !out.swap(outCount)) { return -1; }
return count;
}
stream<T> out;
private:
int count;
// Delay buffer
T delay[1024];
int nextOffset = 0;
// Configuration
float _omega = 1.0f;
float _muGain = 1.0f;
float _gainOmega = 0.001f;
float _omegaRelLimit = 0.005;
// Precalculated values
float omegaMin = _omega + (_omega * _omegaRelLimit);
float omegaMax = _omega + (_omega * _omegaRelLimit);
// Runtime adjusted
float _dynOmega = _omega;
float _mu = 0.5f;
float lastOutput = 0.0f;
// Cursed complex stuff
complex_t _p_0T = {0,0}, _p_1T = {0,0}, _p_2T = {0,0};
complex_t _c_0T = {0,0}, _c_1T = {0,0}, _c_2T = {0,0};
stream<T>* _in;
};
}

View File

@ -0,0 +1,189 @@
#pragma once
#include "../processor.h"
#include "../loop/phase_control_loop.h"
#include "../taps/windowed_sinc.h"
#include "../multirate/polyphase_bank.h"
#include "../math/step.h"
namespace dsp::clock_recovery {
class FD : public Processor<float, float> {
using base_type = Processor<float, float> ;
public:
FD() {}
FD(stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~FD() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(buffer);
}
void init(stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::init(in);
}
void setOmega(double omega) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_omega = omega;
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
base_type::tempStart();
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(buffer);
generateInterpTaps();
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
inline int process(int count, const float* in, float* out) {
// Copy data to work buffer
memcpy(bufStart, in, count * sizeof(float));
// Process all samples
int outCount = 0;
while (offset < count) {
float error;
float outVal;
float dfdt;
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
out[outCount++] = outVal;
// Calculate derivative of the signal
if (phase == 0) {
float fT1;
volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount);
dfdt = fT1 - outVal;
}
else if (phase == _interpPhaseCount - 1) {
float fT_1;
volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount);
dfdt = outVal - fT_1;
}
else {
float fT_1;
float fT1;
volk_32f_x2_dot_prod_32f(&fT_1, &buffer[offset], interpBank.phases[phase-1], _interpTapCount);
volk_32f_x2_dot_prod_32f(&fT1, &buffer[offset], interpBank.phases[phase+1], _interpTapCount);
dfdt = (fT1 - fT_1) * 0.5f;
}
// Calculate error
error = dfdt * math::step(outVal);
// Clamp symbol phase error
if (error > 1.0f) { error = 1.0f; }
if (error < -1.0f) { error = -1.0f; }
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
return outCount;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
loop::PhaseControlLoop<float, false> pcl;
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
int offset = 0;
float* buffer;
float* bufStart;
};
}

View File

@ -0,0 +1,199 @@
#pragma once
#include "../processor.h"
#include "../loop/phase_control_loop.h"
#include "../taps/windowed_sinc.h"
#include "../multirate/polyphase_bank.h"
#include "../math/step.h"
namespace dsp::clock_recovery {
template<class T>
class MM : public Processor<T, T> {
using base_type = Processor<T, T> ;
public:
MM() {}
MM(stream<T>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~MM() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(buffer);
}
void init(stream<T>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::init(in);
}
void setOmega(double omega) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_omega = omega;
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
base_type::tempStart();
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
buffer::free(buffer);
generateInterpTaps();
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
lastOut = 0.0f;
_p_0T = { 0.0f, 0.0f }; _p_1T = { 0.0f, 0.0f }; _p_2T = { 0.0f, 0.0f };
_c_0T = { 0.0f, 0.0f }; _c_1T = { 0.0f, 0.0f }; _c_2T = { 0.0f, 0.0f };
base_type::tempStart();
}
inline int process(int count, const T* in, T* out) {
// Copy data to work buffer
memcpy(bufStart, in, count * sizeof(T));
// Process all samples
int outCount = 0;
while (offset < count) {
float error;
T outVal;
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
if constexpr (std::is_same_v<T, float>) {
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&outVal, (lv_32fc_t*)&buffer[offset], interpBank.phases[phase], _interpTapCount);
}
out[outCount++] = outVal;
// Calculate symbol phase error
if constexpr (std::is_same_v<T, float>) {
error = (math::step(lastOut) * outVal) - (lastOut * math::step(outVal));
lastOut = outVal;
}
if constexpr (std::is_same_v<T, complex_t>) {
// Propagate delay
_p_2T = _p_1T;
_p_1T = _p_0T;
_c_2T = _c_1T;
_c_1T = _c_0T;
// Update the T0 values
_p_0T = outVal;
_c_0T = math::step(outVal);
// Error
error = (((_p_0T - _p_2T) * _c_1T.conj()) - ((_c_0T - _c_2T) * _p_1T.conj())).re;
}
// Clamp symbol phase error
if (error > 1.0f) { error = 1.0f; }
if (error < -1.0f) { error = -1.0f; }
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(T));
return outCount;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
loop::PhaseControlLoop<float, false> pcl;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
// Previous output storage
float lastOut = 0.0f;
complex_t _p_0T = { 0.0f, 0.0f }, _p_1T = { 0.0f, 0.0f }, _p_2T = { 0.0f, 0.0f };
complex_t _c_0T = { 0.0f, 0.0f }, _c_1T = { 0.0f, 0.0f }, _c_2T = { 0.0f, 0.0f };
int offset = 0;
T* buffer;
T* bufStart;
};
}

View File

@ -1,161 +0,0 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class DynamicRangeCompressor : public generic_block<DynamicRangeCompressor> {
public:
DynamicRangeCompressor() {}
enum PCMType {
PCM_TYPE_I8,
PCM_TYPE_I16,
PCM_TYPE_F32
};
DynamicRangeCompressor(stream<complex_t>* in, PCMType pcmType) { init(in, pcmType); }
void init(stream<complex_t>* in, PCMType pcmType) {
_in = in;
_pcmType = pcmType;
generic_block<DynamicRangeCompressor>::registerInput(_in);
generic_block<DynamicRangeCompressor>::registerOutput(&out);
generic_block<DynamicRangeCompressor>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<DynamicRangeCompressor>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeCompressor>::ctrlMtx);
generic_block<DynamicRangeCompressor>::tempStop();
generic_block<DynamicRangeCompressor>::unregisterInput(_in);
_in = in;
generic_block<DynamicRangeCompressor>::registerInput(_in);
generic_block<DynamicRangeCompressor>::tempStart();
}
void setPCMType(PCMType pcmType) {
assert(generic_block<DynamicRangeCompressor>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeCompressor>::ctrlMtx);
_pcmType = pcmType;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float* scaler = (float*)out.writeBuf;
void* dataBuf = &out.writeBuf[4];
// If no dynamic range compression is to be done, just pass the data to the output with a null scaler
if (_pcmType == PCM_TYPE_F32) {
*scaler = 0;
memcpy(dataBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(4 + (count * sizeof(complex_t)))) { return -1; }
return count;
}
// Find maximum value
complex_t val;
float absre;
float absim;
float maxVal = 0;
for (int i = 0; i < count; i++) {
val = _in->readBuf[i];
absre = fabsf(val.re);
absim = fabsf(val.im);
if (absre > maxVal) { maxVal = absre; }
if (absim > maxVal) { maxVal = absim; }
}
// Convert to the right type and send it out (sign bit determins pcm type)
if (_pcmType == PCM_TYPE_I8) {
*scaler = maxVal;
volk_32f_s32f_convert_8i((int8_t*)dataBuf, (float*)_in->readBuf, 128.0f / maxVal, count * 2);
_in->flush();
if (!out.swap(4 + (count * sizeof(int8_t) * 2))) { return -1; }
}
else if (_pcmType == PCM_TYPE_I16) {
*scaler = -maxVal;
volk_32f_s32f_convert_16i((int16_t*)dataBuf, (float*)_in->readBuf, 32768.0f / maxVal, count * 2);
_in->flush();
if (!out.swap(4 + (count * sizeof(int16_t) * 2))) { return -1; }
}
else {
_in->flush();
}
return count;
}
stream<uint8_t> out;
private:
stream<complex_t>* _in;
PCMType _pcmType;
};
class DynamicRangeDecompressor : public generic_block<DynamicRangeDecompressor> {
public:
DynamicRangeDecompressor() {}
DynamicRangeDecompressor(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<DynamicRangeDecompressor>::registerInput(_in);
generic_block<DynamicRangeDecompressor>::registerOutput(&out);
generic_block<DynamicRangeDecompressor>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<DynamicRangeDecompressor>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<DynamicRangeDecompressor>::ctrlMtx);
generic_block<DynamicRangeDecompressor>::tempStop();
generic_block<DynamicRangeDecompressor>::unregisterInput(_in);
_in = in;
generic_block<DynamicRangeDecompressor>::registerInput(_in);
generic_block<DynamicRangeDecompressor>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float* scaler = (float*)_in->readBuf;
void* dataBuf = &_in->readBuf[4];
// If the scaler is null, data is F32
if (*scaler == 0) {
memcpy(out.writeBuf, dataBuf, count - 4);
_in->flush();
if (!out.swap((count - 4) / sizeof(complex_t))) { return -1; }
return count;
}
// Convert back to f32 from the pcm type
float absScale = fabsf(*scaler);
if (*scaler > 0) {
spdlog::warn("{0}", absScale);
int outCount = (count - 4) / (sizeof(int8_t) * 2);
volk_8i_s32f_convert_32f((float*)out.writeBuf, (int8_t*)dataBuf, 128.0f / absScale, outCount * 2);
_in->flush();
if (!out.swap(outCount)) { return -1; }
}
else {
int outCount = (count - 4) / (sizeof(int16_t) * 2);
volk_16i_s32f_convert_32f((float*)out.writeBuf, (int16_t*)dataBuf, 32768.0f / absScale, outCount * 2);
_in->flush();
if (!out.swap(outCount)) { return -1; }
}
return count;
}
stream<complex_t> out;
private:
stream<uint8_t>* _in;
};
}

View File

@ -0,0 +1,9 @@
#pragma once
namespace dsp::compression {
enum PCMType {
PCM_TYPE_I8,
PCM_TYPE_I16,
PCM_TYPE_F32
};
}

View File

@ -0,0 +1,79 @@
#pragma once
#include "../processor.h"
#include "pcm_type.h"
namespace dsp::compression {
class SampleStreamCompressor : public Processor<complex_t, uint8_t> {
using base_type = Processor<complex_t, uint8_t>;
public:
SampleStreamCompressor() {}
SampleStreamCompressor(stream<complex_t>* in, PCMType pcmType) { init(in, pcmType); }
void init(stream<complex_t>* in, PCMType pcmType) {
_pcmType = pcmType;
base_type::init(in);
}
void setPCMType(PCMType pcmType) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_pcmType = pcmType;
base_type::tempStart();
}
inline static int process(int count, PCMType pcmType, const complex_t* in, uint8_t* out) {
uint16_t* compressionType = (uint16_t*)out;
uint16_t* sampleType = (uint16_t*)&out[2];
float* scaler = (float*)&out[4];
void* dataBuf = &out[8];
// Write options and leave blank space for compression
*compressionType = 0;
*sampleType = pcmType;
// If type is float32, no compression is needed
if (pcmType == PCMType::PCM_TYPE_F32) {
*scaler = 0;
memcpy(dataBuf, in, count * sizeof(complex_t));
return 8 + (count * sizeof(complex_t));
}
// Find maximum value
uint32_t maxIdx;
volk_32f_index_max_32u(&maxIdx, (float*)in, count * 2);
float maxVal = ((float*)in)[maxIdx];
*scaler = maxVal;
// Convert to the right type and send it out (sign bit determines pcm type)
if (pcmType == PCMType::PCM_TYPE_I8) {
volk_32f_s32f_convert_8i((int8_t*)dataBuf, (float*)in, 128.0f / maxVal, count * 2);
return 8 + (count * sizeof(int8_t) * 2);
}
else if (pcmType == PCMType::PCM_TYPE_I16) {
volk_32f_s32f_convert_16i((int16_t*)dataBuf, (float*)in, 32768.0f / maxVal, count * 2);
return 8 + (count * sizeof(int16_t) * 2);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, _pcmType, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
PCMType _pcmType;
};
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "../processor.h"
#include "pcm_type.h"
namespace dsp::compression {
class SampleStreamDecompressor : public Processor<uint8_t, complex_t> {
using base_type = Processor<uint8_t, complex_t>;
public:
SampleStreamDecompressor() {}
SampleStreamDecompressor(stream<uint8_t>* in) { base_type::init(in); }
inline int process(int count, const uint8_t* in, complex_t* out) {
uint16_t sampleType = *(uint16_t*)&in[2];
float scaler = *(float*)&in[4];
const void* dataBuf = &in[8];
if (sampleType == PCMType::PCM_TYPE_F32) {
memcpy(out, dataBuf, count - 8);
return (count - 8) / sizeof(complex_t);
}
else if (sampleType == PCMType::PCM_TYPE_I16) {
int outCount = (count - 8) / (sizeof(int16_t) * 2);
volk_16i_s32f_convert_32f((float*)out, (int16_t*)dataBuf, 32768.0f / scaler, outCount * 2);
return outCount;
}
else if (sampleType == PCMType::PCM_TYPE_I8) {
int outCount = (count - 8) / (sizeof(int8_t) * 2);
volk_8i_s32f_convert_32f((float*)out, (int8_t*)dataBuf, 128.0f / scaler, outCount * 2);
return outCount;
}
return 0;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
};
}

View File

@ -1,345 +0,0 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class ComplexToStereo : public generic_block<ComplexToStereo> {
public:
ComplexToStereo() {}
ComplexToStereo(stream<complex_t>* in) { init(in); }
static_assert(sizeof(complex_t) == sizeof(stereo_t));
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToStereo>::registerInput(_in);
generic_block<ComplexToStereo>::registerOutput(&out);
generic_block<ComplexToStereo>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexToStereo>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx);
generic_block<ComplexToStereo>::tempStop();
generic_block<ComplexToStereo>::unregisterInput(_in);
_in = in;
generic_block<ComplexToStereo>::registerInput(_in);
generic_block<ComplexToStereo>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
stream<complex_t>* _in;
};
class ComplexToReal : public generic_block<ComplexToReal> {
public:
ComplexToReal() {}
ComplexToReal(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToReal>::registerInput(_in);
generic_block<ComplexToReal>::registerOutput(&out);
generic_block<ComplexToReal>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexToReal>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx);
generic_block<ComplexToReal>::tempStop();
generic_block<ComplexToReal>::unregisterInput(_in);
_in = in;
generic_block<ComplexToReal>::registerInput(_in);
generic_block<ComplexToReal>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
stream<complex_t>* _in;
};
class ComplexToImag : public generic_block<ComplexToImag> {
public:
ComplexToImag() {}
ComplexToImag(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToImag>::registerInput(_in);
generic_block<ComplexToImag>::registerOutput(&out);
generic_block<ComplexToImag>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexToImag>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx);
generic_block<ComplexToImag>::tempStop();
generic_block<ComplexToImag>::unregisterInput(_in);
_in = in;
generic_block<ComplexToImag>::registerInput(_in);
generic_block<ComplexToImag>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if(!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
stream<complex_t>* _in;
};
class RealToComplex : public generic_block<RealToComplex> {
public:
RealToComplex() {}
RealToComplex(stream<float>* in) { init(in); }
~RealToComplex() {
if (!generic_block<RealToComplex>::_block_init) { return; }
generic_block<RealToComplex>::stop();
delete[] nullBuffer;
generic_block<RealToComplex>::_block_init = false;
}
void init(stream<float>* in) {
_in = in;
nullBuffer = new float[STREAM_BUFFER_SIZE];
memset(nullBuffer, 0, STREAM_BUFFER_SIZE * sizeof(float));
generic_block<RealToComplex>::registerInput(_in);
generic_block<RealToComplex>::registerOutput(&out);
generic_block<RealToComplex>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<RealToComplex>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<RealToComplex>::ctrlMtx);
generic_block<RealToComplex>::tempStop();
generic_block<RealToComplex>::unregisterInput(_in);
_in = in;
generic_block<RealToComplex>::registerInput(_in);
generic_block<RealToComplex>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float* nullBuffer;
stream<float>* _in;
};
class Int16CToComplex : public generic_block<Int16CToComplex> {
public:
Int16CToComplex() {}
Int16CToComplex(stream<int16_t>* in) { init(in); }
void init(stream<int16_t>* in) {
_in = in;
generic_block<Int16CToComplex>::registerInput(_in);
generic_block<Int16CToComplex>::registerOutput(&out);
generic_block<Int16CToComplex>::_block_init = true;
}
void setInput(stream<int16_t>* in) {
assert(generic_block<Int16CToComplex>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Int16CToComplex>::ctrlMtx);
generic_block<Int16CToComplex>::tempStop();
generic_block<Int16CToComplex>::unregisterInput(_in);
_in = in;
generic_block<Int16CToComplex>::registerInput(_in);
generic_block<Int16CToComplex>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_16i_s32f_convert_32f((float*)out.writeBuf, _in->readBuf, 32768.0f, count * 2);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
stream<int16_t>* _in;
};
class ComplexToInt16C : public generic_block<ComplexToInt16C> {
public:
ComplexToInt16C() {}
ComplexToInt16C(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<ComplexToInt16C>::registerInput(_in);
generic_block<ComplexToInt16C>::registerOutput(&out);
generic_block<ComplexToInt16C>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexToInt16C>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexToInt16C>::ctrlMtx);
generic_block<ComplexToInt16C>::tempStop();
generic_block<ComplexToInt16C>::unregisterInput(_in);
_in = in;
generic_block<ComplexToInt16C>::registerInput(_in);
generic_block<ComplexToInt16C>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32f_s32f_convert_16i(out.writeBuf, (float*)_in->readBuf, 32768.0f, count * 2);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<int16_t> out;
private:
stream<complex_t>* _in;
};
class Int16ToFloat : public generic_block<Int16ToFloat> {
public:
Int16ToFloat() {}
Int16ToFloat(stream<int16_t>* in) { init(in); }
void init(stream<int16_t>* in) {
_in = in;
generic_block<Int16ToFloat>::registerInput(_in);
generic_block<Int16ToFloat>::registerOutput(&out);
generic_block<Int16ToFloat>::_block_init = true;
}
void setInput(stream<int16_t>* in) {
assert(generic_block<Int16ToFloat>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Int16ToFloat>::ctrlMtx);
generic_block<Int16ToFloat>::tempStop();
generic_block<Int16ToFloat>::unregisterInput(_in);
_in = in;
generic_block<Int16ToFloat>::registerInput(_in);
generic_block<Int16ToFloat>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_16i_s32f_convert_32f(out.writeBuf, _in->readBuf, 32768.0f, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
stream<int16_t>* _in;
};
class FloatToInt16 : public generic_block<FloatToInt16> {
public:
FloatToInt16() {}
FloatToInt16(stream<float>* in) { init(in); }
void init(stream<float>* in) {
_in = in;
generic_block<FloatToInt16>::registerInput(_in);
generic_block<FloatToInt16>::registerOutput(&out);
generic_block<FloatToInt16>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<FloatToInt16>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FloatToInt16>::ctrlMtx);
generic_block<FloatToInt16>::tempStop();
generic_block<FloatToInt16>::unregisterInput(_in);
_in = in;
generic_block<FloatToInt16>::registerInput(_in);
generic_block<FloatToInt16>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32f_s32f_convert_16i(out.writeBuf, _in->readBuf, 32768.0f, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<int16_t> out;
private:
stream<float>* _in;
};
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "../processor.h"
namespace dsp::convert {
class ComplexToReal : public Processor<complex_t, float> {
using base_type = Processor<complex_t, float>;
public:
ComplexToReal() {}
ComplexToReal(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) { base_type::init(in); }
inline static int process(int count, const complex_t* in, float* out) {
volk_32fc_deinterleave_real_32f(out, (lv_32fc_t*)in, count);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "../processor.h"
namespace dsp::convert {
class ComplexToStereo : public Processor<complex_t, stereo_t> {
using base_type = Processor<complex_t, stereo_t>;
public:
ComplexToStereo() {}
ComplexToStereo(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) { base_type::init(in); }
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
memcpy(base_type::out.writeBuf, base_type::_in->readBuf, count * sizeof(complex_t));
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "../operator.h"
namespace dsp::convert {
class LRToStereo : public Operator<float, float, stereo_t> {
using base_type = Operator<float, float, stereo_t>;
public:
LRToStereo() {}
LRToStereo(stream<float>* l, stream<float>* r) { init(l, r); }
void init(stream<float>* l, stream<float>* r) { base_type::init(l, r); }
void setInputs(stream<float>* l, stream<float>* r) { base_type::setInputs(l, r); }
void setInputL(stream<float>* l) { base_type::setInputA(l); }
void setInputR(stream<float>* r) { base_type::setInputB(r); }
static inline int process(int count, const float* l, const float* r, stereo_t* out) {
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, l, r, count);
return count;
}
int run() {
int a_count = base_type::_a->read();
if (a_count < 0) { return -1; }
int b_count = base_type::_b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
base_type::_a->flush();
base_type::_b->flush();
return 0;
}
process(a_count, base_type::_a->readBuf, base_type::_b->readBuf, base_type::out.writeBuf);
base_type::_a->flush();
base_type::_b->flush();
if (!base_type::out.swap(a_count)) { return -1; }
return a_count;
}
};
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "../processor.h"
namespace dsp::convert {
class MonoToStereo : public Processor<float, stereo_t> {
using base_type = Processor<float, stereo_t>;
public:
MonoToStereo() {}
MonoToStereo(stream<float>* in) { base_type::init(in); }
inline static int process(int count, const float* in, stereo_t* out) {
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, in, in, count);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -0,0 +1,44 @@
#pragma once
#include "../processor.h"
namespace dsp::convert {
class RealToComplex : public Processor<float, complex_t> {
using base_type = Processor<float, complex_t>;
public:
RealToComplex() {}
RealToComplex(stream<float>* in) { init(in); }
~RealToComplex() {
if (!base_type::_block_init) { return; }
base_type::stop();
buffer::free(nullBuf);
}
void init(stream<float>* in) {
nullBuf = buffer::alloc<float>(STREAM_BUFFER_SIZE);
buffer::clear(nullBuf, STREAM_BUFFER_SIZE);
base_type::init(in);
}
inline int process(int count, const float* in, complex_t* out) {
volk_32f_x2_interleave_32fc((lv_32fc_t*)out, in, nullBuf, count);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
float* nullBuf;
};
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "../processor.h"
namespace dsp::convert {
class StereoToMono : public Processor<stereo_t, float> {
using base_type = Processor<stereo_t, float>;
public:
StereoToMono() {}
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
inline int process(int count, const stereo_t* in, float* out) {
for (int i = 0; i < count; i++) {
out[i] = (in[i].l + in[i].r) / 2.0f;
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -1,146 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/window.h>
namespace dsp {
class IQCorrector : public generic_block<IQCorrector> {
public:
IQCorrector() {}
IQCorrector(stream<complex_t>* in, float rate) { init(in, rate); }
void init(stream<complex_t>* in, float rate) {
_in = in;
correctionRate = rate;
offset.re = 0;
offset.im = 0;
generic_block<IQCorrector>::registerInput(_in);
generic_block<IQCorrector>::registerOutput(&out);
generic_block<IQCorrector>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<IQCorrector>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<IQCorrector>::ctrlMtx);
generic_block<IQCorrector>::tempStop();
generic_block<IQCorrector>::unregisterInput(_in);
_in = in;
generic_block<IQCorrector>::registerInput(_in);
generic_block<IQCorrector>::tempStart();
}
void setCorrectionRate(float rate) {
correctionRate = rate;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
for (int i = 0; i < count; i++) {
out.writeBuf[i] = _in->readBuf[i] - offset;
offset = offset + (out.writeBuf[i] * correctionRate);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
// TEMPORARY FOR DEBUG PURPOSES
bool bypass = false;
complex_t offset;
private:
stream<complex_t>* _in;
float correctionRate = 0.00001;
};
class DCBlocker : public generic_block<DCBlocker> {
public:
DCBlocker() {}
DCBlocker(stream<float>* in, float rate) { init(in, rate); }
void init(stream<float>* in, float rate) {
_in = in;
correctionRate = rate;
offset = 0;
generic_block<DCBlocker>::registerInput(_in);
generic_block<DCBlocker>::registerOutput(&out);
generic_block<DCBlocker>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<DCBlocker>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<DCBlocker>::ctrlMtx);
generic_block<DCBlocker>::tempStop();
generic_block<DCBlocker>::unregisterInput(_in);
_in = in;
generic_block<DCBlocker>::registerInput(_in);
generic_block<DCBlocker>::tempStart();
}
void setCorrectionRate(float rate) {
correctionRate = rate;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
for (int i = 0; i < count; i++) {
out.writeBuf[i] = _in->readBuf[i] - offset;
offset = offset + (out.writeBuf[i] * correctionRate);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
// TEMPORARY FOR DEBUG PURPOSES
bool bypass = false;
float offset;
private:
stream<float>* _in;
float correctionRate = 0.00001;
};
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "../processor.h"
namespace dsp::correction {
template<class T>
class DCBlocker : public Processor<T, T> {
using base_type = Processor<T, T>;
public:
DCBlocker() {}
DCBlocker(stream<T>* in, double rate) { init(in, rate); }
DCBlocker(stream<T>* in, double rate, double samplerate) { init(in, rate, samplerate); }
void init(stream<T>* in, double rate) {
_rate = rate;
if constexpr (std::is_same_v<T, float>) {
offset = 0.0f;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
offset = { 0.0f, 0.0f };
}
base_type::init(in);
}
void init(stream<T>* in, double rate, double samplerate) {
init(in, rate / samplerate);
}
void setRate(double rate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_rate = rate;
}
void setRate(double rate, double samplerate) {
setRate(rate / samplerate);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
if constexpr (std::is_same_v<T, float>) {
offset = 0.0f;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
offset = { 0.0f, 0.0f };
}
base_type::tempStart();
}
// TODO: Add back the const
int process(int count, T* in, T* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] - offset;
offset += out[i] * _rate;
}
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
float _rate;
T offset;
};
}

View File

@ -1,107 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/window.h>
namespace dsp {
template <class T>
class HalfDecimator : public generic_block<HalfDecimator<T>> {
public:
HalfDecimator() {}
HalfDecimator(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
~HalfDecimator() {
if (!generic_block<HalfDecimator<T>>::_block_init) { return; }
generic_block<HalfDecimator<T>>::stop();
volk_free(buffer);
volk_free(taps);
generic_block<HalfDecimator<T>>::_block_init = false;
}
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
_in = in;
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount);
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
bufStart = &buffer[tapCount];
generic_block<HalfDecimator<T>>::registerInput(_in);
generic_block<HalfDecimator<T>>::registerOutput(&out);
generic_block<HalfDecimator<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<HalfDecimator<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HalfDecimator<T>>::ctrlMtx);
generic_block<HalfDecimator<T>>::tempStop();
generic_block<HalfDecimator<T>>::unregisterInput(_in);
_in = in;
generic_block<HalfDecimator<T>>::registerInput(_in);
generic_block<HalfDecimator<T>>::tempStart();
}
void updateWindow(dsp::filter_window::generic_window* window) {
assert(generic_block<HalfDecimator<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HalfDecimator<T>>::ctrlMtx);
std::lock_guard<std::mutex> lck2(bufMtx);
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
bufStart = &buffer[tapCount];
window->createTaps(taps, tapCount);
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
memcpy(bufStart, _in->readBuf, count * sizeof(T));
_in->flush();
int inIndex = _inIndex;
int outIndex = 0;
if constexpr (std::is_same_v<T, float>) {
while (inIndex < count) {
volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[outIndex], (float*)&buffer[inIndex+1], taps, tapCount);
inIndex += 2;
outIndex++;
}
}
if constexpr (std::is_same_v<T, complex_t>) {
while (inIndex < count) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[inIndex+1], taps, tapCount);
inIndex += 2;
outIndex++;
}
}
_inIndex = inIndex - count;
if (!out.swap(outIndex)) { return -1; }
memmove(buffer, &buffer[count], tapCount * sizeof(T));
return count;
}
stream<T> out;
private:
stream<T>* _in;
dsp::filter_window::generic_window* _window;
std::mutex bufMtx;
T* bufStart;
T* buffer;
int tapCount;
float* taps;
int _inIndex = 0;
};
}

View File

@ -1,422 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <inttypes.h>
#define DSP_SIGN(n) ((n) >= 0)
#define DSP_STEP(n) (((n) > 0.0f) ? 1.0f : -1.0f)
namespace dsp {
class Deframer : public generic_block<Deframer> {
public:
Deframer() {}
Deframer(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
~Deframer() {
if (!generic_block<Deframer>::_block_init) { return; }
generic_block<Deframer>::stop();
generic_block<Deframer>::_block_init = false;
}
void init(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) {
_in = in;
_frameLen = frameLen;
_syncword = new uint8_t[syncLen];
_syncLen = syncLen;
memcpy(_syncword, syncWord, syncLen);
buffer = new uint8_t[STREAM_BUFFER_SIZE + syncLen];
memset(buffer, 0, syncLen);
bufferStart = buffer + syncLen;
generic_block<Deframer>::registerInput(_in);
generic_block<Deframer>::registerOutput(&out);
generic_block<Deframer>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<Deframer>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Deframer>::ctrlMtx);
generic_block<Deframer>::tempStop();
generic_block<Deframer>::unregisterInput(_in);
_in = in;
generic_block<Deframer>::registerInput(_in);
generic_block<Deframer>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
// Copy data into work buffer
memcpy(bufferStart, _in->readBuf, count - 1);
// Iterate through all symbols
for (int i = 0; i < count;) {
// If already in the process of reading bits
if (bitsRead >= 0) {
if ((bitsRead % 8) == 0) { out.writeBuf[bitsRead / 8] = 0; }
out.writeBuf[bitsRead / 8] |= (buffer[i] << (7 - (bitsRead % 8)));
i++;
bitsRead++;
if (bitsRead >= _frameLen) {
if (!out.swap((bitsRead / 8) + ((bitsRead % 8) > 0))) { return -1; }
bitsRead = -1;
if (allowSequential) { nextBitIsStartOfFrame = true; }
}
continue;
}
// Else, check for a header
else if (memcmp(buffer + i, _syncword, _syncLen) == 0) {
bitsRead = 0;
//printf("Frame found!\n");
badFrameCount = 0;
continue;
}
else if (nextBitIsStartOfFrame) {
nextBitIsStartOfFrame = false;
// try to save
if (badFrameCount < 5) {
badFrameCount++;
//printf("Frame found!\n");
bitsRead = 0;
continue;
}
}
else { i++; }
nextBitIsStartOfFrame = false;
}
// Keep last _syncLen4 symbols
memcpy(buffer, &_in->readBuf[count - _syncLen], _syncLen);
//printf("Block processed\n");
callcount++;
_in->flush();
return count;
}
bool allowSequential = true;
stream<uint8_t> out;
private:
uint8_t* buffer;
uint8_t* bufferStart;
uint8_t* _syncword;
int count;
int _frameLen;
int _syncLen;
int bitsRead = -1;
int badFrameCount = 5;
bool nextBitIsStartOfFrame = false;
int callcount = 0;
stream<uint8_t>* _in;
};
inline int MachesterHammingDistance(float* data, uint8_t* syncBits, int n) {
int dist = 0;
for (int i = 0; i < n; i++) {
if ((data[(2*i) + 1] > data[2*i]) != syncBits[i]) { dist++; }
}
return dist;
}
inline int HammingDistance(uint8_t* data, uint8_t* syncBits, int n) {
int dist = 0;
for (int i = 0; i < n; i++) {
if (data[i] != syncBits[i]) { dist++; }
}
return dist;
}
class ManchesterDeframer : public generic_block<ManchesterDeframer> {
public:
ManchesterDeframer() {}
ManchesterDeframer(stream<float>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
void init(stream<float>* in, int frameLen, uint8_t* syncWord, int syncLen) {
_in = in;
_frameLen = frameLen;
_syncword = new uint8_t[syncLen];
_syncLen = syncLen;
memcpy(_syncword, syncWord, syncLen);
buffer = new float[STREAM_BUFFER_SIZE + (syncLen * 2)];
memset(buffer, 0, syncLen * 2 * sizeof(float));
bufferStart = &buffer[syncLen * 2];
generic_block<ManchesterDeframer>::registerInput(_in);
generic_block<ManchesterDeframer>::registerOutput(&out);
generic_block<ManchesterDeframer>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<ManchesterDeframer>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ManchesterDeframer>::ctrlMtx);
generic_block<ManchesterDeframer>::tempStop();
generic_block<ManchesterDeframer>::unregisterInput(_in);
_in = in;
generic_block<ManchesterDeframer>::registerInput(_in);
generic_block<ManchesterDeframer>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
int readable;
// Copy data into work buffer
memcpy(bufferStart, _in->readBuf, (count - 1) * sizeof(float));
// Iterate through all symbols
for (int i = 0; i < count;) {
// If already in the process of reading bits
if (bitsRead >= 0) {
readable = std::min<int>(count - i, _frameLen - bitsRead);
memcpy(&out.writeBuf[bitsRead], &buffer[i], readable * sizeof(float));
bitsRead += readable;
i += readable;
if (bitsRead >= _frameLen) {
out.swap(_frameLen);
bitsRead = -1;
}
continue;
}
// Else, check for a header
if (MachesterHammingDistance(&buffer[i], _syncword, _syncLen) <= 2) {
bitsRead = 0;
continue;
}
i++;
}
// Keep last _syncLen symbols
memcpy(buffer, &_in->readBuf[count - (_syncLen * 2)], _syncLen * 2 * sizeof(float));
_in->flush();
return count;
}
stream<float> out;
private:
float* buffer;
float* bufferStart;
uint8_t* _syncword;
int count;
int _frameLen;
int _syncLen;
int bitsRead = -1;
stream<float>* _in;
};
class SymbolDeframer : public generic_block<SymbolDeframer> {
public:
SymbolDeframer() {}
SymbolDeframer(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) { init(in, frameLen, syncWord, syncLen); }
void init(stream<uint8_t>* in, int frameLen, uint8_t* syncWord, int syncLen) {
_in = in;
_frameLen = frameLen;
_syncword = new uint8_t[syncLen];
_syncLen = syncLen;
memcpy(_syncword, syncWord, syncLen);
buffer = new uint8_t[STREAM_BUFFER_SIZE + syncLen];
memset(buffer, 0, syncLen);
bufferStart = &buffer[syncLen];
generic_block<SymbolDeframer>::registerInput(_in);
generic_block<SymbolDeframer>::registerOutput(&out);
generic_block<SymbolDeframer>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<SymbolDeframer>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<SymbolDeframer>::ctrlMtx);
generic_block<SymbolDeframer>::tempStop();
generic_block<SymbolDeframer>::unregisterInput(_in);
_in = in;
generic_block<SymbolDeframer>::registerInput(_in);
generic_block<SymbolDeframer>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
int readable;
// Copy data into work buffer
memcpy(bufferStart, _in->readBuf, count - 1);
// Iterate through all symbols
for (int i = 0; i < count;) {
// If already in the process of reading bits
if (bitsRead >= 0) {
readable = std::min<int>(count - i, _frameLen - bitsRead);
memcpy(&out.writeBuf[bitsRead], &buffer[i], readable);
bitsRead += readable;
i += readable;
if (bitsRead >= _frameLen) {
out.swap(_frameLen);
bitsRead = -1;
}
continue;
}
// Else, check for a header
if (HammingDistance(&buffer[i], _syncword, _syncLen) <= 2) {
bitsRead = 0;
continue;
}
i++;
}
// Keep last _syncLen symbols
memcpy(buffer, &_in->readBuf[count - _syncLen], _syncLen);
_in->flush();
return count;
}
stream<uint8_t> out;
private:
uint8_t* buffer;
uint8_t* bufferStart;
uint8_t* _syncword;
int count;
int _frameLen;
int _syncLen;
int bitsRead = -1;
stream<uint8_t>* _in;
};
class ManchesterDecoder : public generic_block<ManchesterDecoder> {
public:
ManchesterDecoder() {}
ManchesterDecoder(stream<float>* in, bool inverted) { init(in, inverted); }
void init(stream<float>* in, bool inverted) {
_in = in;
_inverted = inverted;
generic_block<ManchesterDecoder>::registerInput(_in);
generic_block<ManchesterDecoder>::registerOutput(&out);
generic_block<ManchesterDecoder>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<ManchesterDecoder>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ManchesterDecoder>::ctrlMtx);
generic_block<ManchesterDecoder>::tempStop();
generic_block<ManchesterDecoder>::unregisterInput(_in);
_in = in;
generic_block<ManchesterDecoder>::registerInput(_in);
generic_block<ManchesterDecoder>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (_inverted) {
for (int i = 0; i < count; i += 2) {
out.writeBuf[i/2] = (_in->readBuf[i + 1] < _in->readBuf[i]);
}
}
else {
for (int i = 0; i < count; i += 2) {
out.writeBuf[i/2] = (_in->readBuf[i + 1] > _in->readBuf[i]);
}
}
_in->flush();
out.swap(count / 2);
return count;
}
stream<uint8_t> out;
private:
stream<float>* _in;
bool _inverted;
};
class BitPacker : public generic_block<BitPacker> {
public:
BitPacker() {}
BitPacker(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<BitPacker>::registerInput(_in);
generic_block<BitPacker>::registerOutput(&out);
generic_block<BitPacker>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<BitPacker>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<BitPacker>::ctrlMtx);
generic_block<BitPacker>::tempStop();
generic_block<BitPacker>::unregisterInput(_in);
_in = in;
generic_block<BitPacker>::registerInput(_in);
generic_block<BitPacker>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < count; i++) {
if ((i % 8) == 0) { out.writeBuf[i / 8] = 0; }
out.writeBuf[i / 8] |= (_in->readBuf[i] & 1) << (7 - (i % 8));
}
_in->flush();
out.swap((count / 8) + (((count % 8) == 0) ? 0 : 1));
return count;
}
stream<uint8_t> out;
private:
stream<uint8_t>* _in;
};
}

160
core/src/dsp/demod/am.h Normal file
View File

@ -0,0 +1,160 @@
#pragma once
#include "../processor.h"
#include "../loop/agc.h"
#include "../correction/dc_blocker.h"
#include "../convert/mono_to_stereo.h"
#include "../filter/fir.h"
#include "../taps/low_pass.h"
namespace dsp::demod {
template <class T>
class AM : public Processor<dsp::complex_t, T> {
using base_type = Processor<dsp::complex_t, T>;
public:
enum AGCMode {
CARRIER,
AUDIO,
};
AM() {}
AM(stream<complex_t>* in, AGCMode agcMode, double bandwidth, double agcAttack, double agcDecay, double dcBlockRate, double samplerate) { init(in, agcMode, bandwidth, agcAttack, agcDecay, dcBlockRate, samplerate); }
~AM() {
if (!base_type::_block_init) { return; }
base_type::stop();
taps::free(lpfTaps);
}
void init(stream<complex_t>* in, AGCMode agcMode, double bandwidth, double agcAttack, double agcDecay, double dcBlockRate, double samplerate) {
_agcMode = agcMode;
_bandwidth = bandwidth;
_samplerate = samplerate;
carrierAgc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
audioAgc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
dcBlock.init(NULL, dcBlockRate);
lpfTaps = taps::lowPass(bandwidth / 2.0, (bandwidth / 2.0) * 0.1, samplerate);
lpf.init(NULL, lpfTaps);
if constexpr (std::is_same_v<T, float>) {
audioAgc.out.free();
}
dcBlock.out.free();
lpf.out.free();
base_type::init(in);
}
void setAGCMode(AGCMode agcMode) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_agcMode = agcMode;
reset();
base_type::tempStart();
}
void setBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth;
std::lock_guard<std::mutex> lck2(lpfMtx);
taps::free(lpfTaps);
lpfTaps = taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
}
void setAGCAttack(double attack) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
carrierAgc.setAttack(attack);
audioAgc.setAttack(attack);
}
void setAGCDecay(double decay) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
carrierAgc.setDecay(decay);
audioAgc.setDecay(decay);
}
void setDCBlockRate(double rate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
dcBlock.setRate(rate);
}
// TODO: Implement setSamplerate
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
carrierAgc.reset();
audioAgc.reset();
dcBlock.reset();
base_type::tempStart();
}
int process(int count, complex_t* in, T* out) {
// Apply carrier AGC if needed
if (_agcMode == AGCMode::CARRIER) {
carrierAgc.process(count, in, carrierAgc.out.writeBuf);
in = carrierAgc.out.writeBuf;
}
if constexpr (std::is_same_v<T, float>) {
volk_32fc_magnitude_32f(out, (lv_32fc_t*)in, count);
dcBlock.process(count, out, out);
if (_agcMode == AGCMode::AUDIO) {
audioAgc.process(count, out, out);
}
{
std::lock_guard<std::mutex> lck(lpfMtx);
lpf.process(count, out, out);
}
}
if constexpr (std::is_same_v<T, stereo_t>) {
volk_32fc_magnitude_32f(audioAgc.out.writeBuf, (lv_32fc_t*)in, count);
dcBlock.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
if (_agcMode == AGCMode::AUDIO) {
audioAgc.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
}
{
std::lock_guard<std::mutex> lck(lpfMtx);
lpf.process(count, audioAgc.out.writeBuf, audioAgc.out.writeBuf);
}
convert::MonoToStereo::process(count, audioAgc.out.writeBuf, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
AGCMode _agcMode;
double _samplerate;
double _bandwidth;
loop::AGC<complex_t> carrierAgc;
loop::AGC<float> audioAgc;
correction::DCBlocker<float> dcBlock;
tap<float> lpfTaps;
filter::FIR<float, float> lpf;
std::mutex lpfMtx;
};
}

View File

@ -0,0 +1,269 @@
#pragma once
#include "quadrature.h"
#include "../taps/low_pass.h"
#include "../taps/band_pass.h"
#include "../filter/fir.h"
#include "../loop/pll.h"
#include "../convert/l_r_to_stereo.h"
#include "../convert/real_to_complex.h"
#include "../convert/complex_to_real.h"
#include "../math/conjugate.h"
#include "../math/delay.h"
#include "../math/multiply.h"
#include "../math/add.h"
#include "../math/subtract.h"
#include "../multirate/rational_resampler.h"
namespace dsp::demod {
class BroadcastFM : public Processor<complex_t, stereo_t> {
using base_type = Processor<complex_t, stereo_t>;
public:
BroadcastFM() {}
BroadcastFM(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) { init(in, deviation, samplerate, stereo, lowPass); }
~BroadcastFM() {
if (!base_type::_block_init) { return; }
base_type::stop();
buffer::free(lmr);
buffer::free(l);
buffer::free(r);
taps::free(pilotFirTaps);
taps::free(audioFirTaps);
}
virtual void init(stream<complex_t>* in, double deviation, double samplerate, bool stereo = true, bool lowPass = true, bool rdsOut = false) {
_deviation = deviation;
_samplerate = samplerate;
_stereo = stereo;
_lowPass = lowPass;
_rdsOut = rdsOut;
demod.init(NULL, _deviation, _samplerate);
pilotFirTaps = taps::bandPass<complex_t>(18750.0, 19250.0, 3000.0, _samplerate, true);
pilotFir.init(NULL, pilotFirTaps);
rtoc.init(NULL);
pilotPLL.init(NULL, 25000.0 / _samplerate, 0.0, math::hzToRads(19000.0, _samplerate), math::hzToRads(18750.0, _samplerate), math::hzToRads(19250.0, _samplerate));
lprDelay.init(NULL, ((pilotFirTaps.size - 1) / 2) + 1);
lmrDelay.init(NULL, ((pilotFirTaps.size - 1) / 2) + 1);
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.init(NULL, audioFirTaps);
arFir.init(NULL, audioFirTaps);
rdsResamp.init(NULL, samplerate, 5000.0);
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
l = buffer::alloc<float>(STREAM_BUFFER_SIZE);
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
lprDelay.out.free();
lmrDelay.out.free();
arFir.out.free();
alFir.out.free();
rdsResamp.out.free();
base_type::init(in);
}
void setDeviation(double deviation) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_deviation = deviation;
demod.setDeviation(_deviation, _samplerate);
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
demod.setDeviation(_deviation, _samplerate);
taps::free(pilotFirTaps);
pilotFirTaps = taps::bandPass<complex_t>(18750.0, 19250.0, 3000.0, samplerate, true);
pilotFir.setTaps(pilotFirTaps);
pilotPLL.setFrequencyLimits(math::hzToRads(18750.0, _samplerate), math::hzToRads(19250.0, _samplerate));
pilotPLL.setInitialFreq(math::hzToRads(19000.0, _samplerate));
lprDelay.setDelay(((pilotFirTaps.size - 1) / 2) + 1);
lmrDelay.setDelay(((pilotFirTaps.size - 1) / 2) + 1);
taps::free(audioFirTaps);
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.setTaps(audioFirTaps);
arFir.setTaps(audioFirTaps);
rdsResamp.setInSamplerate(samplerate);
reset();
base_type::tempStart();
}
void setStereo(bool stereo) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_stereo = stereo;
reset();
base_type::tempStart();
}
void setLowPass(bool lowPass) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_lowPass = lowPass;
reset();
base_type::tempStart();
}
void setRDSOut(bool rdsOut) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_rdsOut = rdsOut;
reset();
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
demod.reset();
pilotFir.reset();
pilotPLL.reset();
lprDelay.reset();
lmrDelay.reset();
alFir.reset();
arFir.reset();
base_type::tempStart();
}
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
// Demodulate
demod.process(count, in, demod.out.writeBuf);
if (_stereo) {
// Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Filter out pilot and run through PLL
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Do RDS demod
if (_rdsOut) {
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
}
// Convert output back to real for further processing
convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
// Amplify by 2x
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
// Do L = (L+R) + (L-R), R = (L+R) - (L-R)
math::Add<float>::process(count, demod.out.writeBuf, lmr, l);
math::Subtract<float>::process(count, demod.out.writeBuf, lmr, r);
// Filter if needed
if (_lowPass) {
alFir.process(count, l, l);
arFir.process(count, r, r);
}
// Interleave into stereo
convert::LRToStereo::process(count, l, r, out);
}
else {
// Process RDS if needed. Note: find a way to not have to copy half the code from the stereo demod
if (_rdsOut) {
// Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Filter out pilot and run through PLL
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
}
// Filter if needed
if (_lowPass) {
alFir.process(count, demod.out.writeBuf, demod.out.writeBuf);
}
// Interleave raw MPX to stereo
convert::LRToStereo::process(count, demod.out.writeBuf, demod.out.writeBuf, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int rdsOutCount = 0;
process(count, base_type::_in->readBuf, base_type::out.writeBuf, rdsOutCount, rdsOut.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (rdsOutCount && _rdsOut) {
if (!rdsOut.swap(rdsOutCount)) { return -1; }
}
return count;
}
stream<float> rdsOut;
protected:
double _deviation;
double _samplerate;
bool _stereo;
bool _lowPass;
bool _rdsOut;
Quadrature demod;
tap<complex_t> pilotFirTaps;
filter::FIR<complex_t, complex_t> pilotFir;
convert::RealToComplex rtoc;
loop::PLL pilotPLL;
math::Delay<float> lprDelay;
math::Delay<complex_t> lmrDelay;
tap<float> audioFirTaps;
filter::FIR<float, float> arFir;
filter::FIR<float, float> alFir;
multirate::RationalResampler<float> rdsResamp;
float* lmr;
float* l;
float* r;
};
}

90
core/src/dsp/demod/cw.h Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include "../processor.h"
#include "../channel/frequency_xlator.h"
#include "../convert/complex_to_real.h"
#include "../loop/agc.h"
#include "../convert/mono_to_stereo.h"
namespace dsp::demod {
template <class T>
class CW : public Processor<complex_t, T> {
using base_type = Processor<complex_t, T>;
public:
CW() {}
CW(stream<complex_t>* in, double tone, double agcAttack, double agcDecay, double samplerate) { init(in, tone, agcAttack, agcDecay, samplerate); }
void init(stream<complex_t>* in, double tone, double agcAttack, double agcDecay, double samplerate) {
_tone = tone;
_samplerate = samplerate;
xlator.init(NULL, tone, samplerate);
agc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
if constexpr (std::is_same_v<T, float>) {
agc.out.free();
}
base_type::init(in);
}
void setTone(double tone) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_tone = tone;
xlator.setOffset(_tone, _samplerate);
}
void setAGCAttack(double attack) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
agc.setAttack(attack);
}
void setAGCDecay(double decay) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
agc.setDecay(decay);
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_samplerate = samplerate;
xlator.setOffset(_tone, _samplerate);
}
inline int process(int count, const complex_t* in, T* out) {
xlator.process(count, in, xlator.out.writeBuf);
if constexpr (std::is_same_v<T, float>) {
dsp::convert::ComplexToReal::process(count, xlator.out.writeBuf, out);
agc.process(count, out, out);
}
if constexpr (std::is_same_v<T, stereo_t>) {
dsp::convert::ComplexToReal::process(count, xlator.out.writeBuf, agc.out.writeBuf);
agc.process(count, agc.out.writeBuf, agc.out.writeBuf);
convert::MonoToStereo::process(count, agc.out.writeBuf, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
double _tone;
double _samplerate;
dsp::channel::FrequencyXlator xlator;
dsp::loop::AGC<float> agc;
};
}

121
core/src/dsp/demod/fm.h Normal file
View File

@ -0,0 +1,121 @@
#pragma once
#include "../processor.h"
#include "quadrature.h"
#include "../filter/fir.h"
#include "../taps/low_pass.h"
#include "../convert/mono_to_stereo.h"
namespace dsp::demod {
template <class T>
class FM : public dsp::Processor<dsp::complex_t, T> {
using base_type = dsp::Processor<dsp::complex_t, T>;
public:
FM() {}
FM(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) { init(in, samplerate, bandwidth, lowPass); }
~FM() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::taps::free(lpfTaps);
}
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
_samplerate = samplerate;
_bandwidth = bandwidth;
_lowPass = lowPass;
demod.init(NULL, bandwidth / 2.0, _samplerate);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
lpf.init(NULL, lpfTaps);
if constexpr (std::is_same_v<T, float>) {
demod.out.free();
}
lpf.out.free();
base_type::init(in);
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
demod.setDeviation(_bandwidth / 2.0, _samplerate);
dsp::taps::free(lpfTaps);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
base_type::tempStart();
}
void setBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth;
std::lock_guard<std::mutex> lck2(lpfMtx);
demod.setDeviation(_bandwidth / 2.0, _samplerate);
dsp::taps::free(lpfTaps);
lpfTaps = dsp::taps::lowPass(_bandwidth / 2, (_bandwidth / 2) * 0.1, _samplerate);
lpf.setTaps(lpfTaps);
}
void setLowPass(bool lowPass) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
std::lock_guard<std::mutex> lck2(lpfMtx);
_lowPass = lowPass;
lpf.reset();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
demod.reset();
lpf.reset();
base_type::tempStart();
}
inline int process(int count, dsp::complex_t* in, T* out) {
if constexpr (std::is_same_v<T, float>) {
demod.process(count, in, out);
if (_lowPass) {
std::lock_guard<std::mutex> lck(lpfMtx);
lpf.process(count, out, out);
}
}
if constexpr (std::is_same_v<T, stereo_t>) {
demod.process(count, in, demod.out.writeBuf);
if (_lowPass) {
std::lock_guard<std::mutex> lck(lpfMtx);
lpf.process(count, demod.out.writeBuf, demod.out.writeBuf);
}
convert::MonoToStereo::process(count, demod.out.writeBuf, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
double _samplerate;
double _bandwidth;
bool _lowPass;
Quadrature demod;
tap<float> lpfTaps;
filter::FIR<float, float> lpf;
std::mutex lpfMtx;
};
}

163
core/src/dsp/demod/gfsk.h Normal file
View File

@ -0,0 +1,163 @@
#pragma once
#include "quadrature.h"
#include "../taps/root_raised_cosine.h"
#include "../filter/fir.h"
#include "../clock_recovery/mm.h"
namespace dsp::demod {
// Note: I don't like how this demodulator reuses 90% of the code from the PSK demod. Same will be for the PM demod...
class GFSK : public Processor<complex_t, float> {
using base_type = Processor<complex_t, float>;
public:
GFSK() {}
GFSK(stream<complex_t>* in, double symbolrate, double samplerate, double deviation, int rrcTapCount, double rrcBeta, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
init(in, symbolrate, samplerate, deviation, rrcTapCount, rrcBeta, omegaGain, muGain);
}
~GFSK() {
if (!base_type::_block_init) { return; }
base_type::stop();
taps::free(rrcTaps);
}
void init(stream<complex_t>* in, double symbolrate, double samplerate, double deviation, int rrcTapCount, double rrcBeta, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
_symbolrate = symbolrate;
_samplerate = samplerate;
_deviation = deviation;
_rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta;
demod.init(NULL, _deviation, _samplerate);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.init(NULL, rrcTaps);
recov.init(NULL, _samplerate / _symbolrate, omegaGain, muGain, omegaRelLimit);
demod.out.free();
rrc.out.free();
recov.out.free();
base_type::init(in);
}
void setSymbolrate(double symbolrate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_symbolrate = symbolrate;
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
recov.setOmega(_samplerate / _symbolrate);
base_type::tempStart();
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
demod.setDeviation(_deviation, _samplerate);
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
recov.setOmega(_samplerate / _symbolrate);
base_type::tempStart();
}
void setDeviation(double deviation) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_deviation = deviation;
demod.setDeviation(_deviation, _samplerate);
}
void setRRCParams(int rrcTapCount, double rrcBeta) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta;
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
base_type::tempStart();
}
void setRRCTapCount(int rrcTapCount) {
setRRCParams(rrcTapCount, _rrcBeta);
}
void setRRCBeta(int rrcBeta) {
setRRCParams(_rrcTapCount, rrcBeta);
}
void setMMParams(double omegaGain, double muGain, double omegaRelLimit = 0.01) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaGain(omegaGain);
recov.setMuGain(muGain);
recov.setOmegaRelLimit(omegaRelLimit);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaGain(omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setMuGain(muGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaRelLimit(omegaRelLimit);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
demod.reset();
rrc.reset();
recov.reset();
base_type::tempStart();
}
inline int process(int count, complex_t* in, float* out) {
demod.process(count, in, out);
rrc.process(count, out, out);
return recov.process(count, out, out);
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
double _symbolrate;
double _samplerate;
double _deviation;
int _rrcTapCount;
double _rrcBeta;
Quadrature demod;
tap<float> rrcTaps;
filter::FIR<float, float> rrc;
clock_recovery::MM<float> recov;
};
}

171
core/src/dsp/demod/psk.h Normal file
View File

@ -0,0 +1,171 @@
#pragma once
#include "../taps/root_raised_cosine.h"
#include "../filter/fir.h"
#include "../loop/fast_agc.h"
#include "../loop/costas.h"
#include "../clock_recovery/mm.h"
namespace dsp::demod {
template<int ORDER>
class PSK : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>;
public:
PSK() {}
PSK(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, omegaGain, muGain);
}
~PSK() {
if (!base_type::_block_init) { return; }
base_type::stop();
taps::free(rrcTaps);
}
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
_symbolrate = symbolrate;
_samplerate = samplerate;
_rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta;
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.init(NULL, rrcTaps);
agc.init(NULL, 1.0, 10e6, agcRate);
costas.init(NULL, costasBandwidth);
recov.init(NULL, _samplerate / _symbolrate, omegaGain, muGain, omegaRelLimit);
rrc.out.free();
agc.out.free();
costas.out.free();
recov.out.free();
base_type::init(in);
}
void setSymbolrate(double symbolrate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_symbolrate = symbolrate;
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
recov.setOmega(_samplerate / _symbolrate);
base_type::tempStart();
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
recov.setOmega(_samplerate / _symbolrate);
base_type::tempStart();
}
void setRRCParams(int rrcTapCount, double rrcBeta) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta;
taps::free(rrcTaps);
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.setTaps(rrcTaps);
base_type::tempStart();
}
void setRRCTapCount(int rrcTapCount) {
setRRCParams(rrcTapCount, _rrcBeta);
}
void setRRCBeta(int rrcBeta) {
setRRCParams(_rrcTapCount, rrcBeta);
}
void setAGCRate(double agcRate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
agc.setRate(agcRate);
}
void setCostasBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
costas.setBandwidth(bandwidth);
}
void setMMParams(double omegaGain, double muGain, double omegaRelLimit = 0.01) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaGain(omegaGain);
recov.setMuGain(muGain);
recov.setOmegaRelLimit(omegaRelLimit);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaGain(omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setMuGain(muGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
recov.setOmegaRelLimit(omegaRelLimit);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
rrc.reset();
agc.reset();
costas.reset();
recov.reset();
base_type::tempStart();
}
inline int process(int count, const complex_t* in, complex_t* out) {
rrc.process(count, in, out);
agc.process(count, out, out);
costas.process(count, out, out);
return recov.process(count, out, out);
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
double _symbolrate;
double _samplerate;
int _rrcTapCount;
double _rrcBeta;
tap<float> rrcTaps;
filter::FIR<complex_t, float> rrc;
loop::FastAGC<complex_t> agc;
loop::Costas<ORDER> costas;
clock_recovery::MM<complex_t> recov;
};
}

View File

@ -0,0 +1,69 @@
#pragma once
#include "../processor.h"
#include "../math/fast_atan2.h"
#include "../math/hz_to_rads.h"
#include "../math/normalize_phase.h"
namespace dsp::demod {
class Quadrature : public Processor<complex_t, float> {
using base_type = Processor<complex_t, float>;
public:
Quadrature() {}
Quadrature(stream<complex_t>* in, double deviation) { init(in, deviation); }
Quadrature(stream<complex_t>* in, double deviation, double samplerate) { init(in, deviation, samplerate); }
virtual void init(stream<complex_t>* in, double deviation) {
_invDeviation = 1.0 / deviation;
base_type::init(in);
}
virtual void init(stream<complex_t>* in, double deviation, double samplerate) {
init(in, math::hzToRads(deviation, samplerate));
}
void setDeviation(double deviation) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_invDeviation = 1.0 / deviation;
}
void setDeviation(double deviation, double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_invDeviation = 1.0 / math::hzToRads(deviation, samplerate);
}
inline int process(int count, complex_t* in, float* out) {
for (int i = 0; i < count; i++) {
float cphase = in[i].phase();
out[i] = math::normalizePhase(cphase - phase) * _invDeviation;
phase = cphase;
}
return count;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
phase = 0.0f;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
float _invDeviation;
float phase = 0.0f;
};
}

125
core/src/dsp/demod/ssb.h Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include "../processor.h"
#include "../channel/frequency_xlator.h"
#include "../convert/complex_to_real.h"
#include "../loop/agc.h"
#include "../convert/mono_to_stereo.h"
namespace dsp::demod {
template <class T>
class SSB : public Processor<complex_t, T> {
using base_type = Processor<complex_t, T>;
public:
enum Mode {
USB,
LSB,
DSB
};
SSB() {}
SSB(stream<complex_t>* in, Mode mode, double bandwidth, double samplerate, double agcAttack, double agcDecay) { init(in, mode, bandwidth, samplerate, agcAttack, agcDecay); }
void init(stream<complex_t>* in, Mode mode, double bandwidth, double samplerate, double agcAttack, double agcDecay) {
_mode = mode;
_bandwidth = bandwidth;
_samplerate = samplerate;
xlator.init(NULL, getTranslation(), _samplerate);
agc.init(NULL, 1.0, agcAttack, agcDecay, 10e6, 10.0, INFINITY);
if constexpr (std::is_same_v<T, float>) {
agc.out.free();
}
base_type::init(in);
}
void setMode(Mode mode) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_mode = mode;
xlator.setOffset(getTranslation(), _samplerate);
base_type::tempStart();
}
void setBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_bandwidth = bandwidth;
xlator.setOffset(getTranslation(), _samplerate);
base_type::tempStart();
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
xlator.setOffset(getTranslation(), _samplerate);
base_type::tempStart();
}
void setAGCAttack(double attack) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
agc.setAttack(attack);
}
void setAGCDecay(double decay) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
agc.setDecay(decay);
}
int process(int count, const complex_t* in, T* out) {
// Move back sideband
xlator.process(count, in, xlator.out.writeBuf);
if constexpr (std::is_same_v<T, float>) {
convert::ComplexToReal::process(count, xlator.out.writeBuf, out);
agc.process(count, out, out);
}
if constexpr (std::is_same_v<T, stereo_t>) {
convert::ComplexToReal::process(count, xlator.out.writeBuf, agc.out.writeBuf);
agc.process(count, agc.out.writeBuf, agc.out.writeBuf);
convert::MonoToStereo::process(count, agc.out.writeBuf, out);
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
double getTranslation() {
if (_mode == Mode::USB) {
return _bandwidth / 2.0;
}
else if (_mode == Mode::LSB) {
return -_bandwidth / 2.0;
}
else if (_mode == Mode::DSB) {
return 0.0;
}
}
Mode _mode;
double _bandwidth;
double _samplerate;
channel::FrequencyXlator xlator;
loop::AGC<float> agc;
};
};

View File

@ -1,834 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <dsp/filter.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <spdlog/spdlog.h>
#include <dsp/pll.h>
#include <dsp/clock_recovery.h>
#include <dsp/math.h>
#include <dsp/conversion.h>
#include <dsp/audio.h>
#include <dsp/stereo_fm.h>
#include <dsp/correction.h>
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
inline float fast_arctan2(float y, float x) {
float abs_y = fabsf(y);
float r, angle;
if (x == 0.0f && y == 0.0f) { return 0.0f; }
if (x>=0.0f) {
r = (x - abs_y) / (x + abs_y);
angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r;
}
else {
r = (x + abs_y) / (abs_y - x);
angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r;
}
if (y < 0.0f) {
return -angle;
}
return angle;
}
namespace dsp {
class FloatFMDemod : public generic_block<FloatFMDemod> {
public:
FloatFMDemod() {}
FloatFMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
void init(stream<complex_t>* in, float sampleRate, float deviation) {
_in = in;
_sampleRate = sampleRate;
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FloatFMDemod>::registerInput(_in);
generic_block<FloatFMDemod>::registerOutput(&out);
generic_block<FloatFMDemod>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<FloatFMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
generic_block<FloatFMDemod>::tempStop();
generic_block<FloatFMDemod>::unregisterInput(_in);
_in = in;
generic_block<FloatFMDemod>::registerInput(_in);
generic_block<FloatFMDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<FloatFMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
generic_block<FloatFMDemod>::tempStop();
_sampleRate = sampleRate;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FloatFMDemod>::tempStart();
}
float getSampleRate() {
assert(generic_block<FloatFMDemod>::_block_init);
return _sampleRate;
}
void setDeviation(float deviation) {
assert(generic_block<FloatFMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FloatFMDemod>::ctrlMtx);
generic_block<FloatFMDemod>::tempStop();
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FloatFMDemod>::tempStart();
}
float getDeviation() {
assert(generic_block<FloatFMDemod>::_block_init);
return _deviation;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// This is somehow faster than volk...
float diff, currentPhase;
for (int i = 0; i < count; i++) {
currentPhase = fast_arctan2(_in->readBuf[i].im, _in->readBuf[i].re);
diff = currentPhase - phase;
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
out.writeBuf[i] = diff / phasorSpeed;
phase = currentPhase;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float phase = 0;
float phasorSpeed, _sampleRate, _deviation;
stream<complex_t>* _in;
};
class FMDemod : public generic_block<FMDemod> {
public:
FMDemod() {}
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
void init(stream<complex_t>* in, float sampleRate, float deviation) {
_in = in;
_sampleRate = sampleRate;
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::registerInput(_in);
generic_block<FMDemod>::registerOutput(&out);
generic_block<FMDemod>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<FMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
generic_block<FMDemod>::unregisterInput(_in);
_in = in;
generic_block<FMDemod>::registerInput(_in);
generic_block<FMDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<FMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
_sampleRate = sampleRate;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::tempStart();
}
float getSampleRate() {
assert(generic_block<FMDemod>::_block_init);
return _sampleRate;
}
void setDeviation(float deviation) {
assert(generic_block<FMDemod>::_block_init);
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
}
float getDeviation() {
assert(generic_block<FMDemod>::_block_init);
return _deviation;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// This is somehow faster than volk...
float diff, currentPhase;
for (int i = 0; i < count; i++) {
currentPhase = fast_arctan2(_in->readBuf[i].im, _in->readBuf[i].re);
diff = currentPhase - phase;
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
out.writeBuf[i].l = diff / phasorSpeed;
out.writeBuf[i].r = diff / phasorSpeed;
phase = currentPhase;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
float phase = 0;
float phasorSpeed, _sampleRate, _deviation;
stream<complex_t>* _in;
};
class AMDemod : public generic_block<AMDemod> {
public:
AMDemod() {}
AMDemod(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<AMDemod>::registerInput(_in);
generic_block<AMDemod>::registerOutput(&out);
generic_block<AMDemod>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<AMDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx);
generic_block<AMDemod>::tempStop();
generic_block<AMDemod>::unregisterInput(_in);
_in = in;
generic_block<AMDemod>::registerInput(_in);
generic_block<AMDemod>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_magnitude_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
for (int i = 0; i < count; i++) {
out.writeBuf[i] -= avg;
avg += out.writeBuf[i] * 10e-4;
}
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
stream<complex_t>* _in;
float avg = 0;
};
class SSBDemod : public generic_block<SSBDemod> {
public:
SSBDemod() {}
SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); }
~SSBDemod() {
if (!generic_block<SSBDemod>::_block_init) { return; }
generic_block<SSBDemod>::stop();
delete[] buffer;
generic_block<SSBDemod>::_block_init = false;
}
enum {
MODE_USB,
MODE_LSB,
MODE_DSB
};
void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) {
_in = in;
_sampleRate = sampleRate;
_bandWidth = bandWidth;
_mode = mode;
phase = lv_cmake(1.0f, 0.0f);
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
buffer = new lv_32fc_t[STREAM_BUFFER_SIZE];
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::registerOutput(&out);
generic_block<SSBDemod>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<SSBDemod>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx);
generic_block<SSBDemod>::tempStop();
generic_block<SSBDemod>::unregisterInput(_in);
_in = in;
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<SSBDemod>::_block_init);
_sampleRate = sampleRate;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setBandWidth(float bandWidth) {
assert(generic_block<SSBDemod>::_block_init);
_bandWidth = bandWidth;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setMode(int mode) {
assert(generic_block<SSBDemod>::_block_init);
_mode = mode;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
volk_32fc_deinterleave_real_32f(out.writeBuf, buffer, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
int _mode;
float _sampleRate, _bandWidth;
stream<complex_t>* _in;
lv_32fc_t* buffer;
lv_32fc_t phase;
lv_32fc_t phaseDelta;
};
class FSKDemod : public generic_hier_block<FSKDemod> {
public:
FSKDemod() {}
FSKDemod(stream<complex_t>* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
init(input, sampleRate, deviation, baudRate, omegaGain, muGain, omegaRelLimit);
}
void init(stream<complex_t>* input, float sampleRate, float deviation, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
_sampleRate = sampleRate;
_deviation = deviation;
_baudRate = baudRate;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
demod.init(input, _sampleRate, _deviation);
recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
out = &recov.out;
generic_hier_block<FSKDemod>::registerBlock(&demod);
generic_hier_block<FSKDemod>::registerBlock(&recov);
generic_hier_block<FSKDemod>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert((generic_hier_block<FSKDemod>::_block_init));
demod.setInput(input);
}
void setSampleRate(float sampleRate) {
assert(generic_hier_block<FSKDemod>::_block_init);
generic_hier_block<FSKDemod>::tempStop();
_sampleRate = sampleRate;
demod.setSampleRate(_sampleRate);
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
generic_hier_block<FSKDemod>::tempStart();
}
void setDeviation(float deviation) {
assert(generic_hier_block<FSKDemod>::_block_init);
_deviation = deviation;
demod.setDeviation(deviation);
}
void setBaudRate(float baudRate, float omegaRelLimit) {
assert(generic_hier_block<FSKDemod>::_block_init);
_baudRate = baudRate;
_omegaRelLimit = omegaRelLimit;
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
}
void setMMGains(float omegaGain, float myGain) {
assert(generic_hier_block<FSKDemod>::_block_init);
_omegaGain = omegaGain;
_muGain = myGain;
recov.setGains(_omegaGain, _muGain);
}
void setOmegaRelLimit(float omegaRelLimit) {
assert(generic_hier_block<FSKDemod>::_block_init);
_omegaRelLimit = omegaRelLimit;
recov.setOmegaRelLimit(_omegaRelLimit);
}
stream<float>* out = NULL;
private:
FloatFMDemod demod;
MMClockRecovery<float> recov;
float _sampleRate;
float _deviation;
float _baudRate;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
class GFSKDemod : public generic_hier_block<GFSKDemod> {
public:
GFSKDemod() {}
GFSKDemod(stream<complex_t>* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
init(input, sampleRate, deviation, rrcAlpha, baudRate, omegaGain, muGain, omegaRelLimit);
}
void init(stream<complex_t>* input, float sampleRate, float deviation, float rrcAlpha, float baudRate, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
_sampleRate = sampleRate;
_deviation = deviation;
_rrcAlpha = rrcAlpha;
_baudRate = baudRate;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
demod.init(input, _sampleRate, _deviation);
rrc.init(31, _sampleRate, _baudRate, _rrcAlpha);
fir.init(&demod.out, &rrc);
recov.init(&fir.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
out = &recov.out;
generic_hier_block<GFSKDemod>::registerBlock(&demod);
generic_hier_block<GFSKDemod>::registerBlock(&fir);
generic_hier_block<GFSKDemod>::registerBlock(&recov);
generic_hier_block<GFSKDemod>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert((generic_hier_block<GFSKDemod>::_block_init));
demod.setInput(input);
}
void setSampleRate(float sampleRate) {
assert(generic_hier_block<GFSKDemod>::_block_init);
generic_hier_block<GFSKDemod>::tempStop();
_sampleRate = sampleRate;
demod.setSampleRate(_sampleRate);
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
rrc.setSampleRate(_sampleRate);
fir.updateWindow(&rrc);
generic_hier_block<GFSKDemod>::tempStart();
}
void setDeviation(float deviation) {
assert(generic_hier_block<GFSKDemod>::_block_init);
_deviation = deviation;
demod.setDeviation(deviation);
}
void setRRCAlpha(float rrcAlpha) {
assert(generic_hier_block<GFSKDemod>::_block_init);
_rrcAlpha = rrcAlpha;
rrc.setAlpha(_rrcAlpha);
fir.updateWindow(&rrc);
}
void setBaudRate(float baudRate, float omegaRelLimit) {
assert(generic_hier_block<GFSKDemod>::_block_init);
_baudRate = baudRate;
_omegaRelLimit = omegaRelLimit;
generic_hier_block<GFSKDemod>::tempStop();
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
rrc.setBaudRate(_baudRate);
fir.updateWindow(&rrc);
generic_hier_block<GFSKDemod>::tempStart();
}
void setMMGains(float omegaGain, float myGain) {
assert(generic_hier_block<GFSKDemod>::_block_init);
_omegaGain = omegaGain;
_muGain = myGain;
recov.setGains(_omegaGain, _muGain);
}
void setOmegaRelLimit(float omegaRelLimit) {
assert(generic_hier_block<GFSKDemod>::_block_init);
_omegaRelLimit = omegaRelLimit;
recov.setOmegaRelLimit(_omegaRelLimit);
}
stream<float>* out = NULL;
private:
FloatFMDemod demod;
RRCTaps rrc;
FIR<float> fir;
MMClockRecovery<float> recov;
float _sampleRate;
float _deviation;
float _rrcAlpha;
float _baudRate;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
template<int ORDER, bool OFFSET>
class PSKDemod : public generic_hier_block<PSKDemod<ORDER, OFFSET>> {
public:
PSKDemod() {}
PSKDemod(stream<complex_t>* input, float sampleRate, float baudRate, int RRCTapCount = 31, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
init(input, sampleRate, baudRate, RRCTapCount, RRCAlpha, agcRate, costasLoopBw, omegaGain, muGain, omegaRelLimit);
}
void init(stream<complex_t>* input, float sampleRate, float baudRate, int RRCTapCount = 31, float RRCAlpha = 0.32f, float agcRate = 10e-4, float costasLoopBw = 0.004f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
_RRCTapCount = RRCTapCount;
_RRCAlpha = RRCAlpha;
_sampleRate = sampleRate;
_agcRate = agcRate;
_costasLoopBw = costasLoopBw;
_baudRate = baudRate;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
agc.init(input, 1.0f, 65535, _agcRate);
taps.init(_RRCTapCount, _sampleRate, _baudRate, _RRCAlpha);
rrc.init(&agc.out, &taps);
demod.init(&rrc.out, _costasLoopBw);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&agc);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&rrc);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&demod);
if constexpr (OFFSET) {
delay.init(&demod.out);
recov.init(&delay.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&delay);
}
else {
recov.init(&demod.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
}
generic_hier_block<PSKDemod<ORDER, OFFSET>>::registerBlock(&recov);
out = &recov.out;
generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
agc.setInput(input);
}
void setSampleRate(float sampleRate) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_sampleRate = sampleRate;
rrc.tempStop();
recov.tempStop();
taps.setSampleRate(_sampleRate);
rrc.updateWindow(&taps);
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
rrc.tempStart();
recov.tempStart();
}
void setBaudRate(float baudRate) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_baudRate = baudRate;
rrc.tempStop();
recov.tempStop();
taps.setBaudRate(_baudRate);
rrc.updateWindow(&taps);
recov.setOmega(_sampleRate / _baudRate, _omegaRelLimit);
rrc.tempStart();
recov.tempStart();
}
void setRRCParams(int RRCTapCount, float RRCAlpha) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_RRCTapCount = RRCTapCount;
_RRCAlpha = RRCAlpha;
taps.setTapCount(_RRCTapCount);
taps.setAlpha(RRCAlpha);
rrc.updateWindow(&taps);
}
void setAgcRate(float agcRate) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_agcRate = agcRate;
agc.setRate(_agcRate);
}
void setCostasLoopBw(float costasLoopBw) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_costasLoopBw = costasLoopBw;
demod.setLoopBandwidth(_costasLoopBw);
}
void setMMGains(float omegaGain, float myGain) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_omegaGain = omegaGain;
_muGain = myGain;
recov.setGains(_omegaGain, _muGain);
}
void setOmegaRelLimit(float omegaRelLimit) {
assert((generic_hier_block<PSKDemod<ORDER, OFFSET>>::_block_init));
_omegaRelLimit = omegaRelLimit;
recov.setOmegaRelLimit(_omegaRelLimit);
}
stream<complex_t>* out = NULL;
private:
dsp::ComplexAGC agc;
dsp::RRCTaps taps;
dsp::FIR<dsp::complex_t> rrc;
CostasLoop<ORDER> demod;
DelayImag delay;
MMClockRecovery<dsp::complex_t> recov;
int _RRCTapCount;
float _RRCAlpha;
float _sampleRate;
float _agcRate;
float _baudRate;
float _costasLoopBw;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
class PMDemod : public generic_hier_block<PMDemod> {
public:
PMDemod() {}
PMDemod(stream<complex_t>* input, float sampleRate, float baudRate, float agcRate = 0.02e-3f, float pllLoopBandwidth = (0.06f*0.06f) / 4.0f, int rrcTapCount = 31, float rrcAlpha = 0.6f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
init(input, sampleRate, baudRate, agcRate, pllLoopBandwidth, rrcTapCount, rrcAlpha, omegaGain, muGain, omegaRelLimit);
}
void init(stream<complex_t>* input, float sampleRate, float baudRate, float agcRate = 0.02e-3f, float pllLoopBandwidth = (0.06f*0.06f) / 4.0f, int rrcTapCount = 31, float rrcAlpha = 0.6f, float omegaGain = (0.01*0.01) / 4, float muGain = 0.01f, float omegaRelLimit = 0.005f) {
_sampleRate = sampleRate;
_baudRate = baudRate;
_agcRate = agcRate;
_pllLoopBandwidth = pllLoopBandwidth;
_rrcTapCount = rrcTapCount;
_rrcAlpha = rrcAlpha;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
agc.init(input, 1.0f, 65535, _agcRate);
pll.init(&agc.out, _pllLoopBandwidth);
rrcwin.init(_rrcTapCount, _sampleRate, _baudRate, _rrcAlpha);
rrc.init(&pll.out, &rrcwin);
recov.init(&rrc.out, _sampleRate / _baudRate, _omegaGain, _muGain, _omegaRelLimit);
out = &recov.out;
generic_hier_block<PMDemod>::registerBlock(&agc);
generic_hier_block<PMDemod>::registerBlock(&pll);
generic_hier_block<PMDemod>::registerBlock(&rrc);
generic_hier_block<PMDemod>::registerBlock(&recov);
generic_hier_block<PMDemod>::_block_init = true;
}
void setInput(stream<complex_t>* input) {
assert(generic_hier_block<PMDemod>::_block_init);
agc.setInput(input);
}
void setAgcRate(float agcRate) {
assert(generic_hier_block<PMDemod>::_block_init);
_agcRate = agcRate;
agc.setRate(_agcRate);
}
void setPllLoopBandwidth(float pllLoopBandwidth) {
assert(generic_hier_block<PMDemod>::_block_init);
_pllLoopBandwidth = pllLoopBandwidth;
pll.setLoopBandwidth(_pllLoopBandwidth);
}
void setRRCParams(int rrcTapCount, float rrcAlpha) {
assert(generic_hier_block<PMDemod>::_block_init);
_rrcTapCount = rrcTapCount;
_rrcAlpha = rrcAlpha;
rrcwin.setTapCount(_rrcTapCount);
rrcwin.setAlpha(_rrcAlpha);
rrc.updateWindow(&rrcwin);
}
void setMMGains(float omegaGain, float muGain) {
assert(generic_hier_block<PMDemod>::_block_init);
_omegaGain = omegaGain;
_muGain = muGain;
recov.setGains(_omegaGain, _muGain);
}
void setOmegaRelLimit(float omegaRelLimit) {
assert(generic_hier_block<PMDemod>::_block_init);
_omegaRelLimit = omegaRelLimit;
recov.setOmegaRelLimit(_omegaRelLimit);
}
stream<float>* out = NULL;
private:
dsp::ComplexAGC agc;
dsp::CarrierTrackingPLL<float> pll;
dsp::RRCTaps rrcwin;
dsp::FIR<float> rrc;
dsp::MMClockRecovery<float> recov;
float _sampleRate;
float _baudRate;
float _agcRate;
float _pllLoopBandwidth;
int _rrcTapCount;
float _rrcAlpha;
float _omegaGain;
float _muGain;
float _omegaRelLimit;
};
class StereoFMDemod : public generic_hier_block<StereoFMDemod> {
public:
StereoFMDemod() {}
StereoFMDemod(stream<complex_t>* input, float sampleRate, float deviation) {
init(input, sampleRate, deviation);
}
void init(stream<complex_t>* input, float sampleRate, float deviation) {
_sampleRate = sampleRate;
PilotFirWin.init(18750, 19250, 3000, _sampleRate);
demod.init(input, _sampleRate, deviation);
r2c.init(&demod.out);
pilotFilter.init(&r2c.out, &PilotFirWin);
demux.init(&pilotFilter.dataOut, &pilotFilter.pilotOut, 0.1f);
recon.init(&demux.AplusBOut, &demux.AminusBOut);
out = &recon.out;
generic_hier_block<StereoFMDemod>::registerBlock(&demod);
generic_hier_block<StereoFMDemod>::registerBlock(&r2c);
generic_hier_block<StereoFMDemod>::registerBlock(&pilotFilter);
generic_hier_block<StereoFMDemod>::registerBlock(&demux);
generic_hier_block<StereoFMDemod>::registerBlock(&recon);
generic_hier_block<StereoFMDemod>::_block_init = true;
}
void setInput(stream<float>* input) {
assert(generic_hier_block<StereoFMDemod>::_block_init);
r2c.setInput(input);
}
void setDeviation(float deviation) {
demod.setDeviation(deviation);
}
stream<stereo_t>* out = NULL;
private:
filter_window::BandPassBlackmanWindow PilotFirWin;
FloatFMDemod demod;
RealToComplex r2c;
FMStereoDemuxPilotFilter pilotFilter;
FMStereoDemux demux;
FMStereoReconstruct recon;
float _sampleRate;
};
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "../processor.h"
namespace dsp::digital {
class BinarySlicer : public Processor<float, uint8_t> {
using base_type = Processor<float, uint8_t>;
public:
BinarySlicer() {}
BinarySlicer(stream<float> *in) { base_type::init(in); }
static inline int process(int count, const float* in, uint8_t* out) {
// TODO: Switch to volk
for (int i = 0; i < count; i++) {
out[i] = in[i] > 0.0f;
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -0,0 +1,65 @@
#pragma once
#include "../processor.h"
namespace dsp::digital {
class DifferentialDecoder : public Processor<uint8_t, uint8_t> {
using base_type = Processor<uint8_t, uint8_t>;
public:
DifferentialDecoder() {}
DifferentialDecoder(stream<uint8_t> *in) { base_type::init(in); }
void init(stream<uint8_t> *in, uint8_t modulus, uint8_t initSym = 0) {
_modulus = modulus;
_initSym = initSym;
last = _initSym;
base_type::init(in);
}
void setModulus(uint8_t modulus) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_modulus = modulus;
}
void setInitSym(uint8_t initSym) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_initSym = initSym;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
last = _initSym;
base_type::tempStart();
}
inline int process(int count, const uint8_t* in, uint8_t* out) {
for (int i = 0; i < count; i++) {
out[i] = (in[i] - last + _modulus) % _modulus;
last = in[i];
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
uint8_t last;
uint8_t _initSym;
uint8_t _modulus;
};
}

View File

@ -0,0 +1,47 @@
#pragma once
#include "../processor.h"
namespace dsp::digital {
class ManchesterDecoder : public Processor<uint8_t, uint8_t> {
using base_type = Processor<uint8_t, uint8_t>;
public:
ManchesterDecoder() {}
ManchesterDecoder(stream<uint8_t> *in) { base_type::init(in); }
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
base_type::tempStart();
}
inline int process(int count, const uint8_t* in, uint8_t* out) {
// TODO: NOT THIS BULLSHIT
int outCount = 0;
for (; offset < count; offset += 2) {
out[outCount++] = in[offset];
}
offset -= count;
return outCount;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
int offset = 0;
};
}

View File

@ -1,134 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <inttypes.h>
// WTF???
extern "C"
{
#include <correct.h>
}
const uint8_t toDB[] = {
0x00, 0x7b, 0xaf, 0xd4, 0x99, 0xe2, 0x36, 0x4d, 0xfa, 0x81, 0x55, 0x2e, 0x63, 0x18, 0xcc, 0xb7, 0x86, 0xfd, 0x29, 0x52, 0x1f,
0x64, 0xb0, 0xcb, 0x7c, 0x07, 0xd3, 0xa8, 0xe5, 0x9e, 0x4a, 0x31, 0xec, 0x97, 0x43, 0x38, 0x75, 0x0e, 0xda, 0xa1, 0x16, 0x6d, 0xb9, 0xc2, 0x8f, 0xf4,
0x20, 0x5b, 0x6a, 0x11, 0xc5, 0xbe, 0xf3, 0x88, 0x5c, 0x27, 0x90, 0xeb, 0x3f, 0x44, 0x09, 0x72, 0xa6, 0xdd, 0xef, 0x94, 0x40, 0x3b, 0x76, 0x0d, 0xd9,
0xa2, 0x15, 0x6e, 0xba, 0xc1, 0x8c, 0xf7, 0x23, 0x58, 0x69, 0x12, 0xc6, 0xbd, 0xf0, 0x8b, 0x5f, 0x24, 0x93, 0xe8, 0x3c, 0x47, 0x0a, 0x71, 0xa5, 0xde,
0x03, 0x78, 0xac, 0xd7, 0x9a, 0xe1, 0x35, 0x4e, 0xf9, 0x82, 0x56, 0x2d, 0x60, 0x1b, 0xcf, 0xb4, 0x85, 0xfe, 0x2a, 0x51, 0x1c, 0x67, 0xb3, 0xc8, 0x7f,
0x04, 0xd0, 0xab, 0xe6, 0x9d, 0x49, 0x32, 0x8d, 0xf6, 0x22, 0x59, 0x14, 0x6f, 0xbb, 0xc0, 0x77, 0x0c, 0xd8, 0xa3, 0xee, 0x95, 0x41, 0x3a, 0x0b, 0x70,
0xa4, 0xdf, 0x92, 0xe9, 0x3d, 0x46, 0xf1, 0x8a, 0x5e, 0x25, 0x68, 0x13, 0xc7, 0xbc, 0x61, 0x1a, 0xce, 0xb5, 0xf8, 0x83, 0x57, 0x2c, 0x9b, 0xe0, 0x34,
0x4f, 0x02, 0x79, 0xad, 0xd6, 0xe7, 0x9c, 0x48, 0x33, 0x7e, 0x05, 0xd1, 0xaa, 0x1d, 0x66, 0xb2, 0xc9, 0x84, 0xff, 0x2b, 0x50, 0x62, 0x19, 0xcd, 0xb6,
0xfb, 0x80, 0x54, 0x2f, 0x98, 0xe3, 0x37, 0x4c, 0x01, 0x7a, 0xae, 0xd5, 0xe4, 0x9f, 0x4b, 0x30, 0x7d, 0x06, 0xd2, 0xa9, 0x1e, 0x65, 0xb1, 0xca, 0x87,
0xfc, 0x28, 0x53, 0x8e, 0xf5, 0x21, 0x5a, 0x17, 0x6c, 0xb8, 0xc3, 0x74, 0x0f, 0xdb, 0xa0, 0xed, 0x96, 0x42, 0x39, 0x08, 0x73, 0xa7, 0xdc, 0x91, 0xea,
0x3e, 0x45, 0xf2, 0x89, 0x5d, 0x26, 0x6b, 0x10, 0xc4, 0xbf
};
const uint8_t fromDB[] = {
0x00, 0xcc, 0xac, 0x60, 0x79, 0xb5, 0xd5, 0x19, 0xf0, 0x3c, 0x5c, 0x90, 0x89, 0x45, 0x25, 0xe9, 0xfd, 0x31, 0x51, 0x9d,
0x84, 0x48, 0x28, 0xe4, 0x0d, 0xc1, 0xa1, 0x6d, 0x74, 0xb8, 0xd8, 0x14, 0x2e, 0xe2, 0x82, 0x4e, 0x57, 0x9b, 0xfb, 0x37, 0xde, 0x12, 0x72, 0xbe, 0xa7,
0x6b, 0x0b, 0xc7, 0xd3, 0x1f, 0x7f, 0xb3, 0xaa, 0x66, 0x06, 0xca, 0x23, 0xef, 0x8f, 0x43, 0x5a, 0x96, 0xf6, 0x3a, 0x42, 0x8e, 0xee, 0x22, 0x3b, 0xf7,
0x97, 0x5b, 0xb2, 0x7e, 0x1e, 0xd2, 0xcb, 0x07, 0x67, 0xab, 0xbf, 0x73, 0x13, 0xdf, 0xc6, 0x0a, 0x6a, 0xa6, 0x4f, 0x83, 0xe3, 0x2f, 0x36, 0xfa, 0x9a,
0x56, 0x6c, 0xa0, 0xc0, 0x0c, 0x15, 0xd9, 0xb9, 0x75, 0x9c, 0x50, 0x30, 0xfc, 0xe5, 0x29, 0x49, 0x85, 0x91, 0x5d, 0x3d, 0xf1, 0xe8, 0x24, 0x44, 0x88,
0x61, 0xad, 0xcd, 0x01, 0x18, 0xd4, 0xb4, 0x78, 0xc5, 0x09, 0x69, 0xa5, 0xbc, 0x70, 0x10, 0xdc, 0x35, 0xf9, 0x99, 0x55, 0x4c, 0x80, 0xe0, 0x2c, 0x38,
0xf4, 0x94, 0x58, 0x41, 0x8d, 0xed, 0x21, 0xc8, 0x04, 0x64, 0xa8, 0xb1, 0x7d, 0x1d, 0xd1, 0xeb, 0x27, 0x47, 0x8b, 0x92, 0x5e, 0x3e, 0xf2, 0x1b, 0xd7,
0xb7, 0x7b, 0x62, 0xae, 0xce, 0x02, 0x16, 0xda, 0xba, 0x76, 0x6f, 0xa3, 0xc3, 0x0f, 0xe6, 0x2a, 0x4a, 0x86, 0x9f, 0x53, 0x33, 0xff, 0x87, 0x4b, 0x2b,
0xe7, 0xfe, 0x32, 0x52, 0x9e, 0x77, 0xbb, 0xdb, 0x17, 0x0e, 0xc2, 0xa2, 0x6e, 0x7a, 0xb6, 0xd6, 0x1a, 0x03, 0xcf, 0xaf, 0x63, 0x8a, 0x46, 0x26, 0xea,
0xf3, 0x3f, 0x5f, 0x93, 0xa9, 0x65, 0x05, 0xc9, 0xd0, 0x1c, 0x7c, 0xb0, 0x59, 0x95, 0xf5, 0x39, 0x20, 0xec, 0x8c, 0x40, 0x54, 0x98, 0xf8, 0x34, 0x2d,
0xe1, 0x81, 0x4d, 0xa4, 0x68, 0x08, 0xc4, 0xdd, 0x11, 0x71, 0xbd
};
const uint8_t randVals[] = {
0xFF, 0x48, 0x0E, 0xC0, 0x9A, 0x0D, 0x70, 0xBC, 0x8E, 0x2C, 0x93, 0xAD, 0xA7, 0xB7, 0x46, 0xCE,
0x5A, 0x97, 0x7D, 0xCC, 0x32, 0xA2, 0xBF, 0x3E, 0x0A, 0x10, 0xF1, 0x88, 0x94, 0xCD, 0xEA, 0xB1,
0xFE, 0x90, 0x1D, 0x81, 0x34, 0x1A, 0xE1, 0x79, 0x1C, 0x59, 0x27, 0x5B, 0x4F, 0x6E, 0x8D, 0x9C,
0xB5, 0x2E, 0xFB, 0x98, 0x65, 0x45, 0x7E, 0x7C, 0x14, 0x21, 0xE3, 0x11, 0x29, 0x9B, 0xD5, 0x63,
0xFD, 0x20, 0x3B, 0x02, 0x68, 0x35, 0xC2, 0xF2, 0x38, 0xB2, 0x4E, 0xB6, 0x9E, 0xDD, 0x1B, 0x39,
0x6A, 0x5D, 0xF7, 0x30, 0xCA, 0x8A, 0xFC, 0xF8, 0x28, 0x43, 0xC6, 0x22, 0x53, 0x37, 0xAA, 0xC7,
0xFA, 0x40, 0x76, 0x04, 0xD0, 0x6B, 0x85, 0xE4, 0x71, 0x64, 0x9D, 0x6D, 0x3D, 0xBA, 0x36, 0x72,
0xD4, 0xBB, 0xEE, 0x61, 0x95, 0x15, 0xF9, 0xF0, 0x50, 0x87, 0x8C, 0x44, 0xA6, 0x6F, 0x55, 0x8F,
0xF4, 0x80, 0xEC, 0x09, 0xA0, 0xD7, 0x0B, 0xC8, 0xE2, 0xC9, 0x3A, 0xDA, 0x7B, 0x74, 0x6C, 0xE5,
0xA9, 0x77, 0xDC, 0xC3, 0x2A, 0x2B, 0xF3, 0xE0, 0xA1, 0x0F, 0x18, 0x89, 0x4C, 0xDE, 0xAB, 0x1F,
0xE9, 0x01, 0xD8, 0x13, 0x41, 0xAE, 0x17, 0x91, 0xC5, 0x92, 0x75, 0xB4, 0xF6, 0xE8, 0xD9, 0xCB,
0x52, 0xEF, 0xB9, 0x86, 0x54, 0x57, 0xE7, 0xC1, 0x42, 0x1E, 0x31, 0x12, 0x99, 0xBD, 0x56, 0x3F,
0xD2, 0x03, 0xB0, 0x26, 0x83, 0x5C, 0x2F, 0x23, 0x8B, 0x24, 0xEB, 0x69, 0xED, 0xD1, 0xB3, 0x96,
0xA5, 0xDF, 0x73, 0x0C, 0xA8, 0xAF, 0xCF, 0x82, 0x84, 0x3C, 0x62, 0x25, 0x33, 0x7A, 0xAC, 0x7F,
0xA4, 0x07, 0x60, 0x4D, 0x06, 0xB8, 0x5E, 0x47, 0x16, 0x49, 0xD6, 0xD3, 0xDB, 0xA3, 0x67, 0x2D,
0x4B, 0xBE, 0xE6, 0x19, 0x51, 0x5F, 0x9F, 0x05, 0x08, 0x78, 0xC4, 0x4A, 0x66, 0xF5, 0x58
};
namespace dsp {
class FalconRS : public generic_block<FalconRS> {
public:
FalconRS() {}
FalconRS(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
for (int i = 0; i < 5; i++) { memset(buffers[i], 0, 255); }
for (int i = 0; i < 5; i++) { memset(outBuffers[i], 0, 255); }
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 120, 11, 16);
if (rs == NULL) { printf("Error creating the reed solomon decoder\n"); }
generic_block<FalconRS>::registerInput(_in);
generic_block<FalconRS>::registerOutput(&out);
generic_block<FalconRS>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<FalconRS>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FalconRS>::ctrlMtx);
generic_block<FalconRS>::tempStop();
generic_block<FalconRS>::unregisterInput(_in);
_in = in;
generic_block<FalconRS>::registerInput(_in);
generic_block<FalconRS>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
uint8_t* data = _in->readBuf + 4;
// Deinterleave
for (int i = 0; i < 255*5; i++) {
buffers[i%5][i/5] = fromDB[data[i]];
}
// Reed the solomon :weary:
int result = 0;
result = correct_reed_solomon_decode(rs, buffers[0], 255, outBuffers[0]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[1], 255, outBuffers[1]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[2], 255, outBuffers[2]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[3], 255, outBuffers[3]);
if (result == -1) { _in->flush(); return count; }
result = correct_reed_solomon_decode(rs, buffers[4], 255, outBuffers[4]);
if (result == -1) { _in->flush(); return count; }
// Reinterleave
for (int i = 0; i < 255*5; i++) {
out.writeBuf[i] = toDB[outBuffers[i%5][i/5]] ^ randVals[i % 255];
}
out.swap(255*5);
_in->flush();
return count;
}
stream<uint8_t> out;
private:
int count;
uint8_t buffers[5][255];
uint8_t outBuffers[5][255];
correct_reed_solomon* rs;
stream<uint8_t>* _in;
};
}

View File

@ -1,128 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <inttypes.h>
namespace dsp {
struct FalconFrameHeader {
uint32_t counter;
uint16_t packet;
};
class FalconPacketSync : public generic_block<FalconPacketSync> {
public:
FalconPacketSync() {}
FalconPacketSync(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<FalconPacketSync>::registerInput(_in);
generic_block<FalconPacketSync>::registerOutput(&out);
generic_block<FalconPacketSync>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<FalconPacketSync>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FalconPacketSync>::ctrlMtx);
generic_block<FalconPacketSync>::tempStop();
generic_block<FalconPacketSync>::unregisterInput(_in);
_in = in;
generic_block<FalconPacketSync>::registerInput(_in);
generic_block<FalconPacketSync>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
// Parse frame header
FalconFrameHeader header;
header.packet = (_in->readBuf[3] | ((_in->readBuf[2] & 0b111) << 8));
header.counter = ((_in->readBuf[2] >> 3) | (_in->readBuf[1] << 5) | ((_in->readBuf[0] & 0b111111) << 13));
// Pointer to the data aera of the frame
uint8_t* data = _in->readBuf + 4;
int dataLen = 1191;
// If a frame was missed, cancel reading the current packet
if (lastCounter + 1 != header.counter) {
packetRead = -1;
}
lastCounter = header.counter;
// If frame is just a continuation of a single packet, save it
// If we're not currently reading a packet
if (header.packet == 2047 && packetRead >= 0) {
memcpy(packet + packetRead, data, dataLen);
packetRead += dataLen;
_in->flush();
printf("Wow, all data\n");
return count;
}
else if (header.packet == 2047) {
printf("Wow, all data\n");
_in->flush();
return count;
}
// Finish reading the last package and send it
if (packetRead >= 0) {
memcpy(packet + packetRead, data, header.packet);
memcpy(out.writeBuf, packet, packetRead + header.packet);
out.swap(packetRead + header.packet);
packetRead = -1;
}
// Iterate through every packet of the frame
for (int i = header.packet; i < dataLen;) {
// First, check if we can read the header. If not, save and wait for next frame
if (dataLen - i < 4) {
packetRead = dataLen - i;
memcpy(packet, &data[i], packetRead);
break;
}
// Extract packet length
uint16_t length = (((data[i] & 0b1111) << 8) | data[i + 1]) + 2;
// Check if it's not an invalid zero length packet
if (length <= 2) {
packetRead = -1;
break;
}
uint64_t pktId = ((uint64_t)data[i + 2] << 56) | ((uint64_t)data[i + 3] << 48) | ((uint64_t)data[i + 4] << 40) | ((uint64_t)data[i + 5] << 32)
| ((uint64_t)data[i + 6] << 24) | ((uint64_t)data[i + 7] << 16) | ((uint64_t)data[i + 8] << 8) | data[i + 9];
// If the packet doesn't fit the frame, save and go to next frame
if (dataLen - i < length) {
packetRead = dataLen - i;
memcpy(packet, &data[i], packetRead);
break;
}
// Here, the package fits fully, read it and jump to the next
memcpy(out.writeBuf, &data[i], length);
out.swap(length);
i += length;
}
_in->flush();
return count;
}
stream<uint8_t> out;
private:
int count;
uint32_t lastCounter = 0;
int packetRead = -1;
uint8_t packet[0x4008];
stream<uint8_t>* _in;
};
}

View File

@ -1,272 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <string.h>
namespace dsp {
template <class T>
class FIR : public generic_block<FIR<T>> {
public:
FIR() {}
FIR(stream<T>* in, dsp::filter_window::generic_window* window) { init(in, window); }
~FIR() {
if (!generic_block<FIR<T>>::_block_init) { return; }
generic_block<FIR<T>>::stop();
volk_free(buffer);
volk_free(taps);
generic_block<FIR<T>>::_block_init = false;
}
void init(stream<T>* in, dsp::filter_window::generic_window* window) {
_in = in;
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount);
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
bufStart = &buffer[tapCount];
generic_block<FIR<T>>::registerInput(_in);
generic_block<FIR<T>>::registerOutput(&out);
generic_block<FIR<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<FIR<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
generic_block<FIR<T>>::tempStop();
generic_block<FIR<T>>::unregisterInput(_in);
_in = in;
generic_block<FIR<T>>::registerInput(_in);
generic_block<FIR<T>>::tempStart();
}
void updateWindow(dsp::filter_window::generic_window* window) {
assert(generic_block<FIR<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FIR<T>>::ctrlMtx);
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
bufStart = &buffer[tapCount];
window->createTaps(taps, tapCount);
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
generic_block<FIR<T>>::ctrlMtx.lock();
memcpy(bufStart, _in->readBuf, count * sizeof(T));
_in->flush();
if constexpr (std::is_same_v<T, float>) {
for (int i = 0; i < count; i++) {
volk_32f_x2_dot_prod_32f((float*)&out.writeBuf[i], (float*)&buffer[i+1], taps, tapCount);
}
}
if constexpr (std::is_same_v<T, complex_t>) {
for (int i = 0; i < count; i++) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], taps, tapCount);
}
}
if (!out.swap(count)) { return -1; }
memmove(buffer, &buffer[count], tapCount * sizeof(T));
generic_block<FIR<T>>::ctrlMtx.unlock();
return count;
}
stream<T> out;
private:
stream<T>* _in;
dsp::filter_window::generic_window* _window;
T* bufStart;
T* buffer;
int tapCount;
float* taps;
};
class ComplexFIR : public generic_block<ComplexFIR> {
public:
ComplexFIR() {}
ComplexFIR(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) { init(in, window); }
~ComplexFIR() {
if (!generic_block<ComplexFIR>::_block_init) { return; }
generic_block<ComplexFIR>::stop();
volk_free(buffer);
volk_free(taps);
generic_block<ComplexFIR>::_block_init = false;
}
void init(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) {
_in = in;
tapCount = window->getTapCount();
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
window->createTaps(taps, tapCount);
buffer = (complex_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(complex_t) * 2, volk_get_alignment());
bufStart = &buffer[tapCount];
generic_block<ComplexFIR>::registerInput(_in);
generic_block<ComplexFIR>::registerOutput(&out);
generic_block<ComplexFIR>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexFIR>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexFIR>::ctrlMtx);
generic_block<ComplexFIR>::tempStop();
generic_block<ComplexFIR>::unregisterInput(_in);
_in = in;
generic_block<ComplexFIR>::registerInput(_in);
generic_block<ComplexFIR>::tempStart();
}
void updateWindow(dsp::filter_window::generic_complex_window* window) {
assert(generic_block<ComplexFIR>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexFIR>::ctrlMtx);
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
bufStart = &buffer[tapCount];
window->createTaps(taps, tapCount);
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
generic_block<ComplexFIR>::ctrlMtx.lock();
memcpy(bufStart, _in->readBuf, count * sizeof(complex_t));
_in->flush();
for (int i = 0; i < count; i++) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[i], (lv_32fc_t*)&buffer[i+1], (lv_32fc_t*)taps, tapCount);
}
if (!out.swap(count)) { return -1; }
memmove(buffer, &buffer[count], tapCount * sizeof(complex_t));
generic_block<ComplexFIR>::ctrlMtx.unlock();
return count;
}
stream<complex_t> out;
private:
stream<complex_t>* _in;
dsp::filter_window::generic_complex_window* _window;
complex_t* bufStart;
complex_t* buffer;
int tapCount;
complex_t* taps;
};
class BFMDeemp : public generic_block<BFMDeemp> {
public:
BFMDeemp() {}
BFMDeemp(stream<stereo_t>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
void init(stream<stereo_t>* in, float sampleRate, float tau) {
_in = in;
_sampleRate = sampleRate;
_tau = tau;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
generic_block<BFMDeemp>::registerInput(_in);
generic_block<BFMDeemp>::registerOutput(&out);
generic_block<BFMDeemp>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
assert(generic_block<BFMDeemp>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<BFMDeemp>::ctrlMtx);
generic_block<BFMDeemp>::tempStop();
generic_block<BFMDeemp>::unregisterInput(_in);
_in = in;
generic_block<BFMDeemp>::registerInput(_in);
generic_block<BFMDeemp>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<BFMDeemp>::_block_init);
_sampleRate = sampleRate;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
}
void setTau(float tau) {
assert(generic_block<BFMDeemp>::_block_init);
_tau = tau;
float dt = 1.0f / _sampleRate;
alpha = dt / (_tau + dt);
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
if (isnan(lastOutL)) {
lastOutL = 0.0f;
}
if (isnan(lastOutR)) {
lastOutR = 0.0f;
}
out.writeBuf[0].l = (alpha * _in->readBuf[0].l) + ((1-alpha) * lastOutL);
out.writeBuf[0].r = (alpha * _in->readBuf[0].r) + ((1-alpha) * lastOutR);
for (int i = 1; i < count; i++) {
out.writeBuf[i].l = (alpha * _in->readBuf[i].l) + ((1 - alpha) * out.writeBuf[i - 1].l);
out.writeBuf[i].r = (alpha * _in->readBuf[i].r) + ((1 - alpha) * out.writeBuf[i - 1].r);
}
lastOutL = out.writeBuf[count - 1].l;
lastOutR = out.writeBuf[count - 1].r;
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
bool bypass = false;
stream<stereo_t> out;
private:
int count;
float lastOutL = 0.0f;
float lastOutR = 0.0f;
float alpha;
float _tau;
float _sampleRate;
stream<stereo_t>* _in;
};
}

View File

@ -0,0 +1,88 @@
#pragma once
#include "fir.h"
namespace dsp::filter {
template <class D, class T>
class DecimatingFIR : public FIR<D, T> {
using base_type = FIR<D, T>;
public:
DecimatingFIR() {}
DecimatingFIR(stream<D>* in, tap<T>& taps, int decimation) { init(in, taps, decimation); }
void init(stream<D>* in, tap<T>& taps, int decimation) {
_decimation = decimation;
base_type::init(in, taps);
}
void setTaps(tap<T>& taps) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
base_type::setTaps(taps);
base_type::tempStart();
}
void setDecimation(int decimation) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_decimation = decimation;
offset = 0;
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
base_type::reset();
base_type::tempStart();
}
inline int process(int count, const D* in, D* out) {
// Copy data to work buffer
memcpy(base_type::bufStart, in, count * sizeof(D));
// Do convolution
int outCount = 0;
for (; offset < count; offset += _decimation) {
if constexpr (std::is_same_v<D, float> && std::is_same_v<T, float>) {
volk_32f_x2_dot_prod_32f(&out[outCount++], &base_type::buffer[offset], base_type::_taps.taps, base_type::_taps.size);
}
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, float>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[outCount++], (lv_32fc_t*)&base_type::buffer[offset], base_type::_taps.taps, base_type::_taps.size);
}
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, complex_t>) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[outCount++], (lv_32fc_t*)&base_type::buffer[offset], (lv_32fc_t*)base_type::_taps.taps, base_type::_taps.size);
}
}
offset -= count;
// Move unused data
memmove(base_type::buffer, &base_type::buffer[count], (base_type::_taps.size - 1) * sizeof(D));
return outCount;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
int outCount = process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (outCount) {
if (!base_type::out.swap(outCount)) { return -1; }
}
return outCount;
}
protected:
int _decimation;
int offset = 0;
};
}

View File

@ -0,0 +1,102 @@
#pragma once
#include "../processor.h"
namespace dsp::filter {
template<class T>
class Deemphasis : public Processor<T, T> {
using base_type = Processor<T, T>;
public:
Deemphasis() {}
Deemphasis(stream<T>* in, double tau, double samplerate) {}
void init(stream<T>* in, double tau, double samplerate) {
_tau = tau;
_samplerate = samplerate;
updateAlpha();
// Initialize state
if constexpr (std::is_same_v<T, float>) {
lastOut = 0;
}
if constexpr (std::is_same_v<T, stereo_t>) {
lastOut = { 0, 0 };
}
base_type::init(in);
}
void setTau(double tau) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_tau = tau;
updateAlpha();
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_samplerate = samplerate;
updateAlpha();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
if constexpr (std::is_same_v<T, float>) {
lastOut = 0;
}
if constexpr (std::is_same_v<T, stereo_t>) {
lastOut = { 0, 0 };
}
base_type::tempStart();
}
inline int process(int count, const T* in, T* out) {
if constexpr (std::is_same_v<T, float>) {
out[0] = (alpha * in[0]) + ((1 - alpha) * lastOut);
for (int i = 1; i < count; i++) {
out[i] = (alpha * in[i]) + ((1 - alpha) * out[i - 1]);
}
lastOut = out[count - 1];
}
if constexpr (std::is_same_v<T, stereo_t>) {
out[0].l = (alpha * in[0].l) + ((1 - alpha) * lastOut.l);
out[0].r = (alpha * in[0].r) + ((1 - alpha) * lastOut.r);
for (int i = 1; i < count; i++) {
out[i].l = (alpha * in[i].l) + ((1 - alpha) * out[i - 1].l);
out[i].r = (alpha * in[i].r) + ((1 - alpha) * out[i - 1].r);
}
lastOut.l = out[count - 1].l;
lastOut.r = out[count - 1].r;
}
return count;
}
//DEFAULT_PROC_RUN();
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
void updateAlpha() {
float dt = 1.0f / _samplerate;
alpha = dt / (_tau + dt);
}
double _tau;
double _samplerate;
float alpha;
T lastOut;
};
}

101
core/src/dsp/filter/fir.h Normal file
View File

@ -0,0 +1,101 @@
#pragma once
#include "../processor.h"
#include "../taps/tap.h"
namespace dsp::filter {
template <class D, class T>
class FIR : public Processor<D, D> {
using base_type = Processor<D, D>;
public:
FIR() {}
FIR(stream<D>* in, tap<T>& taps) { init(in, taps); }
~FIR() {
if (!base_type::_block_init) { return; }
base_type::stop();
buffer::free(buffer);
}
virtual void init(stream<D>* in, tap<T>& taps) {
_taps = taps;
// Allocate and clear buffer
buffer = buffer::alloc<D>(STREAM_BUFFER_SIZE + 64000);
bufStart = &buffer[_taps.size - 1];
buffer::clear<D>(buffer, _taps.size - 1);
base_type::init(in);
}
virtual void setTaps(tap<T>& taps) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
int oldTC = _taps.size;
_taps = taps;
// Update start of buffer
bufStart = &buffer[_taps.size - 1];
// Move existing data to make transition seemless
if (_taps.size < oldTC) {
memcpy(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
}
else if (_taps.size > oldTC) {
memcpy(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
buffer::clear<D>(buffer, _taps.size - oldTC);
}
base_type::tempStart();
}
virtual void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
buffer::clear<D>(buffer, _taps.size - 1);
base_type::tempStart();
}
inline int process(int count, const D* in, D* out) {
// Copy data to work buffer
memcpy(bufStart, in, count * sizeof(D));
// Do convolution
for (int i = 0; i < count; i++) {
if constexpr (std::is_same_v<D, float> && std::is_same_v<T, float>) {
volk_32f_x2_dot_prod_32f(&out[i], &buffer[i], _taps.taps, _taps.size);
}
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, float>) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out[i], (lv_32fc_t*)&buffer[i], _taps.taps, _taps.size);
}
if constexpr ((std::is_same_v<D, complex_t> || std::is_same_v<D, stereo_t>) && std::is_same_v<T, complex_t>) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&out[i], (lv_32fc_t*)&buffer[i], (lv_32fc_t*)_taps.taps, _taps.size);
}
}
// Move unused data
memmove(buffer, &buffer[count], (_taps.size - 1) * sizeof(D));
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
tap<T> _taps;
D* buffer;
D* bufStart;
};
}

83
core/src/dsp/hier_block.h Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include "block.h"
namespace dsp {
class hier_block : public generic_block {
public:
virtual void init() {}
virtual ~hier_block() {
if (!_block_init) { return; }
stop();
_block_init = false;
}
virtual void start() {
assert(_block_init);
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
if (running) {
return;
}
running = true;
doStart();
}
virtual void stop() {
assert(_block_init);
std::lock_guard<std::recursive_mutex> lck(ctrlMtx);
if (!running) {
return;
}
doStop();
running = false;
}
void tempStart() {
assert(_block_init);
if (!tempStopDepth || --tempStopDepth) { return; }
if (tempStopped) {
doStart();
tempStopped = false;
}
}
void tempStop() {
assert(_block_init);
if (tempStopDepth++) { return; }
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
private:
virtual void doStart() {
for (auto& block : blocks) {
block->start();
}
}
virtual void doStop() {
for (auto& block : blocks) {
block->stop();
}
}
std::vector<generic_block*> blocks;
bool tempStopped = false;
bool running = false;
int tempStopDepth = 0;
protected:
void registerBlock(generic_block* block) {
blocks.push_back(block);
}
void unregisterBlock(generic_block* block) {
blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
}
bool _block_init = false;
std::recursive_mutex ctrlMtx;
};
}

View File

@ -1,136 +0,0 @@
#pragma once
const int INTERP_TAP_COUNT = 8;
const int INTERP_STEPS = 128;
const float INTERP_TAPS[INTERP_STEPS + 1][INTERP_TAP_COUNT] = {
{ 0.00000e+00, 0.00000e+00, 0.00000e+00, 1.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00 },
{ -1.98993e-04, 1.24642e-03, -5.41054e-03, 9.98534e-01, 7.89295e-03, -2.76968e-03, 8.53777e-04, -1.54700e-04 },
{ -3.96391e-04, 2.47942e-03, -1.07209e-02, 9.96891e-01, 1.58840e-02, -5.55134e-03, 1.70888e-03, -3.09412e-04 },
{ -5.92100e-04, 3.69852e-03, -1.59305e-02, 9.95074e-01, 2.39714e-02, -8.34364e-03, 2.56486e-03, -4.64053e-04 },
{ -7.86031e-04, 4.90322e-03, -2.10389e-02, 9.93082e-01, 3.21531e-02, -1.11453e-02, 3.42130e-03, -6.18544e-04 },
{ -9.78093e-04, 6.09305e-03, -2.60456e-02, 9.90917e-01, 4.04274e-02, -1.39548e-02, 4.27773e-03, -7.72802e-04 },
{ -1.16820e-03, 7.26755e-03, -3.09503e-02, 9.88580e-01, 4.87921e-02, -1.67710e-02, 5.13372e-03, -9.26747e-04 },
{ -1.35627e-03, 8.42626e-03, -3.57525e-02, 9.86071e-01, 5.72454e-02, -1.95925e-02, 5.98883e-03, -1.08030e-03 },
{ -1.54221e-03, 9.56876e-03, -4.04519e-02, 9.83392e-01, 6.57852e-02, -2.24178e-02, 6.84261e-03, -1.23337e-03 },
{ -1.72594e-03, 1.06946e-02, -4.50483e-02, 9.80543e-01, 7.44095e-02, -2.52457e-02, 7.69462e-03, -1.38589e-03 },
{ -1.90738e-03, 1.18034e-02, -4.95412e-02, 9.77526e-01, 8.31162e-02, -2.80746e-02, 8.54441e-03, -1.53777e-03 },
{ -2.08645e-03, 1.28947e-02, -5.39305e-02, 9.74342e-01, 9.19033e-02, -3.09033e-02, 9.39154e-03, -1.68894e-03 },
{ -2.26307e-03, 1.39681e-02, -5.82159e-02, 9.70992e-01, 1.00769e-01, -3.37303e-02, 1.02356e-02, -1.83931e-03 },
{ -2.43718e-03, 1.50233e-02, -6.23972e-02, 9.67477e-01, 1.09710e-01, -3.65541e-02, 1.10760e-02, -1.98880e-03 },
{ -2.60868e-03, 1.60599e-02, -6.64743e-02, 9.63798e-01, 1.18725e-01, -3.93735e-02, 1.19125e-02, -2.13733e-03 },
{ -2.77751e-03, 1.70776e-02, -7.04471e-02, 9.59958e-01, 1.27812e-01, -4.21869e-02, 1.27445e-02, -2.28483e-03 },
{ -2.94361e-03, 1.80759e-02, -7.43154e-02, 9.55956e-01, 1.36968e-01, -4.49929e-02, 1.35716e-02, -2.43121e-03 },
{ -3.10689e-03, 1.90545e-02, -7.80792e-02, 9.51795e-01, 1.46192e-01, -4.77900e-02, 1.43934e-02, -2.57640e-03 },
{ -3.26730e-03, 2.00132e-02, -8.17385e-02, 9.47477e-01, 1.55480e-01, -5.05770e-02, 1.52095e-02, -2.72032e-03 },
{ -3.42477e-03, 2.09516e-02, -8.52933e-02, 9.43001e-01, 1.64831e-01, -5.33522e-02, 1.60193e-02, -2.86289e-03 },
{ -3.57923e-03, 2.18695e-02, -8.87435e-02, 9.38371e-01, 1.74242e-01, -5.61142e-02, 1.68225e-02, -3.00403e-03 },
{ -3.73062e-03, 2.27664e-02, -9.20893e-02, 9.33586e-01, 1.83711e-01, -5.88617e-02, 1.76185e-02, -3.14367e-03 },
{ -3.87888e-03, 2.36423e-02, -9.53307e-02, 9.28650e-01, 1.93236e-01, -6.15931e-02, 1.84071e-02, -3.28174e-03 },
{ -4.02397e-03, 2.44967e-02, -9.84679e-02, 9.23564e-01, 2.02814e-01, -6.43069e-02, 1.91877e-02, -3.41815e-03 },
{ -4.16581e-03, 2.53295e-02, -1.01501e-01, 9.18329e-01, 2.12443e-01, -6.70018e-02, 1.99599e-02, -3.55283e-03 },
{ -4.30435e-03, 2.61404e-02, -1.04430e-01, 9.12947e-01, 2.22120e-01, -6.96762e-02, 2.07233e-02, -3.68570e-03 },
{ -4.43955e-03, 2.69293e-02, -1.07256e-01, 9.07420e-01, 2.31843e-01, -7.23286e-02, 2.14774e-02, -3.81671e-03 },
{ -4.57135e-03, 2.76957e-02, -1.09978e-01, 9.01749e-01, 2.41609e-01, -7.49577e-02, 2.22218e-02, -3.94576e-03 },
{ -4.69970e-03, 2.84397e-02, -1.12597e-01, 8.95936e-01, 2.51417e-01, -7.75620e-02, 2.29562e-02, -4.07279e-03 },
{ -4.82456e-03, 2.91609e-02, -1.15113e-01, 8.89984e-01, 2.61263e-01, -8.01399e-02, 2.36801e-02, -4.19774e-03 },
{ -4.94589e-03, 2.98593e-02, -1.17526e-01, 8.83893e-01, 2.71144e-01, -8.26900e-02, 2.43930e-02, -4.32052e-03 },
{ -5.06363e-03, 3.05345e-02, -1.19837e-01, 8.77666e-01, 2.81060e-01, -8.52109e-02, 2.50946e-02, -4.44107e-03 },
{ -5.17776e-03, 3.11866e-02, -1.22047e-01, 8.71305e-01, 2.91006e-01, -8.77011e-02, 2.57844e-02, -4.55932e-03 },
{ -5.28823e-03, 3.18153e-02, -1.24154e-01, 8.64812e-01, 3.00980e-01, -9.01591e-02, 2.64621e-02, -4.67520e-03 },
{ -5.39500e-03, 3.24205e-02, -1.26161e-01, 8.58189e-01, 3.10980e-01, -9.25834e-02, 2.71272e-02, -4.78866e-03 },
{ -5.49804e-03, 3.30021e-02, -1.28068e-01, 8.51437e-01, 3.21004e-01, -9.49727e-02, 2.77794e-02, -4.89961e-03 },
{ -5.59731e-03, 3.35600e-02, -1.29874e-01, 8.44559e-01, 3.31048e-01, -9.73254e-02, 2.84182e-02, -5.00800e-03 },
{ -5.69280e-03, 3.40940e-02, -1.31581e-01, 8.37557e-01, 3.41109e-01, -9.96402e-02, 2.90433e-02, -5.11376e-03 },
{ -5.78446e-03, 3.46042e-02, -1.33189e-01, 8.30432e-01, 3.51186e-01, -1.01915e-01, 2.96543e-02, -5.21683e-03 },
{ -5.87227e-03, 3.50903e-02, -1.34699e-01, 8.23188e-01, 3.61276e-01, -1.04150e-01, 3.02507e-02, -5.31716e-03 },
{ -5.95620e-03, 3.55525e-02, -1.36111e-01, 8.15826e-01, 3.71376e-01, -1.06342e-01, 3.08323e-02, -5.41467e-03 },
{ -6.03624e-03, 3.59905e-02, -1.37426e-01, 8.08348e-01, 3.81484e-01, -1.08490e-01, 3.13987e-02, -5.50931e-03 },
{ -6.11236e-03, 3.64044e-02, -1.38644e-01, 8.00757e-01, 3.91596e-01, -1.10593e-01, 3.19495e-02, -5.60103e-03 },
{ -6.18454e-03, 3.67941e-02, -1.39767e-01, 7.93055e-01, 4.01710e-01, -1.12650e-01, 3.24843e-02, -5.68976e-03 },
{ -6.25277e-03, 3.71596e-02, -1.40794e-01, 7.85244e-01, 4.11823e-01, -1.14659e-01, 3.30027e-02, -5.77544e-03 },
{ -6.31703e-03, 3.75010e-02, -1.41727e-01, 7.77327e-01, 4.21934e-01, -1.16618e-01, 3.35046e-02, -5.85804e-03 },
{ -6.37730e-03, 3.78182e-02, -1.42566e-01, 7.69305e-01, 4.32038e-01, -1.18526e-01, 3.39894e-02, -5.93749e-03 },
{ -6.43358e-03, 3.81111e-02, -1.43313e-01, 7.61181e-01, 4.42134e-01, -1.20382e-01, 3.44568e-02, -6.01374e-03 },
{ -6.48585e-03, 3.83800e-02, -1.43968e-01, 7.52958e-01, 4.52218e-01, -1.22185e-01, 3.49066e-02, -6.08674e-03 },
{ -6.53412e-03, 3.86247e-02, -1.44531e-01, 7.44637e-01, 4.62289e-01, -1.23933e-01, 3.53384e-02, -6.15644e-03 },
{ -6.57836e-03, 3.88454e-02, -1.45004e-01, 7.36222e-01, 4.72342e-01, -1.25624e-01, 3.57519e-02, -6.22280e-03 },
{ -6.61859e-03, 3.90420e-02, -1.45387e-01, 7.27714e-01, 4.82377e-01, -1.27258e-01, 3.61468e-02, -6.28577e-03 },
{ -6.65479e-03, 3.92147e-02, -1.45682e-01, 7.19116e-01, 4.92389e-01, -1.28832e-01, 3.65227e-02, -6.34530e-03 },
{ -6.68698e-03, 3.93636e-02, -1.45889e-01, 7.10431e-01, 5.02377e-01, -1.30347e-01, 3.68795e-02, -6.40135e-03 },
{ -6.71514e-03, 3.94886e-02, -1.46009e-01, 7.01661e-01, 5.12337e-01, -1.31800e-01, 3.72167e-02, -6.45388e-03 },
{ -6.73929e-03, 3.95900e-02, -1.46043e-01, 6.92808e-01, 5.22267e-01, -1.33190e-01, 3.75341e-02, -6.50285e-03 },
{ -6.75943e-03, 3.96678e-02, -1.45993e-01, 6.83875e-01, 5.32164e-01, -1.34515e-01, 3.78315e-02, -6.54823e-03 },
{ -6.77557e-03, 3.97222e-02, -1.45859e-01, 6.74865e-01, 5.42025e-01, -1.35775e-01, 3.81085e-02, -6.58996e-03 },
{ -6.78771e-03, 3.97532e-02, -1.45641e-01, 6.65779e-01, 5.51849e-01, -1.36969e-01, 3.83650e-02, -6.62802e-03 },
{ -6.79588e-03, 3.97610e-02, -1.45343e-01, 6.56621e-01, 5.61631e-01, -1.38094e-01, 3.86006e-02, -6.66238e-03 },
{ -6.80007e-03, 3.97458e-02, -1.44963e-01, 6.47394e-01, 5.71370e-01, -1.39150e-01, 3.88151e-02, -6.69300e-03 },
{ -6.80032e-03, 3.97077e-02, -1.44503e-01, 6.38099e-01, 5.81063e-01, -1.40136e-01, 3.90083e-02, -6.71985e-03 },
{ -6.79662e-03, 3.96469e-02, -1.43965e-01, 6.28739e-01, 5.90706e-01, -1.41050e-01, 3.91800e-02, -6.74291e-03 },
{ -6.78902e-03, 3.95635e-02, -1.43350e-01, 6.19318e-01, 6.00298e-01, -1.41891e-01, 3.93299e-02, -6.76214e-03 },
{ -6.77751e-03, 3.94578e-02, -1.42658e-01, 6.09836e-01, 6.09836e-01, -1.42658e-01, 3.94578e-02, -6.77751e-03 },
{ -6.76214e-03, 3.93299e-02, -1.41891e-01, 6.00298e-01, 6.19318e-01, -1.43350e-01, 3.95635e-02, -6.78902e-03 },
{ -6.74291e-03, 3.91800e-02, -1.41050e-01, 5.90706e-01, 6.28739e-01, -1.43965e-01, 3.96469e-02, -6.79662e-03 },
{ -6.71985e-03, 3.90083e-02, -1.40136e-01, 5.81063e-01, 6.38099e-01, -1.44503e-01, 3.97077e-02, -6.80032e-03 },
{ -6.69300e-03, 3.88151e-02, -1.39150e-01, 5.71370e-01, 6.47394e-01, -1.44963e-01, 3.97458e-02, -6.80007e-03 },
{ -6.66238e-03, 3.86006e-02, -1.38094e-01, 5.61631e-01, 6.56621e-01, -1.45343e-01, 3.97610e-02, -6.79588e-03 },
{ -6.62802e-03, 3.83650e-02, -1.36969e-01, 5.51849e-01, 6.65779e-01, -1.45641e-01, 3.97532e-02, -6.78771e-03 },
{ -6.58996e-03, 3.81085e-02, -1.35775e-01, 5.42025e-01, 6.74865e-01, -1.45859e-01, 3.97222e-02, -6.77557e-03 },
{ -6.54823e-03, 3.78315e-02, -1.34515e-01, 5.32164e-01, 6.83875e-01, -1.45993e-01, 3.96678e-02, -6.75943e-03 },
{ -6.50285e-03, 3.75341e-02, -1.33190e-01, 5.22267e-01, 6.92808e-01, -1.46043e-01, 3.95900e-02, -6.73929e-03 },
{ -6.45388e-03, 3.72167e-02, -1.31800e-01, 5.12337e-01, 7.01661e-01, -1.46009e-01, 3.94886e-02, -6.71514e-03 },
{ -6.40135e-03, 3.68795e-02, -1.30347e-01, 5.02377e-01, 7.10431e-01, -1.45889e-01, 3.93636e-02, -6.68698e-03 },
{ -6.34530e-03, 3.65227e-02, -1.28832e-01, 4.92389e-01, 7.19116e-01, -1.45682e-01, 3.92147e-02, -6.65479e-03 },
{ -6.28577e-03, 3.61468e-02, -1.27258e-01, 4.82377e-01, 7.27714e-01, -1.45387e-01, 3.90420e-02, -6.61859e-03 },
{ -6.22280e-03, 3.57519e-02, -1.25624e-01, 4.72342e-01, 7.36222e-01, -1.45004e-01, 3.88454e-02, -6.57836e-03 },
{ -6.15644e-03, 3.53384e-02, -1.23933e-01, 4.62289e-01, 7.44637e-01, -1.44531e-01, 3.86247e-02, -6.53412e-03 },
{ -6.08674e-03, 3.49066e-02, -1.22185e-01, 4.52218e-01, 7.52958e-01, -1.43968e-01, 3.83800e-02, -6.48585e-03 },
{ -6.01374e-03, 3.44568e-02, -1.20382e-01, 4.42134e-01, 7.61181e-01, -1.43313e-01, 3.81111e-02, -6.43358e-03 },
{ -5.93749e-03, 3.39894e-02, -1.18526e-01, 4.32038e-01, 7.69305e-01, -1.42566e-01, 3.78182e-02, -6.37730e-03 },
{ -5.85804e-03, 3.35046e-02, -1.16618e-01, 4.21934e-01, 7.77327e-01, -1.41727e-01, 3.75010e-02, -6.31703e-03 },
{ -5.77544e-03, 3.30027e-02, -1.14659e-01, 4.11823e-01, 7.85244e-01, -1.40794e-01, 3.71596e-02, -6.25277e-03 },
{ -5.68976e-03, 3.24843e-02, -1.12650e-01, 4.01710e-01, 7.93055e-01, -1.39767e-01, 3.67941e-02, -6.18454e-03 },
{ -5.60103e-03, 3.19495e-02, -1.10593e-01, 3.91596e-01, 8.00757e-01, -1.38644e-01, 3.64044e-02, -6.11236e-03 },
{ -5.50931e-03, 3.13987e-02, -1.08490e-01, 3.81484e-01, 8.08348e-01, -1.37426e-01, 3.59905e-02, -6.03624e-03 },
{ -5.41467e-03, 3.08323e-02, -1.06342e-01, 3.71376e-01, 8.15826e-01, -1.36111e-01, 3.55525e-02, -5.95620e-03 },
{ -5.31716e-03, 3.02507e-02, -1.04150e-01, 3.61276e-01, 8.23188e-01, -1.34699e-01, 3.50903e-02, -5.87227e-03 },
{ -5.21683e-03, 2.96543e-02, -1.01915e-01, 3.51186e-01, 8.30432e-01, -1.33189e-01, 3.46042e-02, -5.78446e-03 },
{ -5.11376e-03, 2.90433e-02, -9.96402e-02, 3.41109e-01, 8.37557e-01, -1.31581e-01, 3.40940e-02, -5.69280e-03 },
{ -5.00800e-03, 2.84182e-02, -9.73254e-02, 3.31048e-01, 8.44559e-01, -1.29874e-01, 3.35600e-02, -5.59731e-03 },
{ -4.89961e-03, 2.77794e-02, -9.49727e-02, 3.21004e-01, 8.51437e-01, -1.28068e-01, 3.30021e-02, -5.49804e-03 },
{ -4.78866e-03, 2.71272e-02, -9.25834e-02, 3.10980e-01, 8.58189e-01, -1.26161e-01, 3.24205e-02, -5.39500e-03 },
{ -4.67520e-03, 2.64621e-02, -9.01591e-02, 3.00980e-01, 8.64812e-01, -1.24154e-01, 3.18153e-02, -5.28823e-03 },
{ -4.55932e-03, 2.57844e-02, -8.77011e-02, 2.91006e-01, 8.71305e-01, -1.22047e-01, 3.11866e-02, -5.17776e-03 },
{ -4.44107e-03, 2.50946e-02, -8.52109e-02, 2.81060e-01, 8.77666e-01, -1.19837e-01, 3.05345e-02, -5.06363e-03 },
{ -4.32052e-03, 2.43930e-02, -8.26900e-02, 2.71144e-01, 8.83893e-01, -1.17526e-01, 2.98593e-02, -4.94589e-03 },
{ -4.19774e-03, 2.36801e-02, -8.01399e-02, 2.61263e-01, 8.89984e-01, -1.15113e-01, 2.91609e-02, -4.82456e-03 },
{ -4.07279e-03, 2.29562e-02, -7.75620e-02, 2.51417e-01, 8.95936e-01, -1.12597e-01, 2.84397e-02, -4.69970e-03 },
{ -3.94576e-03, 2.22218e-02, -7.49577e-02, 2.41609e-01, 9.01749e-01, -1.09978e-01, 2.76957e-02, -4.57135e-03 },
{ -3.81671e-03, 2.14774e-02, -7.23286e-02, 2.31843e-01, 9.07420e-01, -1.07256e-01, 2.69293e-02, -4.43955e-03 },
{ -3.68570e-03, 2.07233e-02, -6.96762e-02, 2.22120e-01, 9.12947e-01, -1.04430e-01, 2.61404e-02, -4.30435e-03 },
{ -3.55283e-03, 1.99599e-02, -6.70018e-02, 2.12443e-01, 9.18329e-01, -1.01501e-01, 2.53295e-02, -4.16581e-03 },
{ -3.41815e-03, 1.91877e-02, -6.43069e-02, 2.02814e-01, 9.23564e-01, -9.84679e-02, 2.44967e-02, -4.02397e-03 },
{ -3.28174e-03, 1.84071e-02, -6.15931e-02, 1.93236e-01, 9.28650e-01, -9.53307e-02, 2.36423e-02, -3.87888e-03 },
{ -3.14367e-03, 1.76185e-02, -5.88617e-02, 1.83711e-01, 9.33586e-01, -9.20893e-02, 2.27664e-02, -3.73062e-03 },
{ -3.00403e-03, 1.68225e-02, -5.61142e-02, 1.74242e-01, 9.38371e-01, -8.87435e-02, 2.18695e-02, -3.57923e-03 },
{ -2.86289e-03, 1.60193e-02, -5.33522e-02, 1.64831e-01, 9.43001e-01, -8.52933e-02, 2.09516e-02, -3.42477e-03 },
{ -2.72032e-03, 1.52095e-02, -5.05770e-02, 1.55480e-01, 9.47477e-01, -8.17385e-02, 2.00132e-02, -3.26730e-03 },
{ -2.57640e-03, 1.43934e-02, -4.77900e-02, 1.46192e-01, 9.51795e-01, -7.80792e-02, 1.90545e-02, -3.10689e-03 },
{ -2.43121e-03, 1.35716e-02, -4.49929e-02, 1.36968e-01, 9.55956e-01, -7.43154e-02, 1.80759e-02, -2.94361e-03 },
{ -2.28483e-03, 1.27445e-02, -4.21869e-02, 1.27812e-01, 9.59958e-01, -7.04471e-02, 1.70776e-02, -2.77751e-03 },
{ -2.13733e-03, 1.19125e-02, -3.93735e-02, 1.18725e-01, 9.63798e-01, -6.64743e-02, 1.60599e-02, -2.60868e-03 },
{ -1.98880e-03, 1.10760e-02, -3.65541e-02, 1.09710e-01, 9.67477e-01, -6.23972e-02, 1.50233e-02, -2.43718e-03 },
{ -1.83931e-03, 1.02356e-02, -3.37303e-02, 1.00769e-01, 9.70992e-01, -5.82159e-02, 1.39681e-02, -2.26307e-03 },
{ -1.68894e-03, 9.39154e-03, -3.09033e-02, 9.19033e-02, 9.74342e-01, -5.39305e-02, 1.28947e-02, -2.08645e-03 },
{ -1.53777e-03, 8.54441e-03, -2.80746e-02, 8.31162e-02, 9.77526e-01, -4.95412e-02, 1.18034e-02, -1.90738e-03 },
{ -1.38589e-03, 7.69462e-03, -2.52457e-02, 7.44095e-02, 9.80543e-01, -4.50483e-02, 1.06946e-02, -1.72594e-03 },
{ -1.23337e-03, 6.84261e-03, -2.24178e-02, 6.57852e-02, 9.83392e-01, -4.04519e-02, 9.56876e-03, -1.54221e-03 },
{ -1.08030e-03, 5.98883e-03, -1.95925e-02, 5.72454e-02, 9.86071e-01, -3.57525e-02, 8.42626e-03, -1.35627e-03 },
{ -9.26747e-04, 5.13372e-03, -1.67710e-02, 4.87921e-02, 9.88580e-01, -3.09503e-02, 7.26755e-03, -1.16820e-03 },
{ -7.72802e-04, 4.27773e-03, -1.39548e-02, 4.04274e-02, 9.90917e-01, -2.60456e-02, 6.09305e-03, -9.78093e-04 },
{ -6.18544e-04, 3.42130e-03, -1.11453e-02, 3.21531e-02, 9.93082e-01, -2.10389e-02, 4.90322e-03, -7.86031e-04 },
{ -4.64053e-04, 2.56486e-03, -8.34364e-03, 2.39714e-02, 9.95074e-01, -1.59305e-02, 3.69852e-03, -5.92100e-04 },
{ -3.09412e-04, 1.70888e-03, -5.55134e-03, 1.58840e-02, 9.96891e-01, -1.07209e-02, 2.47942e-03, -3.96391e-04 },
{ -1.54700e-04, 8.53777e-04, -2.76968e-03, 7.89295e-03, 9.98534e-01, -5.41054e-03, 1.24642e-03, -1.98993e-04 },
{ 0.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00, 1.00000e+00, 0.00000e+00, 0.00000e+00, 0.00000e+00 },
};

136
core/src/dsp/loop/agc.h Normal file
View File

@ -0,0 +1,136 @@
#pragma once
#include "../processor.h"
namespace dsp::loop {
template <class T>
class AGC : public Processor<T, T> {
using base_type = Processor<T, T>;
public:
AGC() {}
AGC(stream<T>* in, double setPoint, double attack, double decay, double maxGain, double maxOutputAmp, double initGain = 1.0) { init(in, setPoint, attack, decay, maxGain, maxOutputAmp, initGain); }
void init(stream<T>* in, double setPoint, double attack, double decay, double maxGain, double maxOutputAmp, double initGain = 1.0) {
_setPoint = setPoint;
_attack = attack;
_invAttack = 1.0f - _attack;
_decay = decay;
_invDecay = 1.0f - _decay;
_maxGain = maxGain;
_maxOutputAmp = maxOutputAmp;
_initGain = initGain;
amp = _setPoint / _initGain;
base_type::init(in);
}
void setSetPoint(double setPoint) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_setPoint = setPoint;
}
void setAttack(double attack) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_attack = attack;
_invAttack = 1.0f - _attack;
}
void setDecay(double decay) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_decay = decay;
_invDecay = 1.0f - _decay;
}
void setMaxGain(double maxGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_maxGain = maxGain;
}
void setMaxOutputAmp(double maxOutputAmp) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_maxOutputAmp = maxOutputAmp;
}
void setInitialGain(double initGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_initGain = initGain;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
amp = _setPoint / _initGain;
}
inline int process(int count, T* in, T* out) {
for (int i = 0; i < count; i++) {
// Get signal amplitude
float inAmp, gain;
if constexpr (std::is_same_v<T, complex_t>) {
inAmp = in[i].amplitude();
}
if constexpr (std::is_same_v<T, float>) {
inAmp = fabsf(in[i]);
}
// Update average amplitude
if (inAmp != 0.0f) {
amp = (inAmp > amp) ? ((amp * _invAttack) + (inAmp * _attack)) : ((amp * _invDecay) + (inAmp * _decay));
gain = std::min<float>(_setPoint / amp, _maxGain);
}
else {
gain = 1.0f;
}
// If clipping is detected look ahead and correct
if (inAmp*gain > _maxOutputAmp) {
float maxAmp = 0;
for (int j = i; j < count; j++) {
if constexpr (std::is_same_v<T, complex_t>) {
inAmp = in[j].amplitude();
}
if constexpr (std::is_same_v<T, float>) {
inAmp = fabsf(in[j]);
}
if (inAmp > maxAmp) { maxAmp = inAmp; }
}
amp = maxAmp;
gain = std::min<float>(_setPoint / amp, _maxGain);
}
// Scale output by gain
out[i] = in[i] * gain;
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
float _setPoint;
float _attack;
float _invAttack;
float _decay;
float _invDecay;
float _maxGain;
float _maxOutputAmp;
float _initGain;
float amp = 1.0;
};
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "pll.h"
namespace dsp::loop {
class CarrierTrackingPLL : public PLL {
using base_type = PLL;
public:
CarrierTrackingPLL() {}
CarrierTrackingPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advance(math::normalizePhase(in[i].phase() - pcl.phase));
}
return count;
}
};
}

View File

@ -0,0 +1,47 @@
#pragma once
#include "pll.h"
#include "../math/step.h"
namespace dsp::loop {
template<int ORDER>
class Costas : public PLL {
static_assert(ORDER == 2 || ORDER == 4 || ORDER == 8, "Invalid costas order");
using base_type = PLL;
public:
Costas() {}
Costas(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advance(errorFunction(out[i]));
}
return count;
}
protected:
inline float errorFunction(complex_t val) {
float err;
if constexpr (ORDER == 2) {
err = val.re * val.im;
}
if constexpr (ORDER == 4) {
err = (math::step(val.re) * val.im) - (math::step(val.im) * val.re);
}
if constexpr (ORDER == 8) {
// The way this works is it compresses order 4 constellations into the quadrants
const float K = sqrtf(2.0) - 1.0;
if (fabsf(val.re) >= fabsf(val.im)) {
err = (math::step(val.re) * val.im) - (math::step(val.im) * val.re * K);
}
else {
err = (math::step(val.re) * val.im * K) - (math::step(val.im) * val.re);
}
}
return std::clamp<float>(err, -1.0f, 1.0f);
}
};
}

View File

@ -0,0 +1,100 @@
#pragma once
#include "../processor.h"
namespace dsp::loop {
template <class T>
class FastAGC : public Processor<T, T> {
using base_type = Processor<T, T>;
public:
FastAGC() {}
FastAGC(stream<T>* in, double setPoint, double maxGain, double rate, double initGain = 1.0) { init(in, setPoint, maxGain, rate, initGain); }
void init(stream<T>* in, double setPoint, double maxGain, double rate, double initGain = 1.0) {
_setPoint = setPoint;
_maxGain = maxGain;
_rate = rate;
_initGain = initGain;
_gain = _initGain;
base_type::init(in);
}
void setSetPoint(double setPoint) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_setPoint = setPoint;
}
void setMaxGain(double maxGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_maxGain = maxGain;
}
void setRate(double rate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_rate = rate;
}
void setInitGain(double initGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_initGain = initGain;
}
void setGain(double gain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_gain = gain;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_gain = _initGain;
}
inline int process(int count, T* in, T* out) {
for (int i = 0; i < count; i++) {
// Output scaled input
out[i] = in[i] * _gain;
// Calculate output amplitude
float amp;
if constexpr (std::is_same_v<T, float>) {
amp = fabsf(out[i]);
}
if constexpr (std::is_same_v<T, complex_t>) {
amp = out[i].amplitude();
}
// Update and clamp gain
_gain += (_setPoint - amp) * _rate;
if (_gain > _maxGain) { _gain = _maxGain; }
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
float _gain;
float _setPoint;
float _rate;
float _maxGain;
float _initGain;
};
}

View File

@ -0,0 +1,90 @@
#pragma once
#include <math.h>
#include <assert.h>
#include "../types.h"
namespace dsp::loop {
template<class T, bool CLAMP_PHASE = true>
class PhaseControlLoop {
public:
PhaseControlLoop() {}
PhaseControlLoop(T alpha, T beta, T phase, T minPhase, T maxPhase, T freq, T minFreq, T maxFreq) {
init(alpha, beta, phase, minPhase, maxPhase, freq, minFreq, maxFreq);
}
void init(T alpha, T beta, T phase, T minPhase, T maxPhase, T freq, T minFreq, T maxFreq) {
assert(maxPhase > minPhase);
assert(maxFreq > minFreq);
_alpha = alpha;
_beta = beta;
this->phase = phase;
_minPhase = minPhase;
_maxPhase = maxPhase;
this->freq = freq;
_minFreq = minFreq;
_maxFreq = maxFreq;
phaseDelta = _maxPhase - _minPhase;
}
static inline void criticallyDamped(T bandwidth, T& alpha, T& beta) {
T dampningFactor = sqrt(2.0) / 2.0;
T denominator = (1.0 + 2.0*dampningFactor*bandwidth + bandwidth*bandwidth);
alpha = (4 * dampningFactor * bandwidth) / denominator;
beta = (4 * bandwidth * bandwidth) / denominator;
}
void setCoefficients(T alpha, T beta) {
_alpha = alpha;
_beta = beta;
}
void setPhaseLimits(T minPhase, T maxPhase) {
assert(maxPhase > minPhase);
_minPhase = minPhase;
_maxPhase = maxPhase;
phaseDelta = _maxPhase - _minPhase;
clampPhase();
}
void setFreqLimits(T minFreq, T maxFreq) {
assert(maxFreq > minFreq);
_minFreq = minFreq;
_maxFreq = maxFreq;
clampFreq();
}
inline void advance(T error) {
// Increment and clamp frequency
freq += _beta * error;
clampFreq();
// Increment and clamp phase
phase += freq + (_alpha * error);
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
T freq;
T phase;
protected:
inline void clampFreq() {
if (freq > _maxFreq) { freq = _maxFreq; }
else if (freq < _minFreq) { freq = _minFreq; }
}
inline void clampPhase() {
while (phase > _maxPhase) { phase -= phaseDelta; }
while (phase < _minPhase) { phase += phaseDelta; }
}
T _alpha;
T _beta;
T _minPhase;
T _maxPhase;
T _minFreq;
T _maxFreq;
T phaseDelta;
};
}

90
core/src/dsp/loop/pll.h Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include "../processor.h"
#include "../math/normalize_phase.h"
#include "../math/phasor.h"
#include "phase_control_loop.h"
namespace dsp::loop {
class PLL : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>;
public:
PLL() {}
PLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) { init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq); }
void init(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
_initPhase = initPhase;
_initFreq = initFreq;
// Init phase control loop
float alpha, beta;
PhaseControlLoop<float>::criticallyDamped(bandwidth, alpha, beta);
pcl.init(alpha, beta, initPhase, -FL_M_PI, FL_M_PI, initFreq, minFreq, maxFreq);
base_type::init(in);
}
void setBandwidth(double bandwidth) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
float alpha, beta;
PhaseControlLoop<float>::criticallyDamped(bandwidth, alpha, beta);
pcl.setCoefficients(alpha, beta);
base_type::tempStart();
}
void setInitialPhase(double initPhase) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_initPhase = initPhase;
}
void setInitialFreq(double initFreq) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_initFreq = initFreq;
}
void setFrequencyLimits(double minFreq, double maxFreq) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
pcl.setFreqLimits(minFreq, maxFreq);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
pcl.phase = _initPhase;
pcl.freq = _initFreq;
base_type::tempStart();
}
virtual inline int process(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = math::phasor(pcl.phase);
pcl.advance(math::normalizePhase(in[i].phase() - pcl.phase));
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
PhaseControlLoop<float> pcl;
float _initPhase;
float _initFreq;
complex_t lastVCO = { 1.0f, 0.0f };
};
}

View File

@ -1,249 +0,0 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
namespace dsp {
template <class T>
class Add : public generic_block<Add<T>> {
public:
Add() {}
Add(stream<T>* a, stream<T>* b) { init(a, b); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Add<T>>::registerInput(a);
generic_block<Add<T>>::registerInput(b);
generic_block<Add<T>>::registerOutput(&out);
generic_block<Add<T>>::_block_init = true;
}
void setInputs(stream<T>* a, stream<T>* b) {
assert(generic_block<Add<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
generic_block<Add<T>>::tempStop();
generic_block<Add<T>>::unregisterInput(_a);
generic_block<Add<T>>::unregisterInput(_b);
_a = a;
_b = b;
generic_block<Add<T>>::registerInput(_a);
generic_block<Add<T>>::registerInput(_b);
generic_block<Add<T>>::tempStart();
}
void setInputA(stream<T>* a) {
assert(generic_block<Add<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
generic_block<Add<T>>::tempStop();
generic_block<Add<T>>::unregisterInput(_a);
_a = a;
generic_block<Add<T>>::registerInput(_a);
generic_block<Add<T>>::tempStart();
}
void setInputB(stream<T>* b) {
assert(generic_block<Add<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Add<T>>::ctrlMtx);
generic_block<Add<T>>::tempStop();
generic_block<Add<T>>::unregisterInput(_b);
_b = b;
generic_block<Add<T>>::registerInput(_b);
generic_block<Add<T>>::tempStart();
}
int run() {
int a_count = _a->read();
if (a_count < 0) { return -1; }
int b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
// TODO: Switch this out for volk_32fc_x2_add_32fc with a check for old volk versions that don't have it (eg. Ubuntu 18.04 that has volk 1.3)
volk_32f_x2_add_32f((float*)out.writeBuf, (float*)_a->readBuf, (float*)_b->readBuf, a_count * 2);
}
else {
volk_32f_x2_add_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
_a->flush();
_b->flush();
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<T> out;
private:
stream<T>* _a;
stream<T>* _b;
};
template <class T>
class Subtract : public generic_block<Subtract<T>> {
public:
Subtract() {}
Subtract(stream<T>* a, stream<T>* b) { init(a, b); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Subtract<T>>::registerInput(a);
generic_block<Subtract<T>>::registerInput(b);
generic_block<Subtract<T>>::registerOutput(&out);
generic_block<Subtract<T>>::_block_init = true;
}
void setInputs(stream<T>* a, stream<T>* b) {
assert(generic_block<Subtract<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
generic_block<Subtract<T>>::tempStop();
generic_block<Subtract<T>>::unregisterInput(_a);
generic_block<Subtract<T>>::unregisterInput(_b);
_a = a;
_b = b;
generic_block<Subtract<T>>::registerInput(_a);
generic_block<Subtract<T>>::registerInput(_b);
generic_block<Subtract<T>>::tempStart();
}
void setInputA(stream<T>* a) {
assert(generic_block<Subtract<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
generic_block<Subtract<T>>::tempStop();
generic_block<Subtract<T>>::unregisterInput(_a);
_a = a;
generic_block<Subtract<T>>::registerInput(_a);
generic_block<Subtract<T>>::tempStart();
}
void setInputB(stream<T>* b) {
assert(generic_block<Subtract<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Subtract<T>>::ctrlMtx);
generic_block<Subtract<T>>::tempStop();
generic_block<Subtract<T>>::unregisterInput(_b);
_b = b;
generic_block<Subtract<T>>::registerInput(_b);
generic_block<Subtract<T>>::tempStart();
}
int run() {
int a_count = _a->read();
if (a_count < 0) { return -1; }
int b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
volk_32f_x2_subtract_32f((float*)out.writeBuf, (float*)_a->readBuf, (float*)_b->readBuf, a_count * 2);
}
else {
volk_32f_x2_subtract_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
_a->flush();
_b->flush();
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<T> out;
private:
stream<T>* _a;
stream<T>* _b;
};
template <class T>
class Multiply : public generic_block<Multiply<T>> {
public:
Multiply() {}
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Multiply<T>>::registerInput(a);
generic_block<Multiply<T>>::registerInput(b);
generic_block<Multiply<T>>::registerOutput(&out);
generic_block<Multiply<T>>::_block_init = true;
}
void setInputs(stream<T>* a, stream<T>* b) {
assert(generic_block<Multiply<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
generic_block<Multiply<T>>::tempStop();
generic_block<Multiply<T>>::unregisterInput(_a);
generic_block<Multiply<T>>::unregisterInput(_b);
_a = a;
_b = b;
generic_block<Multiply<T>>::registerInput(_a);
generic_block<Multiply<T>>::registerInput(_b);
generic_block<Multiply<T>>::tempStart();
}
void setInputA(stream<T>* a) {
assert(generic_block<Multiply<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
generic_block<Multiply<T>>::tempStop();
generic_block<Multiply<T>>::unregisterInput(_a);
_a = a;
generic_block<Multiply<T>>::registerInput(_a);
generic_block<Multiply<T>>::tempStart();
}
void setInputB(stream<T>* b) {
assert(generic_block<Multiply<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Multiply<T>>::ctrlMtx);
generic_block<Multiply<T>>::tempStop();
generic_block<Multiply<T>>::unregisterInput(_b);
_b = b;
generic_block<Multiply<T>>::registerInput(_b);
generic_block<Multiply<T>>::tempStart();
}
int run() {
int a_count = _a->read();
if (a_count < 0) { return -1; }
int b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_x2_multiply_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_a->readBuf, (lv_32fc_t*)_b->readBuf, a_count);
}
else {
volk_32f_x2_multiply_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
_a->flush();
_b->flush();
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<T> out;
private:
stream<T>* _a;
stream<T>* _b;
};
}

42
core/src/dsp/math/add.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include "../operator.h"
namespace dsp::math {
template <class T>
class Add : public Operator<T, T, T> {
using base_type = Operator<T, T, T>;
public:
Add() {}
Add(stream<T>* a, stream<T>* b) { base_type::init(a, b); }
static inline int process(int count, const T* a, const T*b, T* out) {
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
volk_32f_x2_add_32f((float*)out, (float*)a, (float*)b, count * 2);
}
else {
volk_32f_x2_add_32f(out, a, b, count);
}
return count;
}
int run() {
int a_count = base_type::_a->read();
if (a_count < 0) { return -1; }
int b_count = base_type::_b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
base_type::_a->flush();
base_type::_b->flush();
return 0;
}
process(a_count, base_type::_a->readBuf, base_type::_b->readBuf, base_type::out.writeBuf);
base_type::_a->flush();
base_type::_b->flush();
if (!base_type::out.swap(a_count)) { return -1; }
return a_count;
}
};
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "../processor.h"
namespace dsp::math {
class Conjugate : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>;
public:
Conjugate() {}
Conjugate(stream<complex_t>* in) { base_type::init(in); }
inline static int process(int count, const complex_t* in, complex_t* out) {
volk_32fc_conjugate_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, count);
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
};
}

View File

@ -0,0 +1,7 @@
#pragma once
#define DB_M_PI 3.14159265358979323846
#define FL_M_PI 3.1415926535f
#define DB_M_SQRT2 1.4142135623730951
#define FL_M_SQRT2 1.4142135623f

76
core/src/dsp/math/delay.h Normal file
View File

@ -0,0 +1,76 @@
#pragma once
#include "../processor.h"
namespace dsp::math {
template<class T>
class Delay : public Processor<T, T> {
using base_type = Processor<T, T>;
public:
Delay() {}
Delay(stream<T>* in, int delay) { init(in, delay); }
~Delay() {
if (!base_type::_block_init) { return; }
base_type::stop();
buffer::free(buffer);
}
void init(stream<T>* in, int delay) {
_delay = delay;
buffer = buffer::alloc<T>(STREAM_BUFFER_SIZE + 64000);
bufStart = &buffer[_delay];
buffer::clear(buffer, _delay);
base_type::init(in);
}
void setDelay(int delay) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_delay = delay;
bufStart = &buffer[_delay];
reset();
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
buffer::clear(buffer, _delay);
base_type::tempStart();
}
inline int process(int count, const T* in, T* out) {
// Copy data into delay buffer
memcpy(bufStart, in, count * sizeof(T));
// Copy data out of the delay buffer
memcpy(out, buffer, count * sizeof(T));
// Move end of the delay buffer to the front
memmove(buffer, &buffer[count], _delay * sizeof(T));
return count;
}
virtual int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
int _delay;
T* buffer;
T* bufStart;
};
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <math.h>
#include "constants.h"
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
namespace dsp::math {
inline float fastAtan2(float x, float y) {
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;
}
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <math.h>
#include "constants.h"
namespace dsp::math {
inline double hzToRads(double freq, double samplerate) {
return 2.0 * DB_M_PI * (freq / samplerate);
}
}

Some files were not shown because too many files have changed in this diff Show More