775 Commits

Author SHA1 Message Date
d9a0243905 Slight fixes on the hrpt decoder and new version 2021-08-01 14:48:47 +02:00
99096885f5 Changed to a better way of selecting sample rate on the plutosdr 2021-07-31 21:50:46 +02:00
f16c296f38 Potential fix for windows 7 not detecting the home key and the radio not saving the bandwidth when set through the frequency manager 2021-07-31 21:00:47 +02:00
85eb08e422 Fixed issues with unselectable VFOs when too small 2021-07-31 20:17:36 +02:00
ee5b89c4aa Fixed waterfall at very low samplerates 2021-07-31 16:36:04 +02:00
c13eb950b2 better defaults for rtl-sdr and rtl-tcp sources 2021-07-31 05:12:26 +02:00
011fdce237 Fixed weird VFO behavior 2021-07-31 01:38:01 +02:00
79e79e78ac Fixed network sink missing from windows CI builds 2021-07-31 00:03:28 +02:00
6114cf0f58 Fixed crash on disconnect 2021-07-30 23:39:28 +02:00
3350697875 Fixed broken code on linux 2021-07-30 22:06:06 +02:00
7f4557527e Added network sink and fixes to the networking lib 2021-07-30 21:56:56 +02:00
1aa2c064f7 Merge pull request #190 from thedocruby/patch-1
Further progress the rigctl server towards full feature parity
2021-07-29 22:27:31 +02:00
1f3dcc1beb removed broken scripting interface 2021-07-29 22:08:42 +02:00
e9cdf162fa More fixes 2021-07-29 21:20:40 +02:00
a487c0aaea more bugfix 2021-07-29 20:22:16 +02:00
d84273a0db Merge branch 'master' into patch-1 2021-07-29 12:38:23 -04:00
5d08f1018d Partial fix for the SNR meter not being the right size 2021-07-29 17:58:23 +02:00
b7a0f849cf potential fix for Windows 7 freeze on exit 2021-07-29 15:07:22 +02:00
7079ddb74e Fixes to the UI 2021-07-29 02:50:51 +02:00
e744520d50 Fixed setFFTSize visual bug 2021-07-28 22:53:38 +02:00
0741f265d7 Fixed bugs with the frequency manager 2021-07-28 20:16:31 +02:00
2f61b190ca Fixed RTL-TCP bug 2021-07-28 19:35:25 +02:00
fc30287bed Fixed wrong response for get_mode rigctl command 2021-07-28 04:34:44 +02:00
003ff37ab8 Added support for compound commands and long commands to the rigctl server 2021-07-28 04:21:51 +02:00
8290284586 Improved performance of DC correction 2021-07-28 02:18:54 +02:00
b9e35e6558 Added IQ correction 2021-07-27 23:51:06 +02:00
679fb49743 Added IQ correction 2021-07-27 23:50:48 +02:00
5974839515 Merge branch 'AlexandreRouma:master' into patch-1 2021-07-27 13:06:17 -04:00
666b89c4c7 Copy down last edit 2021-07-27 13:02:31 -04:00
d21a61aefd Switches only take ints
dumb mistake. I squashed the if-else chain just a bit so that it wasn't any longer than the switch. it's already really, long, I didn't want it to be any longer. Thought it might be more readable this way.
2021-07-27 13:01:15 -04:00
5f29350dd1 Potential fix for AM squelch issue 2021-07-27 18:47:58 +02:00
8f43110c72 Fix dependency issue 2021-07-27 12:46:25 -04:00
a4240ab8a1 Merge branch 'AlexandreRouma:master' into patch-1 2021-07-27 12:20:12 -04:00
d73a18ddcc Touch-ups and commentary 2021-07-27 12:18:50 -04:00
6dc24a7fc7 Updated version number 2021-07-27 18:09:47 +02:00
cd74313bc8 Added mode and vfo commands
vfo command is currently a dummy implementation, but as some rigs officially supported by rigctl do not support vfo besides a dummy response, such an implementation is acceptable for now.
2021-07-27 11:57:51 -04:00
14fae89a0b Added error message on SDRplay source when API isn't working 2021-07-27 16:32:25 +02:00
0498a48b93 Fixed bug in source selection 2021-07-27 15:47:11 +02:00
22e9d25bed Added longform frequency commands for consistency
Also:
 - fixed control error at line 366 
 - fixed control errors related to returning in compound command loop
 - added commentary
2021-07-26 20:30:03 -04:00
4092874f5c Add support for compound commands 2021-07-26 20:06:33 -04:00
e27702c166 Rearrange commands, add slash commands. 2021-07-26 20:00:32 -04:00
21f4c40e7f Added a FFT framerate setting 2021-07-27 01:57:12 +02:00
bd744d07ba Final fix for the linux crash 2021-07-26 22:16:31 +02:00
8a4055920d More fixes 2021-07-26 21:44:15 +02:00
6c05c11a62 potential fix for stall 2021-07-26 21:30:49 +02:00
e62351c206 Added decimation 2021-07-26 21:18:06 +02:00
ffed602246 Fixed stereo checkbox in radio module 2021-07-26 18:21:06 +02:00
03a2f41c04 Fixed category on .desktop 2021-07-26 18:16:30 +02:00
df51dc7104 Fixed module manager title not locked 2021-07-26 17:49:16 +02:00
8bea72beb5 Fixed module manager not saving module list 2021-07-26 17:45:09 +02:00
14813aa962 Fixed module manager not saving 2021-07-26 17:33:41 +02:00
ce448d6852 Fixed weird source deselect bug 2021-07-26 17:17:10 +02:00
7506e45d3b Fixed bug when removing certain modules 2021-07-26 16:56:48 +02:00
034ada1ed7 Fixed freeze when removing the recorder 2021-07-26 16:40:05 +02:00
646fe4fd02 more bugfix on the module manager and audio streams 2021-07-26 15:54:33 +02:00
fa9347f2ee More bugfix 2021-07-26 04:16:00 +02:00
85de72a859 Fixed support in the module manager 2021-07-26 03:11:51 +02:00
b327bfbe5d Slight fix to the audio sink 2021-07-24 19:53:57 +02:00
da8614438c Added elements for the wiki 2021-07-23 20:32:43 +02:00
5248688241 Fixed username 2021-07-23 19:43:59 +02:00
26dedec561 Merge pull request #180 from Starman0620/master
Change all references to me to use my real name
2021-07-23 19:39:53 +02:00
d8d45ec7af more bugfix on the file source 2021-07-23 19:39:17 +02:00
a888065624 more fixes to stereo FM 2021-07-23 16:18:47 +02:00
8454b40d54 FM stereo prototype 2021-07-23 06:29:16 +02:00
175e361ccd bugfix + preparations for stereo FM 2021-07-22 23:30:41 +02:00
2baf607b8c Fixed RTL-TCP bug 2021-07-21 16:23:03 +02:00
a974658c98 Fixed checkbox with no name 2021-07-21 15:44:50 +02:00
465dc9e5dc fixed typo 2021-07-21 15:43:30 +02:00
8d3f646aec Added bias-t support to rtl_tcp source 2021-07-21 15:42:01 +02:00
4418baba3f Changed Starman0620 to Cam K. in Discord integration credits 2021-07-20 23:08:41 -04:00
3d5047338c Renamed Starman0620 to Cam K. in contributors 2021-07-20 23:07:47 -04:00
fb32b4d55a Made the file source set the frequency of the waterfall 2021-07-21 04:08:28 +02:00
5f1a94c267 more bugfix 2021-07-20 21:39:16 +02:00
0d7f1265da Fixed CW demodulator 2021-07-20 03:38:58 +02:00
09e332a8d1 more fixes 2021-07-20 03:03:22 +02:00
e877844768 More spyserver bugfix 2021-07-19 22:23:03 +02:00
c9f1ec0a8a Fixed more bugs 2021-07-19 20:51:17 +02:00
4b43da095e SpyServer fixes 2021-07-19 20:09:37 +02:00
3103d2d168 spyserver source bugfix 2021-07-19 16:20:01 +02:00
027297933b Fixed hanging bug 2021-07-19 15:57:37 +02:00
1adcdea6d1 Fixed missing modules 2021-07-19 14:25:28 +02:00
ebd74d1d1a Another fix for the spyserver source 2021-07-19 05:18:58 +02:00
0d993937b3 Fixed linux issue 2021-07-19 05:07:24 +02:00
79e2747aed New spyserver source 2021-07-19 04:52:13 +02:00
8d3557268f bugfix 2021-07-18 04:39:21 +02:00
336d69c043 Finished RigCTL server 2021-07-18 04:30:55 +02:00
2ddb1b93c4 Fixed typo in CI 2021-07-17 20:07:26 +02:00
63ae56cb9b Added a new optional audio sink as a test 2021-07-17 19:43:44 +02:00
45e4c21870 Fixed network lib bug on linux 2021-07-16 18:13:18 +02:00
ead7ee153a Merge pull request #170 from Starman0620/master
Add CB to Canadian bandplan
2021-07-16 14:39:46 +02:00
9cb6d96f8f Added new patron 2021-07-16 03:21:53 +02:00
0b7a7ca193 slight bugfix 2021-07-16 03:11:49 +02:00
f6e0e2f39d New custom network lib + half finished rigctl server 2021-07-16 01:49:41 +02:00
def6036b30 New custom network lib + half finished rigctl server 2021-07-16 01:49:33 +02:00
943d23a7ce Added CB to Canadian bandplan 2021-07-15 15:54:05 -04:00
218844ed47 Fixed sample rate not updating 2021-07-14 01:58:59 +02:00
794c486352 Another fix attempt 2021-07-13 22:16:20 +02:00
e6293b6435 fixed error 2021-07-13 22:07:01 +02:00
6ef58f2e7c Merge pull request #167 from mbiette/hackrf_close_workaround
Added error log to the HackRF start and stop calls
2021-07-13 21:12:32 +02:00
c0f3babc49 Fixed code style 2021-07-13 21:12:07 +02:00
0ff287cbfb Merge pull request #169 from KentuckyFriedData/patch-1
Fixed small typos
2021-07-13 21:05:51 +02:00
0c40a2954d Fixed small typos 2021-07-13 11:59:38 -07:00
4735fd238a Bugfix 2021-07-13 20:34:31 +02:00
ace0f4a316 Performance improvements 2021-07-13 20:15:42 +02:00
6583104a96 Add error details on libhackrf open/close 2021-07-13 13:21:02 -04:00
4a5a29a59a Fixed the audio glitches!!! 2021-07-13 18:47:34 +02:00
ff38cefe11 Merge pull request #168 from mbiette/fix_typo
Fix typo untyped_steam -> untyped_stream
2021-07-13 00:17:10 +02:00
168226c634 Fix typo untyped_steam -> untyped_stream 2021-07-12 17:49:06 -04:00
3d7cfffe13 potential fix for the RTL-SDR not tuning (mayhaps?) 2021-07-12 16:53:59 +02:00
6f409b59c8 Fixed credit screen not centered 2021-07-12 16:20:59 +02:00
ff030397a4 DSP code cleanup 2021-07-12 05:03:51 +02:00
1f1b0cd45e Merge pull request #164 from mbiette/fix_delete
Fix destructor crash due to wild pointer
2021-07-12 01:22:02 +02:00
4aaf71f5cc Fixed code style 2021-07-12 00:58:39 +02:00
5971d3d3b3 Fix destructor crash due to wild pointer
Otherwise when RingBuffer is deleted before its init() being called it would crash in the destructor due to its _buffer pointer not being initialized.
2021-07-11 18:33:41 -04:00
2151d1e6cc Merge pull request #163 from cropinghigh/patch-1
Add gqrx colormap
2021-07-11 20:13:42 +02:00
b2d8e19504 fixed case 2021-07-11 20:13:30 +02:00
939197df6b Add gqrx colormap 2021-07-11 13:27:15 +03:00
eb48dd70fb More UI bugfix 2021-07-10 21:15:20 +02:00
6cca4c654f Optimized the menu + bugfix 2021-07-10 18:33:11 +02:00
f86df07c36 bugfix 2021-07-10 01:07:26 +02:00
73bbd69e3f The enabled state of all modules is now preserved 2021-07-10 00:28:56 +02:00
fd9f4ebdc3 Merge pull request #162 from mbiette/fix_typo
Fix typo aquire -> acquire
2021-07-09 21:50:15 +02:00
ac04432453 Fix typo aquire -> acquire
https://en.wiktionary.org/wiki/aquire
2021-07-09 14:57:58 -04:00
91c6823e0c Bugfix 2021-07-09 17:55:17 +02:00
cf3c976651 Added a way to move menus around 2021-07-09 04:29:16 +02:00
29ec14d3f0 Added a better offset config for up/down converters 2021-07-09 00:58:05 +02:00
ba208bf8b3 Added new contributor 2021-07-08 22:41:36 +02:00
7ebfddc03c Merge pull request #160 from mbiette/fix_doc
Improve documentation for windows build
2021-07-08 22:39:57 +02:00
61f56b6e56 Updated ImGui + Fixed bugs in the frequency manager + new features 2021-07-08 22:39:26 +02:00
132f591288 removed outdated folders from the gitignore left there by accident 2021-07-08 22:36:28 +02:00
7e6b9d8487 Changed a few things 2021-07-08 22:35:14 +02:00
4c8b810bd6 Improving Windows build instructions 2021-07-08 10:33:58 -04:00
f9ad86e312 Update .gitignore for Visual Studio, ReSharper and CLion 2021-07-08 10:29:28 -04:00
7219e3a4de Switched back to portaudio for macos 2021-07-06 23:53:11 +02:00
1bc49426e2 Update readme.md 2021-07-05 20:45:04 +02:00
41a307fd35 Update readme.md 2021-07-05 20:44:30 +02:00
d4849af171 Fixed windowing not being enabled by default 2021-07-05 05:26:49 +02:00
ab376ea1aa Performance improvement to the FFT + code cleanup + Added an option to select the FFT window 2021-07-05 01:09:48 +02:00
6db8251e46 Fixed bugs + added option to show bookmarks on FFT 2021-07-04 16:41:46 +02:00
4dc0df74cf Added option to show current list on FFT 2021-07-04 02:25:36 +02:00
5b9bd56cf2 Fixed missing frequency manager from windows CI 2021-07-03 20:39:35 +02:00
ecbb451763 Added zimm to contrib list 2021-07-03 20:31:54 +02:00
78f079ca84 Finished the frequency manager module 2021-07-03 19:46:21 +02:00
a408084237 changed name 2021-07-03 16:29:26 +02:00
9c0602f406 more changes 2021-07-03 16:15:27 +02:00
3f6b8dbe6c Merge pull request #157 from marvin-sinister/feature-improve-build
Improve docker builds
2021-07-03 15:09:51 +02:00
5a21b07269 revert package installation changes 2021-07-03 14:46:55 +02:00
b96206765a Path fix 2021-07-03 13:44:56 +02:00
f0b2d80ba7 docker build improvements 2021-07-03 13:35:42 +02:00
a19e47bd54 more work on the frequency manager and adder 2.4MS/s to rtl tcp 2021-07-03 00:08:01 +02:00
4a2774367f Fixed UI and frequency manager bug 2021-07-02 18:12:56 +02:00
7d720e4d6f Fixed more LimeSDR bugs 2021-07-01 22:23:08 +02:00
b87ec8f2cc Merge pull request #154 from marvin-sinister/feature-debian-version
append build number to debian package version
2021-06-30 17:34:13 +02:00
2c4e221d1c append build number to debian package version 2021-06-30 14:03:30 +02:00
0dbd89db19 Fixed BladeRF and LimeSDR bugs 2021-06-30 03:13:14 +02:00
8f4942bbe9 Mooooore performance 2021-06-30 02:48:36 +02:00
24892c854e Fixed SDRplay source of OSX 2021-06-29 19:37:03 +02:00
5f84ecc4de Added audio fix for MacOS 2021-06-29 18:14:26 +02:00
659b9b1e8c Potential fix for SDR++ not stopping on Linux 2021-06-29 15:52:35 +02:00
8a1df1d712 Fixed SNR not updating when waterfall is hidden 2021-06-29 03:32:40 +02:00
aaa15315ce Fixed file source and cleaned up buffering code 2021-06-29 02:43:04 +02:00
70cf463881 new buffer thingy 2 2021-06-28 22:11:20 +02:00
dff9ec2d37 new buffer thingy 2021-06-28 22:06:42 +02:00
192e86064c Fixed windows CI 2021-06-28 16:28:10 +02:00
bfdeb77f7d Fixed ubuntu hirsute CI 2021-06-28 04:16:37 +02:00
da96ecaaba A lot of new stuff 2021-06-28 02:22:51 +02:00
72a794df6f Disabled bladerf source build for debian buster 2021-06-27 02:36:55 +02:00
c39b9609be Added bladerf source 2021-06-27 02:20:11 +02:00
d31ed762c1 IMPORTANT BUGFIX 2021-06-26 18:26:58 +02:00
dad41e1574 Better light theme 2021-06-24 18:21:07 +02:00
a6aee1d9a4 Fixed themes not being installed 2021-06-24 03:23:30 +02:00
64ed5058bf Fixed wrong disabled color sheme 2021-06-23 22:24:58 +02:00
26079dba0a Added theme system 2021-06-23 21:45:38 +02:00
94fae2135d Cleaned up UI code 2021-06-20 21:17:11 +02:00
1e71a52727 Removed temporary missing code 2021-06-17 20:18:49 +02:00
da2f4fcf3a Changed the default NFM snap interval 2021-06-17 20:14:23 +02:00
b5d38c71ce Added french band plan 2021-05-31 18:39:15 +02:00
8a18bec55c switched pothos version 2021-05-18 20:05:24 +02:00
45ff5dd0cc testing 2021-05-18 02:38:06 +02:00
55017f876d Fixed vfo selection issue 2021-05-18 02:26:55 +02:00
c59b83e564 Merge pull request #141 from invader-zimm/master
Frequency manager by Zimm
2021-05-13 17:32:47 +02:00
c0244e819e Push before merge 2021-05-13 17:31:40 +02:00
3467031bf4 disable Freq Mngr build by default 2021-05-12 18:54:08 -04:00
51ef8b1891 change indentation and brackets style 2021-05-12 18:52:31 -04:00
bbff0036dc maybe fix crash with bookmarks table 2021-05-12 10:49:46 -04:00
e6ab6f3cc9 fix CMakeLists.txt file 2021-05-12 10:38:16 -04:00
99d14f7abb Update CMakeLists.txt 2021-05-12 00:04:12 -04:00
215c17ae20 add frequency manager 2021-05-12 00:03:31 -04:00
b4e1eef8c9 frequency manager initial commit 2021-05-12 00:01:10 -04:00
0b276bed1d Fixed weird audio glitch on some AM station 2021-05-09 03:06:57 +02:00
1b27916c24 Added module management system 2021-05-05 04:31:37 +02:00
85b9649a9a Added BladeRF source to CI 2021-05-04 21:37:57 +02:00
aa9ab8e1e8 Fixed VFO color menu position 2021-05-04 21:05:45 +02:00
1eca58605c Added VFO coloring option 2021-05-04 20:41:23 +02:00
9a1850bd61 Fixed delayed VFO update bug 2021-05-04 02:52:59 +02:00
c23b2bdc55 reduced buffer size 2021-05-03 22:42:10 +02:00
754a9ac406 Acutally moved the mode and freq variables to the stack because why not 2021-05-03 22:40:53 +02:00
3225f15419 Fixed bug in discord integration 2021-05-03 22:38:38 +02:00
b1bb863a7e Added new patron + more bladerf upgrades 2021-05-03 20:21:30 +02:00
b030c22c56 More work on the bladerf 2021-05-03 18:11:46 +02:00
e1086e2d41 Merge pull request #130 from Starman0620/master
Add GitHub info to Rich Presence
2021-05-03 04:23:46 +02:00
4634c8187f More fixes idk 2021-05-03 04:20:48 +02:00
9913124a5c Added GitHub info to Rich Presence 2021-05-02 22:17:56 -04:00
0bc1bd8549 Fixed color interpolation bug 2021-05-03 02:18:26 +02:00
c6c15a446b Fixed OS EventHandler compilation issue 2021-05-03 02:08:49 +02:00
6e4f502454 More work on the BladeRF support 2021-05-02 02:23:10 +02:00
66150922c7 Fixed OSX CI not in group 2021-05-01 14:44:33 +02:00
d1acfeb496 Added MacOS package build 2021-04-30 20:33:51 +02:00
233d5ed838 Fixed mac stuff 2021-04-30 20:10:29 +02:00
860afb2fbd More MacOS related stuff 2021-04-30 19:23:43 +02:00
d8c0c8649c Patched pothos libusb versions in automated builds 2021-04-30 16:34:42 +02:00
55b2b050c8 Fixed crash when changing RSP settings before starting 2021-04-30 15:16:07 +02:00
96f83ee55c Added SNR meter 2021-04-30 04:28:08 +02:00
3e79d4dfad Added tooltip to show VFO name 2021-04-29 23:29:40 +02:00
d3276a1546 Added missing line smh 2021-04-29 22:09:06 +02:00
ce8b4ceb44 Added persistant config for file source 2021-04-29 22:04:20 +02:00
bed0712be1 Recorder should now save dialog path 2021-04-29 21:41:47 +02:00
f483de1f7e Fixed missing vector again 2021-04-29 20:51:26 +02:00
a6df90785a Fixed missing vector 2021-04-29 20:48:37 +02:00
ab4cde9bb8 Added cross platform support for file and folder select widgets 2021-04-29 20:43:17 +02:00
1738706c59 Fixed libusb not found for rtlsdr on OSX 2021-04-29 19:12:54 +02:00
c801b6547f Added workaround for pkgconfig not adding the right directories for libairspy 2021-04-29 16:32:06 +02:00
490243e346 Fixed bad directory for MacOS CI 2021-04-29 15:51:32 +02:00
17db61c302 Fixed target name for airspy source 2021-04-29 15:44:32 +02:00
bef4a6efc1 Fixed soapy source name macos 2021-04-29 15:37:04 +02:00
26605b8d90 Added MacOS CI 2 2021-04-29 15:34:08 +02:00
8b25d74dde Added MacOS CI$ 2021-04-29 15:32:59 +02:00
8c8acf6955 Fixed missing library dir argument for OSX 2021-04-29 14:30:11 +02:00
6aade531a2 Merge pull request #122 from Starman0620/master
Fix Canada bandplan
2021-04-28 21:26:41 +02:00
53f2caa9cc Fixed Canada bandplan
Weatheradio/Environment Canada weather was mistyped as an amateur band
2021-04-28 15:04:30 -04:00
ac474902a4 Bugfix for SDRplay source crashes 2021-04-28 15:34:24 +02:00
20c47ae8f2 Added new contributors to credits and added file name standards to contributing.md 2021-04-28 13:42:05 +02:00
7d4fdad6f6 More additions to contributing.md 2021-04-27 20:16:01 +02:00
de3ee34fce Fixed audio sink not deleting instance 2021-04-27 16:48:54 +02:00
7481f0432b Merge pull request #116 from Starman0620/master
Add Canadian Bandplan
2021-04-27 13:02:53 +02:00
41709ef916 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2021-04-26 21:50:48 -04:00
935534905f Added Canada bandplan 2021-04-26 21:50:10 -04:00
b9a41c83bf Added sdrplay source to CI 2021-04-26 23:34:24 +02:00
54165c64ec Fixed CI again... 2021-04-26 20:17:57 +02:00
0300be1c4b Fixed contributing.md 2021-04-26 19:59:41 +02:00
48932a5230 Added automatic build for full archive 3 2021-04-26 19:43:55 +02:00
624817618c Added automatic build for full archive 2 2021-04-26 19:28:19 +02:00
48399d341f Added automatic build for full archive 2021-04-26 19:27:05 +02:00
48ae57ad2d Added CI for debian sid 2021-04-26 17:41:03 +02:00
128e52e33a Dropping support for old volk and ubuntu versions, please update..; 2021-04-26 17:03:52 +02:00
27697bb638 More bionic compatibility 2021-04-26 16:55:55 +02:00
ed29f9dcc5 Added work around for old volk versions 2021-04-26 16:02:03 +02:00
78c7ef0242 Fixed more bionic stuff 2 2021-04-26 15:34:32 +02:00
95f7171256 Fixed more bionic stuff 2021-04-26 15:30:52 +02:00
f968725469 Fixed cmake 3.10 compatibility for ubuntu bionic based distros 2021-04-26 15:25:28 +02:00
3d65e515ad Fixed bad cmake command 2021-04-26 15:17:24 +02:00
1ba3756be0 Fixed cmake bug and added ubuntu bionic 2021-04-26 15:12:52 +02:00
061cb91c48 Fixed CI Stall 2021-04-26 14:38:14 +02:00
5feba74b9b Fixed stalling issue 2021-04-26 14:21:45 +02:00
6117635a8d Merge pull request #114 from JoshuaKimsey/band-plan-organising
Standardized sorting of bands array for band allocation plans
2021-04-26 13:29:55 +02:00
8c70d816a0 Added JSON styling guides to the Contribution file
* Added guides to the Contribution file for how to properly style and organise the JSON files for Band allocation identifiers and colour maps.
2021-04-26 03:03:38 -04:00
c862882499 Added other ubuntu versions to CI 3 2021-04-26 05:48:52 +02:00
b9642f1e62 Added other ubuntu versions to CI 2 2021-04-26 05:44:55 +02:00
481d5a6b14 Added other ubuntu versions to CI 2021-04-26 05:43:40 +02:00
6deae14870 Standardised sorting of bands array for band plans
* Standardised organisation of the bands array in the band allocation JSON files. They are now organised by the starting frequency.

* Added 'Polar Orbiting Satellites' to the general JSON file, as that affects all general users.
2021-04-25 23:34:02 -04:00
102eea134c Fixed CI for debian 10 and 11 4 2021-04-26 05:23:41 +02:00
1894e191d5 Fixed CI for debian 10 and 11 3 2021-04-26 05:13:32 +02:00
36f2b157e5 Fixed CI for debian 10 and 11 2 2021-04-26 05:07:09 +02:00
e4b4787cbb Merge pull request #113 from JoshuaKimsey/usa-band-plan
Added band allocation plan for the USA
2021-04-26 05:05:54 +02:00
78e40f1f76 Fixed CI for debian 10 and 11 2021-04-26 05:04:31 +02:00
8596c26f7e Fixed CI for debian 10 and 11 2021-04-26 05:04:20 +02:00
a4ce0c8868 Added CI for debian 10 and 11 2021-04-26 04:59:37 +02:00
86702040a4 Added band allocation plan for the USA
* Added band allocation markers for NOAA Weather Radio, Polar Orbiting Satellites, and TV Channel Broadcasting frequencies. Will add more as time moves along.

* Changed some frequencies to better fit with FCC declared frequency ranges.

* Fixed issue with bands array not being organised. Will now be organised by start frequency.

(All relevant information was pulled from the FCC to ensure accuracy)
2021-04-25 22:47:58 -04:00
5322a4632c quick test for volk 2021-04-26 01:59:26 +02:00
670b5aefce Added new patron to credits 2021-04-25 22:54:09 +02:00
f316856682 fixed contributing.md 2021-04-25 19:50:07 +02:00
1c18310f37 adde contributing.md 2021-04-25 19:44:41 +02:00
8c428be885 Fixed ubuntu version in CI 2021-04-25 03:53:31 +02:00
c9a247b64d Added pothos to windows CI 12 2021-04-25 01:21:48 +02:00
95d2d2d7c8 Added pothos to windows CI 11 2021-04-25 00:44:30 +02:00
8eb1067da5 Added pothos to windows CI 10 2021-04-25 00:25:02 +02:00
d08611ee91 Added pothos to windows CI 9 2021-04-25 00:05:47 +02:00
289eba7855 Added pothos to windows CI 8 2021-04-24 23:47:35 +02:00
31e7867915 Added pothos to windows CI 7 2021-04-24 23:46:59 +02:00
5fd1509c96 Added pothos to windows CI 6 2021-04-24 23:25:44 +02:00
e2b897f1f2 Added pothos to windows CI 5 2021-04-24 22:58:19 +02:00
2628700ea8 Added pothos to windows CI 4 2021-04-24 22:50:38 +02:00
10d368444b Added pothos to windows CI 3 2021-04-24 22:20:33 +02:00
75b54fb9b4 Added pothos to windows CI 2 2021-04-24 22:05:02 +02:00
f0250f0cd1 Added pothos to windows CI 2021-04-24 22:01:28 +02:00
db1ab774f0 More windows CI stuff 2021-04-24 21:32:55 +02:00
28d189e35a Windows CI 3 2021-04-24 21:21:10 +02:00
5c487b73ab Fix CI 2 2021-04-24 21:17:49 +02:00
71eb2735f6 Fix CI 2021-04-24 21:17:00 +02:00
53ba9eddf7 Windows CI 2 2021-04-24 21:16:06 +02:00
3efdd51fa6 Windows CI 2021-04-24 21:12:04 +02:00
e115161da8 Merge pull request #108 from KentuckyFriedData/patch-1
Fixed typos and some grammar
2021-04-24 20:58:56 +02:00
dec16eeb87 push before merge 2021-04-24 20:58:33 +02:00
2a22a125bb Fixed bug 2021-04-24 19:41:02 +02:00
8305750016 Fixed frequency select not updating when disabling VFO 2021-04-24 19:26:22 +02:00
77ac94ff99 Fixed typos and some grammar 2021-04-24 09:59:57 -07:00
35122708be Fixed wrong env var name again 2021-04-24 16:44:41 +02:00
6152403faf Fixed wrong env var name 2021-04-24 16:25:07 +02:00
cd4f64bc80 saving github actions artifact 2 2021-04-24 16:11:56 +02:00
7a95229cc2 saving github actions artifact 2021-04-24 16:09:42 +02:00
df42830d38 testing github actions 2021-04-24 15:52:22 +02:00
2df1869824 Fixed typo 2021-04-24 04:30:30 +02:00
05440ed2fc Fixed missing .json 2021-04-24 04:27:32 +02:00
7a9371c062 Merge pull request #106 from Starman0620/master
Revised readme.md
2021-04-24 04:26:08 +02:00
fc350871e4 Update readme 2021-04-23 22:18:17 -04:00
88fe31fead Fixed code block in readme 2021-04-23 22:17:09 -04:00
fc7ecab5f2 Update readme 2021-04-23 22:14:39 -04:00
0382b8aed8 Update readme 2021-04-23 22:13:54 -04:00
a35d0252e7 Fixed scroll bug and added keybinds 2021-04-24 04:06:04 +02:00
dd5490cac6 Added scroll controls 2021-04-24 01:24:27 +02:00
75568a7bf7 added keyboard input to frequency select 2021-04-23 21:35:54 +02:00
b8347fd254 More keyboard controls 2021-04-23 19:12:24 +02:00
d43f501819 Added more keyboard controls 2021-04-23 17:53:25 +02:00
b8e4a79188 Fixed inexplicable ImGui bug 2021-04-23 03:58:10 +02:00
1dbdf48e9a Added persistant menu open/close 2021-04-22 23:49:35 +02:00
ed83abaeef meteor demodulator now saves the recording path 2021-04-22 19:18:19 +02:00
89e805b1a0 more stuff 2021-04-22 05:58:20 +02:00
a4c25280b3 bunch of stuff idk i'm tired 2021-04-22 05:38:25 +02:00
72cbf741b3 Code clean up + added inter module communication 2021-04-22 04:15:23 +02:00
2c83d79836 Fixed hackrf performance issues 2021-04-22 02:18:43 +02:00
9efdb6b150 Merge pull request #102 from Starman0620/master
Add Discord Rich Presence module
2021-04-22 02:17:29 +02:00
20ce4efebb Removed Discords dumb crap from discord-rpc 2021-04-21 20:11:47 -04:00
d3ad4a5035 Added persistent config for VFO offsets 2021-04-22 02:00:33 +02:00
5edb838f31 Renamed discord-integration to discord_integration 2021-04-21 19:58:07 -04:00
89dfeeb247 Renamed discord-integration to discord_integration 2021-04-21 19:52:16 -04:00
956969f679 Removed discord-integration GUI element
This was done because there's no point in having an enable/disable checkbox when the module itself isn't even loaded on default.
2021-04-21 19:25:15 -04:00
7ee86a5d40 Merge branch 'master' of https://github.com/Starman0620/SDRPlusPlus; branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2021-04-21 19:20:00 -04:00
f8be28dcee Enabled discord-integration on default 2021-04-21 19:19:36 -04:00
bc11ef7e9c Added rapidjson to discord-integration 2021-04-21 19:15:10 -04:00
eb8cd09e65 Another fix for the weird no exit thing 2021-04-21 20:06:30 +02:00
77dada07da Fixed sdrpp not exiting 2021-04-21 19:24:58 +02:00
e236c42068 Fixed missing include 2021-04-21 18:39:47 +02:00
38c9e2c894 Fixed delay before exiting 2021-04-21 18:36:45 +02:00
a4fa7d2ff6 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2021-04-21 12:00:00 -04:00
61ebff209b Fixed enable/disable bug in discord-integration 2021-04-21 11:59:03 -04:00
4849d9cf09 Fix for threads not terminating 2021-04-21 17:23:09 +02:00
a0316e57c5 Fixed gain bug for RSPduo 2021-04-21 14:53:09 +02:00
6332b33f3e Fixed RSP fft lag 2021-04-21 03:03:40 +02:00
7d6ecf923b Disabled discord-integration on default in CMakeLists 2021-04-20 18:31:59 -04:00
8e194ba5a9 Switched the 10s timer in discord-integration to be thread based 2021-04-20 18:31:28 -04:00
99ec2a12f1 Cleaned up discord-integration 2021-04-20 18:19:58 -04:00
dd0ec72fb7 Merge remote-tracking branch 'origin/master' 2021-04-20 18:15:13 -04:00
271e4a6f46 Changed frequency fetching for discord-integration 2021-04-20 18:15:08 -04:00
a9d1d9b9e7 Changed frequency fetching 2021-04-20 18:14:58 -04:00
9776191e83 Added discord-rpc files 2021-04-20 17:33:11 -04:00
a0f955e907 Full support for the RSPduo 2021-04-20 22:53:23 +02:00
18eb29fabd Renamed rich presence module 2021-04-20 14:34:54 -04:00
f23faa72ec Removed submodule discord/discord-rpc 2021-04-20 14:26:55 -04:00
70c7060eaf Added frequency display to presence 2021-04-20 14:26:48 -04:00
6420553ae9 Added basic rich presence 2021-04-19 21:47:11 -04:00
dc65ff3a05 Added discord-rpc to build process 2021-04-19 21:19:16 -04:00
c9a44d1ecf Added discord-rpc submodule 2021-04-19 21:06:58 -04:00
13949e0dde Merge branch 'master' of https://github.com/Starman0620/SDRPlusPlus; branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus
Merge updated files
2021-04-19 21:02:45 -04:00
35d50c91c7 Adjusted Discord UI 2021-04-19 20:59:39 -04:00
3a142b0d85 Added boilerplate code for Discord module 2021-04-19 20:58:47 -04:00
f119af5e6f Fix AGC for SDRplay 2021-04-20 01:38:32 +02:00
a91ed266d5 Fixed other potential bug on OSX 2021-04-19 19:46:03 +02:00
bdce551a50 Fixed module extension on OSX 2021-04-18 21:36:15 +02:00
89599d0bf8 More OSX fixes (this is becoming annoying...) 2021-04-18 20:30:21 +02:00
685a14a21e removed unused bug 2021-04-18 20:08:28 +02:00
24e6ac8013 Bugfix for OSX 8 2021-04-18 20:04:59 +02:00
2b4cc46a53 Bugfix for OSX 7 2021-04-18 20:00:47 +02:00
30e8b3b60e Bugfix for OSX 6 2021-04-18 19:32:18 +02:00
026e5f9bcb Bugfix for OSX 5 2021-04-18 19:29:03 +02:00
3c9b8db090 Bugfix for OSX 4 2021-04-18 19:24:56 +02:00
234e1618c8 Bugfix for OSX 3 2021-04-18 19:20:51 +02:00
bc605f1351 Bugfix for OSX 2 2021-04-18 19:17:20 +02:00
06b524213f Bugfix for OSX 2021-04-18 19:12:07 +02:00
21cea65fbe Fixed board not selected after refresh bug for hackrf 2021-04-18 17:59:42 +02:00
000429c3b5 removed debug log 2021-04-18 15:59:37 +02:00
85d79f25d9 new fixes 3 2021-04-18 04:16:17 +02:00
31b7d97097 new fixes 2 2021-04-18 04:15:31 +02:00
16098ba717 new fixesµ 2021-04-18 04:13:46 +02:00
dfd29bfa04 Added fixes for clang 2021-04-18 03:52:13 +02:00
ba25eee09a Rollback of changes to resampler 2021-04-18 01:15:57 +02:00
cf4cfb50fc Even more fixes 2021-04-18 01:04:50 +02:00
91176c9291 More fixes to wfù 2021-04-18 00:47:23 +02:00
5aa9359236 Fixed FM bug 2021-04-18 00:13:32 +02:00
f760aba7dd Fixed waterfall bug 2021-04-17 23:05:53 +02:00
b0409ad033 Fix linux bug 2021-04-17 22:41:46 +02:00
d91934955b New features + waterfall code cleanup 2021-04-17 22:37:50 +02:00
d1e553f05a Fixes to filtering 2021-04-17 03:38:48 +02:00
3bf4f0ce01 typo fix 2021-04-16 20:21:42 +02:00
892f957729 Merge pull request #96 from bvernoux/master
Fix build with Windows SDK 8.1 or more, fix typos in airspy_source
2021-04-16 19:55:05 +02:00
d903daa046 added missing files 2021-04-16 19:54:08 +02:00
b16ab3f0c0 Push before merge 2021-04-16 19:53:47 +02:00
c2bccc9b04 Fixed some typos in spdlog::error() for airspy_source (removed HF+) 2021-04-16 19:48:25 +02:00
0b9d5c2b69 Fix build with SDK 8.1 or more (replace #include <ShlObj_core.h> supported only with Windows SDK10 by #include <ShlObj.h> support by Windows SDK 8.1 or more) 2021-04-16 19:40:12 +02:00
098f09844b Fixed deemphasis bug 2021-04-14 02:03:03 +02:00
2c334c08ac Added position option for the bandplan 2021-04-14 01:45:21 +02:00
11766a2c41 added saved config to recorder 2021-04-14 01:07:54 +02:00
ce389dfd79 Added HackRF source built by default 2021-04-13 18:35:31 +02:00
48bba00cb3 Added FFTSize setting 2021-04-13 18:31:38 +02:00
b74043e2ee Changed defaults 2021-04-13 04:57:42 +02:00
64436f1034 New changes 2021-04-13 04:54:47 +02:00
58864b79e4 Slight performance improvement on detailed fft 2021-04-13 03:52:30 +02:00
cb8bbd7ccc New optional detailed FFT system 2021-04-12 23:02:45 +02:00
37ad6365e3 Another fix 2 2021-04-10 15:05:45 +02:00
6c4af86b29 Another fix 2021-04-10 15:02:36 +02:00
f492c6fc61 Fixed pluto sample rate bug 2021-04-10 14:55:35 +02:00
d41ae73e0d Beginning of code for the RSPduo + bugfix for the hackrf 2021-04-10 03:06:59 +02:00
26e623bec4 Beginning of code for the RSPduo + bugfix for the hackrf 2021-04-10 03:06:51 +02:00
aeab33127d more audio BS 2021-04-02 18:40:57 +02:00
7b6d0c1acd Fixed deemphasis unit 2021-04-02 14:19:33 +02:00
b16c6f50a6 Fixed bad class name 2021-04-02 03:33:48 +02:00
edf3743f49 Fixed meteor bandwidth 2021-04-02 03:29:48 +02:00
1675ee99a4 added throttle block 2021-04-02 00:35:05 +02:00
f88e2312b8 more bugfix on the meteor demod 2021-04-01 20:57:03 +02:00
28aaeef2b6 Fixed other issues 2 2021-04-01 17:42:30 +02:00
669288385e Fixed other issues 2021-04-01 17:42:04 +02:00
4b0516966d Fixed missing semicolon 2021-04-01 17:33:59 +02:00
a9cb6bfe79 Bugfix + added meteor demodulator 2021-04-01 16:54:16 +02:00
bcc1744a76 More bugfix 2021-03-30 03:37:40 +02:00
ce56d03c3e Another fix 2021-03-29 22:41:17 +02:00
38c1949538 Fixed bug with operators 2021-03-29 22:28:26 +02:00
27394a091f DSP performance upgrades + bugfix 2021-03-29 21:53:43 +02:00
b72246d769 Merge pull request #85 from nonoo/master
Fix file source rewind and read after it is finished
2021-03-23 00:45:18 +01:00
a6e58e7b1d Fix compile error caused by inline instructions
Issue: https://github.com/AlexandreRouma/SDRPlusPlus/issues/82
Fix: https://stackoverflow.com/questions/54654290/error-inlining-failed-to-call-always-inline
2021-03-22 22:35:28 +01:00
ad579d514b Fix file source rewind and read after it is finished 2021-03-22 22:12:55 +01:00
3b657484b2 explicitly logging if the recording started 2021-03-21 23:54:41 +01:00
eff9cd4b71 fixed bug introduced in recorder by the last commit 2021-03-21 23:53:45 +01:00
5e9486ef4c Fixed broken code 2021-03-21 19:51:38 +01:00
f29d683918 new stuff 2021-03-20 21:53:44 +01:00
f55d591cba added frequency in recording + fixed SDRplay typo 2021-03-12 22:22:14 +01:00
20ee982e3d Added persistent config for sdrplay + bugfix 2021-03-09 02:21:30 +01:00
c21db4b6d1 added new patron 2021-03-08 16:38:48 +01:00
f0ef239e22 Fixed patron name 2021-03-07 20:11:35 +01:00
a714370eb2 Preparations for RSPduo support + Added new patrons! 2021-03-07 20:10:10 +01:00
abf5ad2eec fixed swapped b and hi-z ports 2021-03-07 16:14:08 +01:00
68fdd7e3f0 Fixed gains 2021-03-07 00:57:08 +01:00
49e671802a Fixed Dab notch 2021-03-07 00:47:12 +01:00
da8fa813c5 Fix for RSP2 2021-03-07 00:22:35 +01:00
c823f0759d Added support for rsp2 and rspdx 2021-03-06 22:56:35 +01:00
c71f7898ed fixes 2021-03-06 19:23:03 +01:00
a24972843e Fixed bad argument in compile args 2021-03-01 17:39:39 +01:00
9f0262fc05 bugfix + added missing contributors 2021-02-28 17:32:22 +01:00
4fc55f75a8 Added stream name 2021-02-28 16:32:57 +01:00
fcb1d94946 Added bias-T support plus fixed bugs for sdrplay module 2021-02-26 21:33:48 +01:00
38acf81a76 Fixed wrong sdprlay lib name for linux 4 2021-02-26 18:25:52 +01:00
eaae856a37 Fixed wrong sdprlay lib name for linux 3 2021-02-26 18:13:54 +01:00
79df475c9d Fixed wrong sdprlay lib name for linux 2 2021-02-26 18:11:54 +01:00
a6ce43ba71 Fixed wrong sdprlay lib name for linxu 2021-02-26 18:04:54 +01:00
9e0fee27ab added more options to sdrplay module 2021-02-26 16:52:54 +01:00
d3a6ee6a94 Hide antenna select when SDR only has one antenna 2021-02-23 15:15:29 +01:00
a4ec53c90a Merge pull request #75 from aosync/master
Added conditionals to build on FreeBSD
2021-02-23 14:55:08 +01:00
0e7a8301f7 Merge pull request #76 from thotypous/soapy-select-antenna
Support selecting antenna for SoapySDR source
2021-02-23 14:54:18 +01:00
5b4c5bd5c1 Support selecting antenna for SoapySDR source 2021-02-23 00:36:24 -03:00
b4ee6420c5 Fixed weird value idk 2021-02-23 03:35:40 +01:00
e4c062c9c2 yeeted portaudio 2021-02-23 00:26:35 +01:00
72bd3e9cc1 Finished RtAudio sink 2021-02-22 04:04:49 +01:00
49cf3af769 Tuned rtaudio sink 2021-02-21 00:06:41 +01:00
127d6bf0a7 added rtaudio sink 2021-02-20 22:05:13 +01:00
044f8cbffd Fixed some stuff 2 2021-02-20 15:35:39 +01:00
89d0f6b761 Fixed some stuff 2021-02-20 15:27:43 +01:00
d0bea51cd4 Fixed CW demod + fixed bug in sinks 2021-02-18 02:47:19 +01:00
d325dab035 added conditionals to build on FreeBSD 2021-02-17 16:59:30 +01:00
4174554260 Added offset tuning option to rtl-sdr 2021-02-17 16:00:22 +01:00
3a81bb9725 Fixed recorder not creating default recording folder 2021-02-17 02:35:00 +01:00
f96c31deab Fixed bug in radio 2021-02-16 23:56:53 +01:00
aae6f535f5 Fixed bug with radio 2021-02-15 20:16:40 +01:00
23dcc16829 Fixed RTL-SDR module bug 2021-02-15 16:35:54 +01:00
8f9fdd8b70 Added sample rates for RTL-SDR 2021-02-15 14:55:48 +01:00
b4d06697b4 Fixed bug tuning with sdrplay and rtlsdr 2021-02-15 13:18:21 +01:00
cc9da905a6 fixed install prefix 2021-02-14 23:14:42 +01:00
e79fa4145b text in rtlsdr 2021-02-14 22:23:30 +01:00
0769b61dd2 added persistant config and bias t support to rtlsdr module 2021-02-14 15:08:54 +01:00
3a8a29402d Added RTL-SDR module by default 2021-02-13 22:48:42 +01:00
e21b876104 Added librtlsdr to CI 2021-02-13 20:46:08 +01:00
36b5af45c6 Added RTL-SDR source module 2021-02-13 20:43:29 +01:00
d0c14efbd1 Changed the FFT scaling 2021-02-12 23:11:57 +01:00
3e9e6de16d added lazy db thing 2 2021-02-12 22:47:10 +01:00
a754becb46 added lazy db thing 2021-02-12 22:44:29 +01:00
fe9af9ced6 Fixed cmakelists 2021-02-12 00:18:20 +01:00
5c9c8c2670 New debian deb build file 2021-02-12 00:15:35 +01:00
0a4c72c571 fixed cmakelists.txt 2021-02-12 00:05:25 +01:00
f765a07c3b Merge pull request #68 from Aang23/master
Generate a Linux desktop icon
2021-02-12 00:04:09 +01:00
3e81a07563 Generate a Linux desktop icon 2021-02-12 00:03:30 +01:00
c2e376879c Merge pull request #67 from Aang23/master
Properly set CMAKE_INSTALL_PREFIX to /usr by default
2021-02-11 23:35:29 +01:00
548b56cf16 Properly set CMAKE_INSTALL_PREFIX to /usr by default 2021-02-11 23:32:56 +01:00
8333912b24 cleanup up string concat 2021-02-11 23:10:01 +01:00
f879a775f8 Merge pull request #66 from Aang23/master
Support installation via CMake
2021-02-11 22:53:15 +01:00
51573146f0 Changes to sdrplay source 2021-02-11 22:53:00 +01:00
835ec716a0 Support installation via CMake 2021-02-11 22:49:33 +01:00
b86cf9681c temporarily disabled sdrplay_source build for CI 2 2021-02-11 15:39:41 +01:00
3b4cc26210 temporarily disabled sdrplay_source build for CI 2021-02-11 15:37:23 +01:00
3422998bd1 fixed file select and folder select widget bug 2021-02-11 15:29:51 +01:00
c90772666e changed limites on demodulators 2021-02-10 23:15:19 +01:00
096c5edbd4 Fixed file select length 2021-02-10 22:47:13 +01:00
3541b8a0dd new stuff idk 2021-02-10 21:35:56 +01:00
9e410e3856 VFOs are now visible in waterfall 2021-02-09 02:11:40 +01:00
35d079beb1 Maybe fixed airspy and airspyhf crash 2021-02-08 20:27:05 +01:00
c846e0f400 Added spdlog test button 2021-02-08 20:08:59 +01:00
7ff8f3f7b9 Fixed airspy_source bug 2021-02-08 19:50:16 +01:00
27eb2571a4 Fixed power slider 2021-02-08 11:13:26 +01:00
ff2ab3b27e Fixed windows code left outside ifdef 2021-02-08 01:55:57 +01:00
9df90e5e75 removed scrolling due to bug + Fixed file source 2021-02-07 23:47:17 +01:00
49ec3d68d2 Added scroll control to sliders in radio 2021-02-07 14:20:10 +01:00
9def1843b2 Added scroll to sliders 2021-02-07 12:30:25 +01:00
c26855d07e Fixed UI bug 2021-02-06 21:52:58 +01:00
a3f147a827 New recorder plugin + bugfix 2021-02-06 21:28:27 +01:00
a2d93915e8 Added airspy_source to defaults 2021-02-04 14:53:12 +01:00
29e9db184f Fixed small bug in SSB tuning 2021-02-02 21:49:35 +01:00
2f93c7ae58 Fixed wrong sample rate at startup 2021-01-30 02:21:30 +01:00
4abfe407da Removed bad files 2021-01-29 18:29:02 +01:00
9b27e81091 Fixed autobuild 3 2021-01-29 18:26:18 +01:00
39787743fd Fixed autobuild 2 2021-01-29 17:44:56 +01:00
22e47807b8 Fixed autobuild 2021-01-29 17:42:02 +01:00
898525a6d8 Added automatic build to actions 2021-01-29 17:24:10 +01:00
1ebcfe7d80 UI improvements 2021-01-29 16:50:57 +01:00
1dbc39b970 Added persistant config to airspyhf_source 2021-01-29 16:15:13 +01:00
80f5f6c288 Added persistant config to airspy_source + bugfix 2021-01-29 15:07:45 +01:00
b18acd469f Fixed missing dependency in CI 2021-01-28 21:13:09 +01:00
cefcd18269 Added airspy module + changes to the UI for scaling 2021-01-28 21:10:53 +01:00
4de3ac176d Update cmake.yml 2021-01-11 21:47:35 +01:00
afd5699ff1 Added new contributors + fixed waterfall bug 2021-01-11 03:19:09 +01:00
b79461e3ce Merge pull request #60 from mnhauke/master
Add bandplan for German mobile networks
2021-01-03 02:49:20 +01:00
de6ab8ecdf Add bandplan for LTE bands used in Germany 2021-01-02 23:43:49 +01:00
d0180d42a8 Add bandplan for German mobile networks 2021-01-02 21:59:13 +01:00
2e504b40f6 Fixed invalid colormap 2021-01-02 15:18:57 +01:00
9b00304c29 Fixed resampling bug + added waterfall colormap selection + general bugfix 2020-12-31 14:42:09 +01:00
979928ded8 Fixed resampling bug + added waterfall colormap selection + general bugfix 2020-12-31 14:26:12 +01:00
7c4e442432 Fixed gain not updated on RTL-SDR 2020-12-28 14:47:34 +01:00
0dd445f101 Merge pull request #55 from cropinghigh/master
Stepped sliders+bandwidth
2020-12-28 14:40:30 +01:00
f217804838 push before merge 2020-12-28 14:39:30 +01:00
9a630fff06 Merge branch 'master' of https://github.com/cropinghigh/SDRPlusPlus 2020-12-28 16:06:34 +03:00
db508214d7 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2020-12-28 16:05:57 +03:00
8e764f48ae Fix unusable bw 2020-12-28 16:05:35 +03:00
2583063f5f Update
Update
2020-12-27 15:30:51 +03:00
dd4ec22b39 + contributors list 2020-12-26 23:12:09 +01:00
42dbcec93f Update
Update
2020-12-27 01:11:05 +03:00
9bbf634f5d Merge pull request #52 from zakrent/master
Added SIMD to polyphase resampler
2020-12-26 23:08:45 +01:00
d6b09759de Merge pull request #51 from cropinghigh/patch-1
Addition to linux guide
2020-12-26 23:08:00 +01:00
5bb8a943ad push before merge 2020-12-26 23:02:07 +01:00
b370eda0d5 Fix bugs+move widget 2020-12-27 00:56:39 +03:00
69bcbf6f27 :Added SIMD to polyphase resampler 2020-12-26 22:18:34 +01:00
04823abb83 Fix bugs 2020-12-26 21:36:16 +03:00
7269a0ea12 Merge branch 'patch-1' of https://github.com/cropinghigh/SDRPlusPlus 2020-12-26 21:02:43 +03:00
153b58fbbd Stepped sliders 2020-12-26 21:00:09 +03:00
149af55e61 Addition to linux guide 2020-12-26 11:47:37 +03:00
9cac95fd82 push before merge 2020-12-26 00:48:12 +01:00
09498f3b18 Merge pull request #50 from AlexandreRouma/revert-49-master
Revert " Added recorder volume meter "
2020-12-26 00:47:22 +01:00
bb919d0f32 Revert " Added recorder volume meter " 2020-12-26 00:46:52 +01:00
6d0abd73a5 Merge pull request #49 from zakrent/master
Added recorder volume meter
2020-12-26 00:42:32 +01:00
717f2a822b Merge branch 'master' of https://github.com/zakrent/SDRPlusPlus 2020-12-26 01:17:37 +01:00
8946b4b4b6 Added recorder volume meter 2020-12-26 01:07:07 +01:00
db279d2b36 Fixed audio freeze on linux 2020-12-25 19:58:52 +01:00
bb7965b3c4 Fixed luckup bug 2020-12-25 18:17:43 +01:00
a33fe5a4cc Added cropinghigh to contributor's list 2020-12-25 17:22:24 +01:00
4a03f0870c Merge pull request #47 from cropinghigh/patch-3
Update russian bandplan
2020-12-25 17:10:33 +01:00
bfe15aff19 Merge pull request #48 from AlexandreRouma/double_bufferd_streams
switched all streams to double buffering
2020-12-25 17:10:13 +01:00
42bc2d01f7 switched all streams to double buffering 2020-12-25 16:58:07 +01:00
0cb9fc0df8 Update russian bandplan 2020-12-25 16:39:24 +03:00
450896b122 OpenGL version fix for shitty SoCs 2020-12-24 23:38:45 +01:00
cc0b89dbe2 fixed wrong dependency in readme 2020-12-24 19:44:17 +01:00
c887b96a77 fixed wrong dependency in readme 2020-12-24 19:44:07 +01:00
22541ae0f4 updated readme 2020-12-24 16:27:12 +01:00
d83da38d79 Added windows build script 2020-12-24 14:43:14 +01:00
b21f8abbd6 fixed missing module 2020-12-24 00:11:33 +01:00
e9aade4d0d fixed debian build again again 2020-12-23 23:39:34 +01:00
2bf2fff3d6 fixed debian build again 2020-12-23 23:34:56 +01:00
463a22fdfb fixed debian build 2 2020-12-23 23:20:28 +01:00
3175022b31 fixed debian build 2020-12-23 23:18:44 +01:00
504d910226 removed automated debian package build 2020-12-23 22:21:46 +01:00
c2769e1a72 fixed CI 2 2020-12-23 21:59:05 +01:00
7577253dbf fixed CI 2020-12-23 21:19:02 +01:00
e4c5b2dbd1 fixed directories 2020-12-23 21:10:24 +01:00
fafd76ff94 Added debian package build 2020-12-23 20:58:02 +01:00
e354d11820 Added debian package build 2020-12-23 20:57:41 +01:00
a3374c7eca fixed airspyhf module missing function 2020-12-23 20:22:20 +01:00
552b886cea fixed volk version in CI 2020-12-23 20:10:55 +01:00
ff9a19381b switch to more recent ubuntu version for CI 2020-12-23 20:09:08 +01:00
77aacc2e5d Fixed compiler version 2020-12-23 19:50:54 +01:00
6a1fa2c13b added more recent GCC version to CI 2020-12-23 19:44:34 +01:00
c3bb64bf6e Update cmake.yml 2020-12-23 19:39:14 +01:00
da68ab4ed0 Create cmake.yml 2020-12-23 19:36:12 +01:00
1aedf92bcd fixes to the rtl-tcp source 2020-12-23 19:23:47 +01:00
abcf484506 removed un-necessary directory 2020-12-23 00:12:52 +01:00
a93681a980 New system for band plans 2020-12-23 00:11:12 +01:00
a08758ea54 fixed directory bug on linux 2 2020-12-22 23:10:49 +01:00
0a0f5b8e8c fixed directory bug on linux 2020-12-22 23:04:46 +01:00
84f67a3ac1 updated readme about new directory system 2020-12-22 22:45:27 +01:00
22d18a9e58 new directory system on linux 2020-12-22 22:39:24 +01:00
d1a8425d43 Create FUNDING.yml 2020-12-22 21:42:16 +01:00
65d94f03e4 Fixed compile bugs 2020-12-22 21:23:49 +01:00
3a49041f27 other fix 2020-12-22 21:09:49 +01:00
eec2f7c4a0 fixed bug in spyserevr source 2020-12-22 20:44:49 +01:00
720df5ce89 Fixed build issues 2 2020-12-22 20:37:10 +01:00
d7cea16d4a Fixed build issues 2020-12-22 20:35:31 +01:00
d5c0fdd525 fixed linux build bug 2020-12-22 20:00:51 +01:00
4a86d6073c added better credit system 2020-12-22 18:42:30 +01:00
98b6e580b4 updated readme 2020-12-22 18:23:26 +01:00
c96c69c112 changes to the build system 2020-12-22 14:56:57 +01:00
bd545feb2c changes to the build system 2020-12-22 14:50:26 +01:00
e90b6656c3 Fixed airspy hf+ module bug 2020-12-16 01:45:17 +01:00
5099c16a12 Added airpyhf_source 2020-12-15 23:05:11 +01:00
d9dcfa4a88 Fixed waterfall non-threadsafe behavior 2020-12-14 19:33:30 +01:00
1fcd783dd9 fixed zoom bug 2020-12-14 18:14:04 +01:00
2c84123158 modified soapy menu 2020-12-14 17:18:43 +01:00
c3d39029b8 Merge pull request #42 from wingrime/sample-rate
Soapysdr: Improve sample rate information
2020-12-14 17:01:50 +01:00
20b703f8bf push before merge 2020-12-14 16:37:56 +01:00
89c579880c Soapy: Refresh button 2020-12-14 19:46:05 +05:00
db389372ad Soapysdr: Improve sample rate information 2020-12-14 19:34:44 +05:00
3a6eaf6526 Removed second radio from the default config 2020-12-14 01:15:52 +01:00
3e27af472b added more info to the readme 2020-12-14 01:07:30 +01:00
7bea6058fe modified readme 2020-12-14 00:47:11 +01:00
b02b6c30b5 Merge pull request #40 from AlexandreRouma/better_dsp
Better dsp
2020-12-14 00:23:35 +01:00
46e9266752 fixed soapy bug 2020-12-13 14:52:54 +01:00
e3db19b16a Bugfixed + performance improvements to the waterfall 2020-12-12 05:34:58 +01:00
774663d70d Added squelch to radio 2020-12-10 05:18:40 +01:00
2c729bf646 Added persistant settings to recorder module 2020-12-09 19:45:32 +01:00
9b1c9e9e29 Added persistant config for the selected demodulator 2020-12-09 15:28:31 +01:00
80badebb37 Added persistant setting sto demodulator of radio module 2020-12-09 15:16:38 +01:00
fc9e155481 random bug fixes 2020-12-09 04:47:30 +01:00
16d8a31c12 center tuning and more 2020-12-08 16:27:52 +01:00
7ba6081cb3 tweaks 2020-12-08 04:44:19 +01:00
c3a8865dd3 Fixed a tone of stuff + new features 2020-12-08 04:36:37 +01:00
929ca50b06 Added AGC + Started working on channel selection 2020-12-07 04:13:16 +01:00
e5123dd8bf Fixed UI bug in radio 2020-12-06 20:02:22 +01:00
fe1de4bed9 Fixed SSB demod bug 2020-12-06 19:51:56 +01:00
c612620ca5 testing 2020-12-06 17:57:44 +01:00
ca9d2c01af other potential fix 2020-12-06 17:26:42 +01:00
51d90c1898 fixed missing include 2020-12-06 17:02:47 +01:00
a6a4193fbb potential fix to audio issues 2020-12-06 16:46:50 +01:00
f4f8c77ffa Fixed bugs + new radio 2020-12-06 16:13:47 +01:00
9b8c1a3072 More bugfix + folder selection in recorder 2020-12-05 22:42:12 +01:00
92b77904f6 more fixes 2020-12-04 20:12:36 +01:00
9805e4a395 Fixed loading screen 2020-11-30 21:17:36 +01:00
6a01c9d426 trying to fix underrun when switching sdr 2020-11-30 17:43:53 +01:00
48df92c8a5 Fixed bug in GUI 2020-11-30 16:45:02 +01:00
5bb2f9bf05 Fixed compile bug on linux 2020-11-30 16:30:45 +01:00
e5dbac4345 Finished sink module system + new icons 2020-11-30 16:05:51 +01:00
618d4ac4cc fix 2020-11-30 05:51:33 +01:00
19e516f206 Push before potential f*ck up 2020-11-29 20:55:00 +01:00
afadb71d64 Fixed sample rate bug 2020-11-28 23:25:14 +01:00
b3d1eabbad Fixed sample rate bug 2020-11-28 23:24:45 +01:00
eac0a7a13f Fixed FIR bug with the pluto module 2020-11-26 19:25:58 +01:00
e06ed84330 fixed wrong ip string 2020-11-25 21:54:30 +01:00
4eae7c3ba5 plutosdr support 2020-11-25 21:52:37 +01:00
61a612cf30 More work on the sink interface 2020-11-22 18:26:48 +01:00
f1084157a3 fixed issues at 64MS/s 2020-11-13 02:04:37 +01:00
de3b056133 Partial RX888 source module 2020-11-12 21:23:18 +01:00
02ae50905d added rx888 2020-11-12 00:53:38 +01:00
0a5fd5c271 Fixed small bug in waterfall 2020-11-04 05:08:42 +01:00
ef968ac1fb finally fixed the waterfall7 2020-11-04 04:11:51 +01:00
3156236745 Fixed DSP 2020-11-04 00:42:39 +01:00
5d320fdd53 Fixes 2020-11-03 19:22:53 +01:00
cee6af1e14 fixing audio bug 2020-11-02 21:13:28 +01:00
35c7f0e3cf Fixed stall 2020-11-02 17:48:17 +01:00
fc9bc496cb fixed 2020-11-02 16:16:21 +01:00
75f8a45119 new dsp 2020-11-02 03:57:44 +01:00
50a73a380d more fixes 2020-10-31 15:07:49 +01:00
e62042d26a Update readme.md 2020-10-24 18:56:32 +02:00
c109de3949 Merge pull request #31 from aosync/openbsd
Build on OpenBSD
2020-10-24 18:54:47 +02:00
39c87782db Merge branch 'experimental' into openbsd 2020-10-24 18:54:34 +02:00
b6566dde14 Merge pull request #32 from howard0su/experimental
Add instruction for Linux
2020-10-24 17:50:32 +02:00
ef36283370 Add instruction for Linux 2020-10-24 23:41:40 +08:00
922a226028 fixed OpenBSD build 4 2020-10-24 17:34:18 +02:00
ba81f25933 Added OpenBSD build instructions 2020-10-24 17:18:54 +02:00
da9528576a fixed openbsd build 3 2020-10-24 17:09:25 +02:00
3fdd2477e5 Fixed typo in root CMakeLists.txt 2020-10-24 15:33:00 +02:00
62368e35a7 Merge pull request #29 from aosync/experimental
make prepare_root.sh an executable and take advantage of globbing
2020-10-24 14:56:10 +02:00
82d3431f1d Merge pull request #28 from howard0su/experimental
Build system fix and cleanup
2020-10-24 14:55:41 +02:00
edbc0c149d fixed linux bugs 2020-10-24 14:51:55 +02:00
b8987e6d2d make prepare_root.sh a proper program and take advantage of globbing 2020-10-24 09:44:15 +02:00
6296b8865b fftw lib is not used but fftw3f only 2020-10-23 17:07:02 +08:00
2df185e340 Copy volk.dll when building on Windows 2020-10-23 17:06:10 +08:00
6262c64daa Consolidate Linux and OSX build
Use pkg-config to find the right include path and lib names.
2020-10-23 10:53:48 +08:00
0fe5af9816 Fix volk.h include path. It should be volk/volk.h 2020-10-23 10:39:20 +08:00
e94888d533 Use pkg for every module, and link them static 2020-10-23 10:37:10 +08:00
6130428989 glfwSetWindowMaximizeCallback is added in 3.3 2020-10-23 10:36:14 +08:00
5400a4e18a Fix build on Debian 10 2020-10-22 23:55:49 +08:00
0d45217dfd Added baseband recording 2020-10-22 12:53:46 +02:00
fa1e647235 Merge pull request #26 from AlexandreRouma/better_modules
Better modules
2020-10-22 11:03:32 +02:00
d637cb9e75 Merge pull request #24 from howard0su/osx
another warning
2020-10-22 11:01:24 +02:00
406f18bf11 another warning 2020-10-22 09:18:58 +08:00
72611b5fa7 Fixed OpenGL memory leak 2020-10-22 03:16:11 +02:00
313b786d88 Merge pull request #23 from howard0su/warnings
Fix warnings
2020-10-22 02:54:01 +02:00
801a56787f Merge pull request #22 from howard0su/osx
Fix OSX build
2020-10-22 02:53:50 +02:00
62868b2533 Fix warnings 2020-10-22 08:48:31 +08:00
4bf88739b5 Fix OSX build 2020-10-22 08:33:55 +08:00
087380c966 Push before merge 2020-10-22 02:33:50 +02:00
fbd7321b48 Fixed typo 2020-10-20 19:55:14 +02:00
f6cfe83d45 Fixed typo 2020-10-20 19:15:43 +02:00
71f6be8d08 Fixed VFO alignment 2020-10-20 14:59:42 +02:00
6e5450ed24 new fixes 2020-10-20 00:32:17 +02:00
027054b57e Fixed waterfall inaccuracies + started adding stepped VFO 2020-10-15 16:09:01 +02:00
3b6a3ff94d Fixed scripting + cleaner code + fixed RTLTCP 2020-10-07 22:44:54 +02:00
46d5b8a750 new scripting system 2020-10-07 14:44:39 +02:00
ac068036b8 Finished soapy module + added file source + added RTL_TCP source (windows only rn) 2020-10-06 15:50:46 +02:00
ace1fe1e5e added linux support for rtl_tcp 2020-10-06 10:54:00 +02:00
14a8e81662 Fixed missing label bug of soapysdr 2020-10-05 18:20:27 +02:00
eff7bbdd5a added new patrons 2020-10-04 15:23:40 +02:00
60342de9c0 Finished soapy module + added file source + added RTL_TCP source (windows only rn) 2020-10-04 02:56:02 +02:00
47b04ffef4 More work on the source module system 2020-10-02 01:44:18 +02:00
1507e6ec31 New module system 2020-10-01 13:46:12 +02:00
524f20bc2f Save before changes 2020-10-01 01:21:15 +02:00
2c4d7cbf09 Moved menus to their own respective files 2020-09-25 14:25:36 +02:00
5fedda08d7 More fixes 3 2020-09-24 19:50:22 +02:00
2056eae139 More fixes 2 2020-09-24 19:38:05 +02:00
48a8b04eaa More fixes 2020-09-24 19:36:57 +02:00
51ee02f9da Fixed windows bugs 2020-09-20 02:18:01 +02:00
ab8ce4c53f Fixed warnings on linux 2020-09-20 01:36:25 +02:00
2aaf254565 Fixed issues with new module system 6 2020-09-20 00:56:00 +02:00
109696c65a Fixed issues with new module system 5 2020-09-20 00:44:45 +02:00
35ef99c6e9 Fixed issues with new module system 4 2020-09-20 00:41:35 +02:00
91d382ca0c Fixed issues with new module system 3 2020-09-20 00:33:38 +02:00
ec234e99a1 Fixed issues with new module system 2 2020-09-20 00:26:45 +02:00
9de585190f Fixed issues with new module system 2020-09-20 00:19:39 +02:00
d6b9e1d86a new modole system 2020-09-19 12:48:34 +02:00
1ef31f0f8b new stuff 2020-09-18 00:23:03 +02:00
c1052b1b28 easier build 2020-09-06 16:31:50 +02:00
e497122c06 Merge pull request #17 from howard0su/fix_warn
Add a missing else to shutdown a warning message
2020-09-06 16:43:16 +03:00
407fcaadc6 Merge pull request #18 from howard0su/fil
cleanup DecimatingFIRFilter code
2020-09-06 16:43:05 +03:00
7e6f24d203 Merge pull request #16 from howard0su/fix_vfo
Freq can be zero
2020-09-06 16:42:41 +03:00
c0825dbeeb Push before merge 2020-09-06 15:39:09 +02:00
39a65b51fb optimize DecimatingFIRFilter 2020-09-06 08:42:05 +08:00
acf3fe0297 Add a missing else to shutdown a warning message 2020-09-06 08:40:42 +08:00
70c2ef36f5 Freq can be zero
In Zero-IF scenerio, freq can be zero.
2020-09-06 08:38:50 +08:00
7190acfe9e Fixed band plan name not appearing correctly 2020-08-21 17:12:48 +02:00
bf6210721d Fixed config not saved properly 2020-08-21 17:11:12 +02:00
27731f376a fixed default config file 2020-08-21 15:47:19 +02:00
d82260d4d4 New stuff ++++ 2020-08-21 15:38:27 +02:00
78086a79f4 New stuff +++ 2020-08-21 15:37:34 +02:00
aa2caa67ad New stuff ++ 2020-08-21 15:34:50 +02:00
709627a738 New stuff lmao 2020-08-20 18:29:23 +02:00
649be86cb6 added a recorder module 2020-08-18 00:56:51 +02:00
b56aab8f74 new styles 2020-08-17 02:39:56 +02:00
53ec38766a bruh 2020-08-16 19:34:53 +02:00
dbe811b47a bruh 2020-08-16 19:28:14 +02:00
f08515420e bruh 2020-08-16 19:07:17 +02:00
11913ab683 bruh 2020-08-16 19:01:59 +02:00
1cecc78c0c bruh 2020-08-16 18:57:50 +02:00
6717c43fc2 bruh 2020-08-16 18:51:20 +02:00
e44d20bdbc bruh 2020-08-16 18:46:30 +02:00
e50ed1b960 bruh 2020-08-16 18:30:40 +02:00
cff5987329 Fixed delete[] in soapy.h 2020-08-16 18:22:22 +02:00
b2191c5d2c Fixed delete[] in soapy.h 2020-08-16 18:18:10 +02:00
03dc5d2042 Fixed SoapySDR trying to set gain on no device 2020-08-16 18:06:21 +02:00
19e07eb767 Fixed gain memory error 2020-08-16 17:56:12 +02:00
c4f49203a1 testing on linux 2020-08-16 17:36:48 +02:00
9830337103 small fixes 2020-08-16 14:26:22 +02:00
eadaf3ce6b a LOT of new stuff 2020-08-16 03:39:05 +02:00
31a95031e4 Full module system 2020-08-12 16:43:44 +02:00
cdea80f8c5 modules 2020-08-11 18:33:42 +02:00
b65bddc1b3 multi-vfo 2020-08-10 02:30:25 +02:00
7759de96da added module system 2020-08-07 14:29:06 +02:00
9d2b60b88e Updated screenshot 2020-08-05 23:27:39 +02:00
a9c82ecc81 Band plans alpha 2020-08-05 21:13:53 +02:00
3aa8ce80db Adding credits to readme 2 2020-08-05 14:06:31 +02:00
294337aa63 Adding credits to readme 2 2020-08-05 13:12:30 +02:00
908abf0743 Adding credits to readme 2 2020-08-05 13:11:42 +02:00
509e4aa7f7 Adding credits to readme 2 2020-08-05 13:07:12 +02:00
13fbd6a800 Adding credits to readme 2 2020-08-05 13:04:46 +02:00
b430f88245 Adding credits to readme 2020-08-05 13:00:32 +02:00
e24e3cbc2a more fixes 2020-08-05 12:50:34 +02:00
08292c279e Bandplan system on new branch 2020-08-05 11:23:13 +02:00
10f1b992dd Bandplan system fix 2020-08-05 11:17:47 +02:00
022898c61d Bandplan system 2020-08-04 21:34:56 +02:00
cd7e5cf1bc Oops, forgot to push before leaving 2020-08-04 14:19:41 +02:00
f5d6e609d7 Fixed slow waterfall with low sample rates 2020-07-21 01:58:47 +02:00
ed49ad6f7f Fixed humming noise 2020-07-20 20:22:13 +02:00
f8c9aa1be4 Fixed crash when starting with no source 2 2020-07-20 18:18:49 +02:00
e364ebac6d Fixed crash when starting with no source 2020-07-20 18:18:08 +02:00
1a42421d92 fixed crash when no dev detected 3 2020-07-20 17:34:05 +02:00
e8ae20232a fixed crash when no dev detected 2 2020-07-20 17:33:06 +02:00
3216daec2b fixed crash when no dev detected 2020-07-20 17:29:13 +02:00
0dc14c663a fixed resamplers again... 2020-07-20 17:21:58 +02:00
07d3fa2034 Fixed bad allocation of waterfall framebuffer 2020-07-20 16:19:35 +02:00
f1fd6fce7a Bug fix in resampler 2020-07-20 16:04:45 +02:00
355a348ba0 more bugfix 2020-07-20 15:26:59 +02:00
d85fed00a0 Fixed overlapping memcpy 2020-07-20 13:48:47 +02:00
d47679c5cf Fixedd buffer overflow in audio IO 2020-07-20 13:45:05 +02:00
379d6f8101 Bugfix + linux support 2020-07-20 13:36:16 +02:00
43f6802199 added license 2020-07-20 00:43:36 +02:00
6bfe15b14e New readme 2020-07-19 22:00:01 +02:00
559 changed files with 137216 additions and 9144 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
patreon: ryzerth

247
.github/workflows/build_all.yml vendored Normal file
View File

@ -0,0 +1,247 @@
name: Build Binaries
on: [push, pull_request]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Download PothosSDR
run: Invoke-WebRequest -Uri "https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2020.01.26-vc14-x64.exe" -OutFile ${{runner.workspace}}/pothos.exe
- name: Install PothosSDR
run: mkdir "C:/Program Files/PothosSDR" ; 7z x ${{runner.workspace}}/pothos.exe -o"C:/Program Files/PothosSDR/"
- name: Download libusb
run: Invoke-WebRequest -Uri "https://github.com/libusb/libusb/releases/download/v1.0.23/libusb-1.0.23.7z" -OutFile ${{runner.workspace}}/libusb.7z
- name: Patch Pothos with earlier libusb version
working-directory: ${{runner.workspace}}
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/"
- name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
- name: Install SDRPlay API
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
- name: Install vcpkg dependencies
run: vcpkg install fftw3:x64-windows glew:x64-windows glfw3:x64-windows portaudio:x64-windows
- name: Install rtaudio
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON
- name: Build
working-directory: ${{runner.workspace}}/build
run: cmake --build . --config Release
- name: Create Archive
working-directory: ${{runner.workspace}}
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Install dependencies
run: brew install fftw glew glfw volk airspy portaudio hackrf rtl-sdr libbladerf
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_AIRSPYHF_SOURCE=OFF -DOPT_BUILD_PLUTOSDR_SOURCE=OFF -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON
- name: Build
working-directory: ${{runner.workspace}}/build
run: make -j3
- name: Create Archive
working-directory: ${{runner.workspace}}
run: sh $GITHUB_WORKSPACE/make_macos_package.sh ${{runner.workspace}}/build
- name: Save Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_macos_amd64
path: ${{runner.workspace}}/sdrpp_macos_amd64.pkg
build_debian_buster:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bullseye:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_groovy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_groovy && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_ubuntu_groovy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_hirsute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_hirsute && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v2
with:
name: sdrpp_ubuntu_hirsute_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
create_full_archive:
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_groovy', 'build_ubuntu_hirsute']
runs-on: ubuntu-latest
steps:
- name: Download All Builds
uses: actions/download-artifact@v2
- name: Create Archive
run: >
mkdir sdrpp_all &&
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
mv sdrpp_macos_amd64/sdrpp_macos_amd64.pkg sdrpp_all/ &&
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_groovy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_groovy_amd64.deb &&
mv sdrpp_ubuntu_hirsute_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_hirsute_amd64.deb
- uses: actions/upload-artifact@v2
with:
name: sdrpp_all
path: sdrpp_all/

12
.gitignore vendored
View File

@ -1,3 +1,13 @@
build/
.vscode/
*.old
.vs/
.idea/
*.old
*.dll
*.exe
*.zip
*.wav
.DS_Store
root_dev/
Folder.DotSettings.user
CMakeSettings.json

View File

@ -1,48 +1,208 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.13)
project(sdrpp)
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_INSTALL_PREFIX "/usr/local")
else()
set(CMAKE_INSTALL_PREFIX "/usr")
endif()
# Compiler config
set(CMAKE_CXX_FLAGS "-O2 /std:c++17")
# Sources
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Depedencies: libairspy)" ON)
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Depedencies: libairspyhf)" ON)
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Depedencies: libbladeRF)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Depedencies: liblimesuite)" OFF)
option(OPT_BUILD_SDDC_SOURCE "Build SDDC Source Module (Depedencies: libusb-1.0)" OFF)
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Depedencies: librtlsdr)" ON)
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Depedencies: libsdrplay)" OFF)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Depedencies: soapysdr)" ON)
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON)
# PothosSDR
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
# Sinks
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Depedencies: portaudio)" OFF)
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Depedencies: portaudio)" OFF)
# Volk
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
link_libraries(volk)
# Decoders
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" ON)
# SoapySDR
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
link_libraries(SoapySDR)
# Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
# Main code
include_directories(sdrpp "src/")
include_directories(sdrpp "src/imgui")
file(GLOB SRC "src/*.cpp")
file(GLOB IMGUI "src/imgui/*.cpp")
add_executable(sdrpp ${SRC} ${IMGUI})
# Compiler arguments for each platform
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
# Glew
find_package(GLEW REQUIRED)
target_link_libraries(sdrpp PRIVATE GLEW::GLEW)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE glfw)
# FFTW3
find_package(FFTW3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3)
find_package(FFTW3f CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f)
find_package(FFTW3l CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3l)
# PortAudio
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
# Core of SDR++
add_subdirectory("core")
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
# Source modules
if (OPT_BUILD_AIRSPY_SOURCE)
add_subdirectory("airspy_source")
endif (OPT_BUILD_AIRSPY_SOURCE)
if (OPT_BUILD_AIRSPYHF_SOURCE)
add_subdirectory("airspyhf_source")
endif (OPT_BUILD_AIRSPYHF_SOURCE)
if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE)
if (OPT_BUILD_FILE_SOURCE)
add_subdirectory("file_source")
endif (OPT_BUILD_FILE_SOURCE)
if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)
if (OPT_BUILD_SDDC_SOURCE)
add_subdirectory("sddc_source")
endif (OPT_BUILD_SDDC_SOURCE)
if (OPT_BUILD_RTL_SDR_SOURCE)
add_subdirectory("rtl_sdr_source")
endif (OPT_BUILD_RTL_SDR_SOURCE)
if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDRPLAY_SOURCE)
add_subdirectory("sdrplay_source")
endif (OPT_BUILD_SDRPLAY_SOURCE)
if (OPT_BUILD_SOAPY_SOURCE)
add_subdirectory("soapy_source")
endif (OPT_BUILD_SOAPY_SOURCE)
if (OPT_BUILD_SPYSERVER_SOURCE)
add_subdirectory("spyserver_source")
endif (OPT_BUILD_SPYSERVER_SOURCE)
if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
# Sink modules
if (OPT_BUILD_AUDIO_SINK)
add_subdirectory("audio_sink")
endif (OPT_BUILD_AUDIO_SINK)
if (OPT_BUILD_PORTAUDIO_SINK)
add_subdirectory("portaudio_sink")
endif (OPT_BUILD_PORTAUDIO_SINK)
if (OPT_BUILD_NETWORK_SINK)
add_subdirectory("network_sink")
endif (OPT_BUILD_NETWORK_SINK)
if (OPT_BUILD_NEW_PORTAUDIO_SINK)
add_subdirectory("new_portaudio_sink")
endif (OPT_BUILD_NEW_PORTAUDIO_SINK)
# Decoders
if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER)
if (OPT_BUILD_METEOR_DEMODULATOR)
add_subdirectory("meteor_demodulator")
endif (OPT_BUILD_METEOR_DEMODULATOR)
if (OPT_BUILD_RADIO)
add_subdirectory("radio")
endif (OPT_BUILD_RADIO)
if (OPT_BUILD_WEATHER_SAT_DECODER)
add_subdirectory("weather_sat_decoder")
endif (OPT_BUILD_WEATHER_SAT_DECODER)
# Misc
if (OPT_BUILD_DISCORD_PRESENCE)
add_subdirectory("discord_integration")
endif (OPT_BUILD_DISCORD_PRESENCE)
if (OPT_BUILD_FREQUENCY_MANAGER)
add_subdirectory("frequency_manager")
endif (OPT_BUILD_FREQUENCY_MANAGER)
if (OPT_BUILD_RECORDER)
add_subdirectory("recorder")
endif (OPT_BUILD_RECORDER)
if (OPT_BUILD_RIGCTL_SERVER)
add_subdirectory("rigctl_server")
endif (OPT_BUILD_RIGCTL_SERVER)
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Copy dynamic libs over
if (MSVC)
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
target_link_libraries(sdrpp PUBLIC pthread)
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON
# Install directives
install(TARGETS sdrpp DESTINATION bin)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/bandplans DESTINATION share/sdrpp)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/colormaps DESTINATION share/sdrpp)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/fonts DESTINATION share/sdrpp)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/icons DESTINATION share/sdrpp)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/root/res/themes DESTINATION share/sdrpp)
configure_file(${CMAKE_SOURCE_DIR}/sdrpp.desktop ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop @ONLY)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdrpp.desktop DESTINATION /usr/share/applications)
endif ()
# Create uninstall target
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)

View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.13)
project(airspy_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(airspy_source SHARED ${SRC})
target_link_libraries(airspy_source PRIVATE sdrpp_core)
set_target_properties(airspy_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(airspy_source PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(airspy_source PUBLIC airspy)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBAIRSPY REQUIRED libairspy)
target_include_directories(airspy_source PUBLIC ${LIBAIRSPY_INCLUDE_DIRS})
target_link_directories(airspy_source PUBLIC ${LIBAIRSPY_LIBRARY_DIRS})
target_link_libraries(airspy_source PUBLIC ${LIBAIRSPY_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(airspy_source PUBLIC "/usr/local/include")
endif()
endif ()
# Install directives
install(TARGETS airspy_source DESTINATION lib/sdrpp/plugins)

607
airspy_source/src/main.cpp Normal file
View File

@ -0,0 +1,607 @@
#include <imgui.h>
#include <spdlog/spdlog.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <config.h>
#include <options.h>
#include <libairspy/airspy.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "airspy_source",
/* Description: */ "Airspy source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
class AirspySourceModule : public ModuleManager::Instance {
public:
AirspySourceModule(std::string name) {
this->name = name;
airspy_init();
sampleRate = 10000000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
refresh();
if (sampleRateList.size() > 0) {
sampleRate = sampleRateList[0];
}
// Select device from config
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
sigpath::sourceManager.registerSource("Airspy", &handler);
}
~AirspySourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("Airspy");
airspy_exit();
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
void refresh() {
devList.clear();
devListTxt = "";
uint64_t serials[256];
int n = airspy_list_devices(serials, 256);
char buf[1024];
for (int i = 0; i < n; i++) {
sprintf(buf, "%016" PRIX64, serials[i]);
devList.push_back(serials[i]);
devListTxt += buf;
devListTxt += '\0';
}
}
void selectFirst() {
if (devList.size() != 0) {
selectBySerial(devList[0]);
}
}
void selectByString(std::string serial) {
char buf[1024];
for (int i = 0; i < devList.size(); i++) {
sprintf(buf, "%016" PRIX64, devList[i]);
std::string str = buf;
if (serial == str) {
selectBySerial(devList[i]);
return;
}
}
selectFirst();
}
void selectBySerial(uint64_t serial) {
airspy_device* dev;
try {
int err = airspy_open_sn(&dev, serial);
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
spdlog::error("Could not open Airspy {0}", buf);
selectedSerial = 0;
return;
}
}
catch (std::exception e) {
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
spdlog::error("Could not open Airspy {0}", buf);
}
selectedSerial = serial;
uint32_t sampleRates[256];
airspy_get_samplerates(dev, sampleRates, 0);
int n = sampleRates[0];
airspy_get_samplerates(dev, sampleRates, n);
sampleRateList.clear();
sampleRateListTxt = "";
for (int i = 0; i < n; i++) {
sampleRateList.push_back(sampleRates[i]);
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
sampleRateListTxt += '\0';
}
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
selectedSerStr = std::string(buf);
// Load config here
config.acquire();
bool created = false;
if (!config.conf["devices"].contains(selectedSerStr)) {
created = true;
config.conf["devices"][selectedSerStr]["sampleRate"] = 10000000;
config.conf["devices"][selectedSerStr]["gainMode"] = 0;
config.conf["devices"][selectedSerStr]["sensitiveGain"] = 0;
config.conf["devices"][selectedSerStr]["linearGain"] = 0;
config.conf["devices"][selectedSerStr]["lnaGain"] = 0;
config.conf["devices"][selectedSerStr]["mixerGain"] = 0;
config.conf["devices"][selectedSerStr]["vgaGain"] = 0;
config.conf["devices"][selectedSerStr]["lnaAgc"] = false;
config.conf["devices"][selectedSerStr]["mixerAgc"] = false;
config.conf["devices"][selectedSerStr]["biasT"] = false;
}
// Load sample rate
srId = 0;
sampleRate = sampleRateList[0];
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
for (int i = 0; i < sampleRateList.size(); i++) {
if (sampleRateList[i] == selectedSr) {
srId = i;
sampleRate = selectedSr;
break;
}
}
}
// Load gains
if (config.conf["devices"][selectedSerStr].contains("gainMode")) {
gainMode = config.conf["devices"][selectedSerStr]["gainMode"];
}
if (config.conf["devices"][selectedSerStr].contains("sensitiveGain")) {
sensitiveGain = config.conf["devices"][selectedSerStr]["sensitiveGain"];
}
if (config.conf["devices"][selectedSerStr].contains("linearGain")) {
linearGain = config.conf["devices"][selectedSerStr]["linearGain"];
}
if (config.conf["devices"][selectedSerStr].contains("lnaGain")) {
lnaGain = config.conf["devices"][selectedSerStr]["lnaGain"];
}
if (config.conf["devices"][selectedSerStr].contains("mixerGain")) {
mixerGain = config.conf["devices"][selectedSerStr]["mixerGain"];
}
if (config.conf["devices"][selectedSerStr].contains("vgaGain")) {
vgaGain = config.conf["devices"][selectedSerStr]["vgaGain"];
}
if (config.conf["devices"][selectedSerStr].contains("lnaAgc")) {
lnaAgc = config.conf["devices"][selectedSerStr]["lnaAgc"];
}
if (config.conf["devices"][selectedSerStr].contains("mixerAgc")) {
mixerAgc = config.conf["devices"][selectedSerStr]["mixerAgc"];
}
// Load Bias-T
if (config.conf["devices"][selectedSerStr].contains("biasT")) {
biasT = config.conf["devices"][selectedSerStr]["biasT"];
}
config.release(created);
airspy_close(dev);
}
private:
std::string getBandwdithScaled(double bw) {
char buf[1024];
if (bw >= 1000000.0) {
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
}
else if (bw >= 1000.0) {
sprintf(buf, "%.1lfKHz", bw / 1000.0);
}
else {
sprintf(buf, "%.1lfHz", bw);
}
return std::string(buf);
}
static void menuSelected(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
spdlog::info("AirspySourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
spdlog::info("AirspySourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
spdlog::error("Tried to start Airspy source with null serial");
return;
}
int err = airspy_open_sn(&_this->openDev, _this->selectedSerial);
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
spdlog::error("Could not open Airspy {0}", buf);
return;
}
airspy_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
airspy_set_freq(_this->openDev, _this->freq);
if (_this->gainMode == 0) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
else if (_this->gainMode == 1) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
else if (_this->gainMode == 2) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
airspy_set_rf_bias(_this->openDev, _this->biasT);
airspy_start_rx(_this->openDev, callback, _this);
_this->running = true;
spdlog::info("AirspySourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
airspy_close(_this->openDev);
_this->stream.clearWriteStop();
spdlog::info("AirspySourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) {
airspy_set_freq(_this->openDev, freq);
}
_this->freq = freq;
spdlog::info("AirspySourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (_this->running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(CONCAT("##_airspy_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
_this->selectBySerial(_this->devList[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
}
if (ImGui::Combo(CONCAT("##_airspy_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
_this->sampleRate = _this->sampleRateList[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
config.release(true);
}
}
ImGui::SameLine();
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
if (ImGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
_this->refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { style::endDisabled(); }
ImGui::BeginGroup();
ImGui::Columns(3, CONCAT("AirspyGainModeColumns##_", _this->name), false);
if (ImGui::RadioButton(CONCAT("Sensitive##_airspy_gm_", _this->name), _this->gainMode == 0)) {
_this->gainMode = 0;
if (_this->running) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
config.release(true);
}
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("Linear##_airspy_gm_", _this->name), _this->gainMode == 1)) {
_this->gainMode = 1;
if (_this->running) {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
config.release(true);
}
}
ImGui::NextColumn();
if (ImGui::RadioButton(CONCAT("Free##_airspy_gm_", _this->name), _this->gainMode == 2)) {
_this->gainMode = 2;
if (_this->running) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
config.release(true);
}
}
ImGui::Columns(1, CONCAT("EndAirspyGainModeColumns##_", _this->name), false);
ImGui::EndGroup();
// Gain menus
if (_this->gainMode == 0) {
ImGui::Text("Gain");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspy_sens_gain_", _this->name), &_this->sensitiveGain, 0, 21)) {
if (_this->running) {
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
config.release(true);
}
}
}
else if (_this->gainMode == 1) {
ImGui::Text("Gain");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspy_lin_gain_", _this->name), &_this->linearGain, 0, 21)) {
if (_this->running) {
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
config.release(true);
}
}
}
else if (_this->gainMode == 2) {
// Calculate position of sliders
float pos = ImGui::CalcTextSize("Mixer Gain").x + 10;
if (_this->lnaAgc) { style::beginDisabled(); }
ImGui::Text("LNA Gain");
ImGui::SameLine();
ImGui::SetCursorPosX(pos);
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspy_lna_gain_", _this->name), &_this->lnaGain, 0, 15)) {
if (_this->running) {
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
config.release(true);
}
}
if (_this->lnaAgc) { style::endDisabled(); }
if (_this->mixerAgc) { style::beginDisabled(); }
ImGui::Text("Mixer Gain");
ImGui::SameLine();
ImGui::SetCursorPosX(pos);
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspy_mix_gain_", _this->name), &_this->mixerGain, 0, 15)) {
if (_this->running) {
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
config.release(true);
}
}
if (_this->mixerAgc) { style::endDisabled(); }
ImGui::Text("VGA Gain");
ImGui::SameLine();
ImGui::SetCursorPosX(pos);
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspy_vga_gain_", _this->name), &_this->vgaGain, 0, 15)) {
if (_this->running) {
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
config.release(true);
}
}
// AGC Control
if (ImGui::Checkbox(CONCAT("LNA AGC##_airspy_", _this->name), &_this->lnaAgc)) {
if (_this->running) {
if (_this->lnaAgc) {
airspy_set_lna_agc(_this->openDev, 1);
}
else {
airspy_set_lna_agc(_this->openDev, 0);
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
config.release(true);
}
}
if (ImGui::Checkbox(CONCAT("Mixer AGC##_airspy_", _this->name), &_this->mixerAgc)) {
if (_this->running) {
if (_this->mixerAgc) {
airspy_set_mixer_agc(_this->openDev, 1);
}
else {
airspy_set_mixer_agc(_this->openDev, 0);
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
config.release(true);
}
}
}
// Bias T
if (ImGui::Checkbox(CONCAT("Bias T##_airspy_", _this->name), &_this->biasT)) {
if (_this->running) {
airspy_set_rf_bias(_this->openDev, _this->biasT);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
config.release(true);
}
}
}
static int callback(airspy_transfer_t* transfer) {
AirspySourceModule* _this = (AirspySourceModule*)transfer->ctx;
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
return 0;
}
std::string name;
airspy_device* openDev;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
uint64_t selectedSerial = 0;
std::string selectedSerStr = "";
int devId = 0;
int srId = 0;
bool biasT = false;
int lnaGain = 0;
int vgaGain = 0;
int mixerGain = 0;
int linearGain = 0;
int sensitiveGain = 0;
int gainMode = 0;
bool lnaAgc = false;
bool mixerAgc = false;
std::vector<uint64_t> devList;
std::string devListTxt;
std::vector<uint32_t> sampleRateList;
std::string sampleRateListTxt;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(options::opts.root + "/airspy_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new AirspySourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (AirspySourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.13)
project(airspyhf_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(airspyhf_source SHARED ${SRC})
target_link_libraries(airspyhf_source PRIVATE sdrpp_core)
set_target_properties(airspyhf_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(airspyhf_source PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(airspyhf_source PUBLIC airspyhf)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBAIRSPYHF REQUIRED libairspyhf)
target_include_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
target_link_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
target_link_libraries(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(airspyhf_source PUBLIC "/usr/local/include")
endif()
endif ()
# Install directives
install(TARGETS airspyhf_source DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,400 @@
#include <imgui.h>
#include <spdlog/spdlog.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <config.h>
#include <options.h>
#include <libairspyhf/airspyhf.h>
#include <gui/widgets/stepped_slider.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "airspyhf_source",
/* Description: */ "Airspy HF+ source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
const char* AGG_MODES_STR = "Off\0Low\0High\0";
class AirspyHFSourceModule : public ModuleManager::Instance {
public:
AirspyHFSourceModule(std::string name) {
this->name = name;
sampleRate = 768000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
sigpath::sourceManager.registerSource("Airspy HF+", &handler);
}
~AirspyHFSourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("Airspy HF+");
}
void postInit() {}
enum AGCMode {
AGC_MODE_OFF,
AGC_MODE_LOW,
AGC_MODE_HIGG
};
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
void refresh() {
devList.clear();
devListTxt = "";
uint64_t serials[256];
int n = airspyhf_list_devices(serials, 256);
char buf[1024];
for (int i = 0; i < n; i++) {
sprintf(buf, "%016" PRIX64, serials[i]);
devList.push_back(serials[i]);
devListTxt += buf;
devListTxt += '\0';
}
}
void selectFirst() {
if (devList.size() != 0) {
selectBySerial(devList[0]);
}
}
void selectByString(std::string serial) {
char buf[1024];
for (int i = 0; i < devList.size(); i++) {
sprintf(buf, "%016" PRIX64, devList[i]);
std::string str = buf;
if (serial == str) {
selectBySerial(devList[i]);
return;
}
}
selectFirst();
}
void selectBySerial(uint64_t serial) {
airspyhf_device_t* dev;
try {
int err = airspyhf_open_sn(&dev, selectedSerial);
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, selectedSerial);
spdlog::error("Could not open Airspy HF+ {0}", buf);
selectedSerial = 0;
return;
}
}
catch (std::exception e) {
char buf[1024];
sprintf(buf, "%016" PRIX64, selectedSerial);
spdlog::error("Could not open Airspy HF+ {0}", buf);
}
selectedSerial = serial;
uint32_t sampleRates[256];
airspyhf_get_samplerates(dev, sampleRates, 0);
int n = sampleRates[0];
airspyhf_get_samplerates(dev, sampleRates, n);
sampleRateList.clear();
sampleRateListTxt = "";
for (int i = 0; i < n; i++) {
sampleRateList.push_back(sampleRates[i]);
sampleRateListTxt += getBandwdithScaled(sampleRates[i]);
sampleRateListTxt += '\0';
}
char buf[1024];
sprintf(buf, "%016" PRIX64, serial);
selectedSerStr = std::string(buf);
// Load config here
config.acquire();
bool created = false;
if (!config.conf["devices"].contains(selectedSerStr)) {
created = true;
config.conf["devices"][selectedSerStr]["sampleRate"] = 768000;
config.conf["devices"][selectedSerStr]["agcMode"] = 0;
config.conf["devices"][selectedSerStr]["lna"] = false;
config.conf["devices"][selectedSerStr]["attenuation"] = 0;
}
// Load sample rate
srId = 0;
sampleRate = sampleRateList[0];
if (config.conf["devices"][selectedSerStr].contains("sampleRate")) {
int selectedSr = config.conf["devices"][selectedSerStr]["sampleRate"];
for (int i = 0; i < sampleRateList.size(); i++) {
if (sampleRateList[i] == selectedSr) {
srId = i;
sampleRate = selectedSr;
break;
}
}
}
// Load Gains
if (config.conf["devices"][selectedSerStr].contains("agcMode")) {
agcMode = config.conf["devices"][selectedSerStr]["agcMode"];
}
if (config.conf["devices"][selectedSerStr].contains("lna")) {
hfLNA = config.conf["devices"][selectedSerStr]["lna"];
}
if (config.conf["devices"][selectedSerStr].contains("attenuation")) {
atten = config.conf["devices"][selectedSerStr]["attenuation"];
}
config.release(created);
airspyhf_close(dev);
}
private:
std::string getBandwdithScaled(double bw) {
char buf[1024];
if (bw >= 1000000.0) {
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
}
else if (bw >= 1000.0) {
sprintf(buf, "%.1lfKHz", bw / 1000.0);
}
else {
sprintf(buf, "%.1lfHz", bw);
}
return std::string(buf);
}
static void menuSelected(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
spdlog::error("Tried to start AirspyHF+ source with null serial");
return;
}
int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial);
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, _this->selectedSerial);
spdlog::error("Could not open Airspy HF+ {0}", buf);
return;
}
airspyhf_set_samplerate(_this->openDev, _this->sampleRateList[_this->srId]);
airspyhf_set_freq(_this->openDev, _this->freq);
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
if (_this->agcMode > 0) {
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
}
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
airspyhf_start(_this->openDev, callback, _this);
_this->running = true;
spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
airspyhf_close(_this->openDev);
_this->stream.clearWriteStop();
spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (_this->running) {
airspyhf_set_freq(_this->openDev, freq);
}
_this->freq = freq;
spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (_this->running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
_this->selectBySerial(_this->devList[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
}
if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) {
_this->sampleRate = _this->sampleRateList[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
config.release(true);
}
}
ImGui::SameLine();
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
_this->refresh();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
core::setInputSampleRate(_this->sampleRate);
}
if (_this->running) { style::endDisabled(); }
ImGui::Text("AGC Mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_airspyhf_agc_", _this->name), &_this->agcMode, AGG_MODES_STR)) {
if (_this->running) {
airspyhf_set_hf_agc(_this->openDev, (_this->agcMode != 0));
if (_this->agcMode > 0) {
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
}
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["agcMode"] = _this->agcMode;
config.release(true);
}
}
ImGui::Text("HF LNA");
ImGui::SameLine();
if (ImGui::Checkbox(CONCAT("##_airspyhf_lna_", _this->name), &_this->hfLNA)) {
if (_this->running) {
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lna"] = _this->hfLNA;
config.release(true);
}
}
ImGui::Text("Attenuation");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloatWithSteps(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, 6, "%.0f dB")) {
if (_this->running) {
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
}
if (_this->selectedSerStr != "") {
config.acquire();
config.conf["devices"][_this->selectedSerStr]["attenuation"] = _this->atten;
config.release(true);
}
}
}
static int callback(airspyhf_transfer_t* transfer) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)transfer->ctx;
memcpy(_this->stream.writeBuf, transfer->samples, transfer->sample_count * sizeof(dsp::complex_t));
if (!_this->stream.swap(transfer->sample_count)) { return -1; }
return 0;
}
std::string name;
airspyhf_device_t* openDev;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
uint64_t selectedSerial = 0;
int devId = 0;
int srId = 0;
int agcMode = AGC_MODE_OFF;
bool hfLNA = false;
float atten = 0.0f;
std::string selectedSerStr = "";
std::vector<uint64_t> devList;
std::string devListTxt;
std::vector<uint32_t> sampleRateList;
std::string sampleRateListTxt;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(options::opts.root + "/airspyhf_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new AirspyHFSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (AirspyHFSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

40
audio_sink/CMakeLists.txt Normal file
View File

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.13)
project(audio_sink)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
file(GLOB SRC "src/*.cpp")
include_directories("src/")
add_library(audio_sink SHARED ${SRC})
target_link_libraries(audio_sink PRIVATE sdrpp_core)
set_target_properties(audio_sink PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(audio_sink PUBLIC "C:/Program Files (x86)/RtAudio/lib")
# Misc headers
target_include_directories(audio_sink PUBLIC "C:/Program Files (x86)/RtAudio/include/rtaudio")
target_link_libraries(audio_sink PUBLIC rtaudio)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
target_include_directories(audio_sink PUBLIC ${RTAUDIO_INCLUDE_DIRS})
target_link_directories(audio_sink PUBLIC ${RTAUDIO_LIBRARY_DIRS})
target_link_libraries(audio_sink PUBLIC ${RTAUDIO_LIBRARIES})
endif ()
# Install directives
install(TARGETS audio_sink DESTINATION lib/sdrpp/plugins)

299
audio_sink/src/main.cpp Normal file
View File

@ -0,0 +1,299 @@
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <dsp/audio.h>
#include <dsp/processing.h>
#include <spdlog/spdlog.h>
#include <RtAudio.h>
#include <config.h>
#include <options.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
class AudioSink : SinkManager::Sink {
public:
AudioSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
s2m.init(_stream->sinkOut);
monoPacker.init(&s2m.out, 512);
stereoPacker.init(_stream->sinkOut, 512);
bool created = false;
std::string device = "";
config.acquire();
if (!config.conf.contains(_streamName)) {
created = true;
config.conf[_streamName]["device"] = "";
config.conf[_streamName]["devices"] = json({});
}
device = config.conf[_streamName]["device"];
config.release(created);
int count = audio.getDeviceCount();
RtAudio::DeviceInfo info;
for (int i = 0; i < count; i++) {
info = audio.getDeviceInfo(i);
if (!info.probed) { continue; }
if (info.outputChannels == 0) { continue; }
if (info.isDefaultOutput) { defaultDevId = devList.size(); }
devList.push_back(info);
deviceIds.push_back(i);
txtDevList += info.name;
txtDevList += '\0';
}
selectByName(device);
}
~AudioSink() {
}
void start() {
if (running) {
return;
}
doStart();
running = true;
}
void stop() {
if (!running) {
return;
}
doStop();
running = false;
}
void selectFirst() {
selectById(defaultDevId);
}
void selectByName(std::string name) {
for (int i = 0; i < devList.size(); i++) {
if (devList[i].name == name) {
selectById(i);
return;
}
}
selectFirst();
}
void selectById(int id) {
devId = id;
bool created = false;
config.acquire();
if (!config.conf[_streamName]["devices"].contains(devList[id].name)) {
created = true;
config.conf[_streamName]["devices"][devList[id].name] = devList[id].preferredSampleRate;
}
sampleRate = config.conf[_streamName]["devices"][devList[id].name];
config.release(created);
sampleRates = devList[id].sampleRates;
sampleRatesTxt = "";
char buf[256];
bool found = false;
unsigned int defaultId = 0;
unsigned int defaultSr = devList[id].preferredSampleRate;
for (int i = 0; i < sampleRates.size(); i++) {
if (sampleRates[i] == sampleRate) {
found = true;
srId = i;
}
if (sampleRates[i] == defaultSr) {
defaultId = i;
}
sprintf(buf, "%d", sampleRates[i]);
sampleRatesTxt += buf;
sampleRatesTxt += '\0';
}
if (!found) {
sampleRate = defaultSr;
srId = defaultId;
}
_stream->setSampleRate(sampleRate);
if (running) { doStop(); }
if (running) { doStart(); }
}
void menuHandler() {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_dev_"+_streamName).c_str(), &devId, txtDevList.c_str())) {
selectById(devId);
config.acquire();
config.conf[_streamName]["device"] = devList[devId].name;
config.release(true);
}
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
if (running) {
doStop();
doStart();
}
config.acquire();
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
config.release(true);
}
}
private:
void doStart() {
RtAudio::StreamParameters parameters;
parameters.deviceId = deviceIds[devId];
parameters.nChannels = 2;
unsigned int bufferFrames = sampleRate / 60;
RtAudio::StreamOptions opts;
opts.flags = RTAUDIO_MINIMIZE_LATENCY;
opts.streamName = _streamName;
try {
audio.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
stereoPacker.setSampleCount(bufferFrames);
audio.startStream();
stereoPacker.start();
}
catch ( RtAudioError& e ) {
spdlog::error("Could not open audio device");
return;
}
spdlog::info("RtAudio stream open");
}
void doStop() {
s2m.stop();
monoPacker.stop();
stereoPacker.stop();
monoPacker.out.stopReader();
stereoPacker.out.stopReader();
audio.stopStream();
audio.closeStream();
monoPacker.out.clearReadStop();
stereoPacker.out.clearReadStop();
}
static int callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) {
AudioSink* _this = (AudioSink*)userData;
int count = _this->stereoPacker.out.read();
if (count < 0) { return 0; }
// For debug purposes only...
// if (nBufferFrames != count) { spdlog::warn("Buffer size missmatch, wanted {0}, was asked for {1}", count, nBufferFrames); }
// for (int i = 0; i < count; i++) {
// if (_this->stereoPacker.out.readBuf[i].l == NAN || _this->stereoPacker.out.readBuf[i].r == NAN) { spdlog::error("NAN in audio data"); }
// if (_this->stereoPacker.out.readBuf[i].l == INFINITY || _this->stereoPacker.out.readBuf[i].r == INFINITY) { spdlog::error("INFINITY in audio data"); }
// if (_this->stereoPacker.out.readBuf[i].l == -INFINITY || _this->stereoPacker.out.readBuf[i].r == -INFINITY) { spdlog::error("-INFINITY in audio data"); }
// }
memcpy(outputBuffer, _this->stereoPacker.out.readBuf, nBufferFrames * sizeof(dsp::stereo_t));
_this->stereoPacker.out.flush();
return 0;
}
SinkManager::Stream* _stream;
dsp::StereoToMono s2m;
dsp::Packer<float> monoPacker;
dsp::Packer<dsp::stereo_t> stereoPacker;
std::string _streamName;
int srId = 0;
int devCount;
int devId = 0;
bool running = false;
unsigned int defaultDevId = 0;
std::vector<RtAudio::DeviceInfo> devList;
std::vector<unsigned int> deviceIds;
std::string txtDevList;
std::vector<unsigned int> sampleRates;
std::string sampleRatesTxt;
unsigned int sampleRate = 48000;
RtAudio audio;
};
class AudioSinkModule : public ModuleManager::Instance {
public:
AudioSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Audio");
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
}
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
json def = json({});
config.setPath(options::opts.root + "/audio_sink_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
AudioSinkModule* instance = new AudioSinkModule(name);
return instance;
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (AudioSinkModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.13)
project(bladerf_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(bladerf_source SHARED ${SRC})
target_link_libraries(bladerf_source PRIVATE sdrpp_core)
set_target_properties(bladerf_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(bladerf_source PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(bladerf_source PUBLIC bladeRF)
else (MSVC)
# Not in pkg-config
target_link_libraries(bladerf_source PUBLIC bladeRF)
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(airspyhf_source PUBLIC "/usr/local/include")
endif()
endif ()
# Install directives
install(TARGETS bladerf_source DESTINATION lib/sdrpp/plugins)

579
bladerf_source/src/main.cpp Normal file
View File

@ -0,0 +1,579 @@
#include <imgui.h>
#include <spdlog/spdlog.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/style.h>
#include <config.h>
#include <options.h>
#include <gui/widgets/stepped_slider.h>
#include <libbladeRF.h>
#include <dsp/processing.h>
#include <algorithm>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
#define NUM_BUFFERS 128
#define NUM_TRANSFERS 1
SDRPP_MOD_INFO {
/* Name: */ "bladerf_source",
/* Description: */ "BladeRF source module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
ConfigManager config;
class BladeRFSourceModule : public ModuleManager::Instance {
public:
BladeRFSourceModule(std::string name) {
this->name = name;
sampleRate = 1000000.0;
handler.ctx = this;
handler.selectHandler = menuSelected;
handler.deselectHandler = menuDeselected;
handler.menuHandler = menuHandler;
handler.startHandler = start;
handler.stopHandler = stop;
handler.tuneHandler = tune;
handler.stream = &stream;
refresh();
// Select device here
config.acquire();
std::string serial = config.conf["device"];
config.release();
selectBySerial(serial);
sigpath::sourceManager.registerSource("BladeRF", &handler);
}
~BladeRFSourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("BladeRF");
}
void postInit() {}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
void refresh() {
devListTxt = "";
if (devInfoList != NULL) {
bladerf_free_device_list(devInfoList);
}
devCount = bladerf_get_device_list(&devInfoList);
if (devCount < 0) {
spdlog::error("Could not list devices");
return;
}
for (int i = 0; i < devCount; i++) {
// Keep only the first 32 character of the serial number for display
devListTxt += std::string(devInfoList[i].serial).substr(0, 16);
devListTxt += '\0';
}
}
void selectFirst() {
if (devCount > 0) { selectByInfo(&devInfoList[0]); }
else { selectedSerial = ""; }
}
void selectBySerial(std::string serial, bool reloadChannelId = true) {
if (serial == "") {
selectFirst();
return;
}
for (int i = 0; i < devCount; i++) {
bladerf_devinfo info = devInfoList[i];
if (serial == info.serial) {
devId = i;
selectByInfo(&info, reloadChannelId);
return;
}
}
selectFirst();
}
void selectByInfo(bladerf_devinfo* info, bool reloadChannelId = true) {
int ret = bladerf_open_with_devinfo(&openDev, info);
if (ret != 0) {
spdlog::error("Could not open device {0}", info->serial);
selectedSerial = "";
return;
}
selectedSerial = info->serial;
for (int i = 0; i < devCount; i++) {
if (selectedSerial == devInfoList[i].serial) { devId = i; }
}
// Gather info about the BladeRF's ranges
channelCount = bladerf_get_channel_count(openDev, BLADERF_RX);
// Load the channelId if there are more than 1 channel
if (reloadChannelId) {
config.acquire();
if (channelCount > 1 && config.conf["devices"].contains(info->serial)) {
if (config.conf["devices"][info->serial].contains("channelId")) {
chanId = config.conf["devices"][info->serial]["channelId"];
}
else { chanId = 0; }
}
else { chanId = 0; }
config.release();
}
chanId = std::clamp<int>(chanId, 0, channelCount - 1);
bladerf_get_sample_rate_range(openDev, BLADERF_CHANNEL_RX(chanId), &srRange);
bladerf_get_bandwidth_range(openDev, BLADERF_CHANNEL_RX(chanId), &bwRange);
bladerf_get_gain_range(openDev, BLADERF_CHANNEL_RX(chanId), &gainRange);
int gainModeCount = bladerf_get_gain_modes(openDev, BLADERF_CHANNEL_RX(chanId), &gainModes);
// Generate sampleRate and Bandwidth lists
sampleRates.clear();
sampleRatesTxt = "";
sampleRates.push_back(srRange->min);
sampleRatesTxt += getBandwdithScaled(srRange->min);
sampleRatesTxt += '\0';
for (int i = 2000000; i < srRange->max; i += 2000000) {
sampleRates.push_back(i);
sampleRatesTxt += getBandwdithScaled(i);
sampleRatesTxt += '\0';
}
sampleRates.push_back(srRange->max);
sampleRatesTxt += getBandwdithScaled(srRange->max);
sampleRatesTxt += '\0';
// Generate bandwidth list
bandwidths.clear();
bandwidthsTxt = "";
bandwidths.push_back(bwRange->min);
bandwidthsTxt += getBandwdithScaled(bwRange->min);
bandwidthsTxt += '\0';
for (int i = 2000000; i < bwRange->max; i += 2000000) {
bandwidths.push_back(i);
bandwidthsTxt += getBandwdithScaled(i);
bandwidthsTxt += '\0';
}
bandwidths.push_back(bwRange->max);
bandwidthsTxt += getBandwdithScaled(bwRange->max);
bandwidthsTxt += '\0';
bandwidthsTxt += "Auto";
bandwidthsTxt += '\0';
// Generate list of channel names
channelNamesTxt = "";
char buf[32];
for (int i = 0; i < channelCount; i++) {
sprintf(buf, "RX %d", i+1);
channelNamesTxt += buf;
channelNamesTxt += '\0';
}
// Generate gain mode list
gainModeNames.clear();
gainModesTxt = "";
for (int i = 0; i < gainModeCount; i++) {
std::string gm = gainModes[i].name;
gm[0] = gm[0] & (~0x20);
gainModeNames.push_back(gm);
gainModesTxt += gm;
gainModesTxt += '\0';
}
// Load settings here
config.acquire();
if (!config.conf["devices"].contains(selectedSerial)) {
config.conf["devices"][info->serial]["channelId"] = 0;
config.conf["devices"][selectedSerial]["sampleRate"] = sampleRates[0];
config.conf["devices"][selectedSerial]["bandwidth"] = bandwidths.size(); // Auto
config.conf["devices"][selectedSerial]["gainMode"] = "Manual";
config.conf["devices"][selectedSerial]["overallGain"] = gainRange->min;
}
// Load sample rate
if (config.conf["devices"][selectedSerial].contains("sampleRate")) {
bool found = false;
uint64_t sr = config.conf["devices"][selectedSerial]["sampleRate"];
for (int i = 0; i < sampleRates.size(); i++) {
if (sr == sampleRates[i]) {
srId = i;
sampleRate = sampleRates[i];
found = true;
break;
}
}
if (!found) {
srId = 0;
sampleRate = sampleRates[0];
}
}
else {
srId = 0;
sampleRate = sampleRates[0];
}
// Load bandwidth
if (config.conf["devices"][selectedSerial].contains("bandwidth")) {
bwId = config.conf["devices"][selectedSerial]["bandwidth"];
bwId = std::clamp<int>(bwId, 0, bandwidths.size());
}
else {
bwId = 0;
}
config.release(true);
// Load gain mode
if (config.conf["devices"][selectedSerial].contains("gainMode")) {
std::string gm = config.conf["devices"][selectedSerial]["gainMode"];
bool found = false;
for (int i = 0; i < gainModeNames.size(); i++) {
if (gainModeNames[i] == gm) {
gainMode = i;
found = true;
break;
}
}
if (!found) {
for (int i = 0; i < gainModeNames.size(); i++) {
if (gainModeNames[i] == "Manual") {
gainMode = i;
break;
}
}
}
}
else {
for (int i = 0; i < gainModeNames.size(); i++) {
if (gainModeNames[i] == "Manual") {
gainMode = i;
break;
}
}
}
// Load gain
if (config.conf["devices"][selectedSerial].contains("overallGain")) {
overallGain = config.conf["devices"][selectedSerial]["overallGain"];
overallGain = std::clamp<int>(overallGain, gainRange->min, gainRange->max);
}
else {
overallGain = gainRange->min;
}
bladerf_close(openDev);
}
private:
std::string getBandwdithScaled(double bw) {
char buf[1024];
if (bw >= 1000000.0) {
sprintf(buf, "%.1lfMHz", bw / 1000000.0);
}
else if (bw >= 1000.0) {
sprintf(buf, "%.1lfKHz", bw / 1000.0);
}
else {
sprintf(buf, "%.1lfHz", bw);
}
return std::string(buf);
}
static void menuSelected(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
core::setInputSampleRate(_this->sampleRate);
spdlog::info("BladeRFSourceModule '{0}': Menu Select!", _this->name);
}
static void menuDeselected(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
spdlog::info("BladeRFSourceModule '{0}': Menu Deselect!", _this->name);
}
static void start(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
if (_this->running) { return; }
if (_this->devCount == 0) { return; }
// Open device
bladerf_devinfo info = _this->devInfoList[_this->devId];
int ret = bladerf_open_with_devinfo(&_this->openDev, &info);
if (ret != 0) {
spdlog::error("Could not open device {0}", info.serial);
return;
}
// Calculate buffer size, must be a multiple of 1024
_this->bufferSize = _this->sampleRate / 200.0;
_this->bufferSize /= 1024;
_this->bufferSize *= 1024;
if (_this->bufferSize < 1024) { _this->bufferSize = 1024; }
// Setup device parameters
bladerf_set_sample_rate(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->sampleRate, NULL);
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ?
std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
bladerf_set_gain_mode(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->gainModes[_this->gainMode].mode);
// If gain mode is manual, set the gain
if (_this->gainModes[_this->gainMode].mode == BLADERF_GAIN_MANUAL) {
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
}
_this->streamingEnabled = true;
// Setup syncronous transfer
bladerf_sync_config(_this->openDev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 16, _this->bufferSize, 8, 3500);
// Enable streaming
bladerf_enable_module(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), true);
_this->running = true;
_this->workerThread = std::thread(&BladeRFSourceModule::worker, _this);
spdlog::info("BladeRFSourceModule '{0}': Start!", _this->name);
}
static void stop(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
_this->streamingEnabled = false;
// Wait for read worker to terminate
if (_this->workerThread.joinable()) {
_this->workerThread.join();
}
// Disable streaming
bladerf_enable_module(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), false);
// Close device
bladerf_close(_this->openDev);
_this->stream.clearWriteStop();
spdlog::info("BladeRFSourceModule '{0}': Stop!", _this->name);
}
static void tune(double freq, void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
_this->freq = freq;
if (_this->running) {
bladerf_set_frequency(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->freq);
}
spdlog::info("BladeRFSourceModule '{0}': Tune: {1}!", _this->name, freq);
}
static void menuHandler(void* ctx) {
BladeRFSourceModule* _this = (BladeRFSourceModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (_this->running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(CONCAT("##_balderf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) {
bladerf_devinfo info = _this->devInfoList[_this->devId];
_this->selectByInfo(&info);
core::setInputSampleRate(_this->sampleRate);
config.acquire();
config.conf["device"] = _this->selectedSerial;
config.release(true);
}
if (ImGui::Combo(CONCAT("##_balderf_sr_sel_", _this->name), &_this->srId, _this->sampleRatesTxt.c_str())) {
_this->sampleRate = _this->sampleRates[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["sampleRate"] = _this->sampleRates[_this->srId];
config.release(true);
}
}
// Refresh button
ImGui::SameLine();
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
if (ImGui::Button(CONCAT("Refresh##_balderf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
_this->refresh();
_this->selectBySerial(_this->selectedSerial, false);
core::setInputSampleRate(_this->sampleRate);
}
// Channel selection (only show if more than one channel)
if (_this->channelCount > 1) {
ImGui::Text("RX Channel");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::Combo(CONCAT("##_balderf_ch_sel_", _this->name), &_this->chanId, _this->channelNamesTxt.c_str());
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["channelId"] = _this->chanId;
config.release(true);
}
}
if (_this->running) { style::endDisabled(); }
ImGui::Text("Bandwidth");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_balderf_bw_sel_", _this->name), &_this->bwId, _this->bandwidthsTxt.c_str())) {
if (_this->running) {
bladerf_set_bandwidth(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), (_this->bwId == _this->bandwidths.size()) ?
std::clamp<uint64_t>(_this->sampleRate, _this->bwRange->min, _this->bwRange->max) : _this->bandwidths[_this->bwId], NULL);
}
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["bandwidth"] = _this->bwId;
config.release(true);
}
}
// General config BS
ImGui::Text("Gain control mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo(CONCAT("##_balderf_gm_sel_", _this->name), &_this->gainMode, _this->gainModesTxt.c_str()) && _this->selectedSerial != "") {
if (_this->running) {
bladerf_set_gain_mode(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->gainModes[_this->gainMode].mode);
}
// if switched to manual, reset gains
if (_this->gainModes[_this->gainMode].mode == BLADERF_GAIN_MANUAL && _this->running) {
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
}
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["gainMode"] = _this->gainModeNames[_this->gainMode];
config.release(true);
}
}
if (_this->selectedSerial != "") { if (_this->gainModes[_this->gainMode].mode != BLADERF_GAIN_MANUAL) { style::beginDisabled(); } }
ImGui::Text("Gain");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt("##_balderf_oag_sel_", &_this->overallGain, (_this->gainRange != NULL) ? _this->gainRange->min : 0, (_this->gainRange != NULL) ? _this->gainRange->max : 60)) {
if (_this->running) {
spdlog::info("Setting gain to {0}", _this->overallGain);
bladerf_set_gain(_this->openDev, BLADERF_CHANNEL_RX(_this->chanId), _this->overallGain);
}
if (_this->selectedSerial != "") {
config.acquire();
config.conf["devices"][_this->selectedSerial]["overallGain"] = _this->overallGain;
config.release(true);
}
}
if (_this->selectedSerial != "") { if (_this->gainModes[_this->gainMode].mode != BLADERF_GAIN_MANUAL) { style::endDisabled(); } }
}
void worker() {
int16_t* buffer = new int16_t[bufferSize * 2];
bladerf_metadata meta;
while (streamingEnabled) {
// Receive from the stream and break on error
int ret = bladerf_sync_rx(openDev, buffer, bufferSize, &meta, 3500);
if (ret != 0) { break; }
// Convert to complex float and swap buffers
volk_16i_s32f_convert_32f((float*)stream.writeBuf, buffer, 32768.0f, bufferSize * 2);
if (!stream.swap(bufferSize)) { break; }
}
delete[] buffer;
}
std::string name;
bladerf* openDev;
bool enabled = true;
dsp::stream<dsp::complex_t> stream;
double sampleRate;
SourceManager::SourceHandler handler;
bool running = false;
double freq;
int devId = 0;
int srId = 0;
int bwId = 0;
int chanId = 0;
int gainMode = 0;
bool streamingEnabled = false;
int channelCount;
const bladerf_range* srRange = NULL;
const bladerf_range* bwRange = NULL;
const bladerf_range* gainRange = NULL;
std::vector<uint64_t> sampleRates;
std::string sampleRatesTxt;
std::vector<uint64_t> bandwidths;
std::string bandwidthsTxt;
std::string channelNamesTxt;
int bufferSize;
struct bladerf_stream* rxStream;
int overallGain = 0;
std::thread workerThread;
int devCount = 0;
bladerf_devinfo* devInfoList = NULL;
std::string devListTxt;
std::string selectedSerial;
bool isBlade1 = false;
const bladerf_gain_modes* gainModes;
std::vector<std::string> gainModeNames;
std::string gainModesTxt;
int gainModeCount;
};
MOD_EXPORT void _INIT_() {
json def = json({});
def["devices"] = json({});
def["device"] = "";
config.setPath(options::opts.root + "/bladerf_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new BladeRFSourceModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) {
delete (BladeRFSourceModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

32
cmake_uninstall.cmake Normal file
View File

@ -0,0 +1,32 @@
# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F
IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
STRING(REGEX REPLACE "\n" ";" files "${files}")
FOREACH(file ${files})
MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
IF(EXISTS "$ENV{DESTDIR}${file}")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ELSEIF(IS_SYMLINK "$ENV{DESTDIR}${file}")
EXEC_PROGRAM(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
IF(NOT "${rm_retval}" STREQUAL 0)
MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
ENDIF(NOT "${rm_retval}" STREQUAL 0)
ELSE(EXISTS "$ENV{DESTDIR}${file}")
MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
ENDIF(EXISTS "$ENV{DESTDIR}${file}")
ENDFOREACH(file)

125
contributing.md Normal file
View File

@ -0,0 +1,125 @@
# Pull Requests
TODO
# Code Style
## Naming Convention
- Files: `snake_case.h` `snake_case.cpp`
- Namespaces: `CamelCase`
- Classes: `CamelCase`
- Structs: `CamelCase_t`
- Members: `camelCase`
- Enum: `SNAKE_CASE`
- Macros: `SNAKE_CASE`
## Brace Style
```c++
int myFunction() {
if (shortIf) { shortFunctionName(); }
if (longIf) {
longFunction();
otherStuff();
myLongFunction();
}
}
```
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
## Pointers
Please use `type* name` for pointers.
## Structure
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
# Modules
## Module Naming Convention
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
For example, lets take the module named `cool_source`:
- Directory: `cool_source`
- Class: `CoolSourceModule`
- Binary: `cool_source.<os dynlib extension>`
## Integration into main repository
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
# JSON Formatting
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSOn files is important for reference and readability. The folowing guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
## Band Frequency Allocation
Please follow this guide to properly format the JSON files for custom radio band allocation identifiers.
```json
{
"name": "Short name (has to fit in the menu)",
"country_name": "Name of country or area, if applicable (Use '--' otherwise)",
"country_code": "Two letter country code, if applicable (Use '--' otherwise)",
"author_name": "Name of the original/main creator of the JSON file",
"author_url": "URL the author wishes to be associated with the file (personal website, GitHub, Twitter, etc)",
"bands": [
// Bands in this array must be sorted by their starting frequency
{
"name": "Name of the band",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
"start": 148500, //In Hz, must be an integer
"end": 283500 //In Hz, must be an integer
},
{
"name": "Name of the band",
"type": "Type name ('amateur', 'broadcast', 'marine', 'military', or any type decalre in config.json)",
"start": 526500, //In Hz, must be an integer
"end": 1606500 //In Hz, must be an integer
}
]
}
```
## Color Maps
Please follow this guide to properly format the JSON files for custom color maps.
```json
{
"name": "Short name (has to fit in the menu)",
"author": "Name of the original/main creator of the color map",
"map": [
// These are the color codes, in hexidecimal (#RRGGBB) format, for the custom color scales for the waterfall. They must be entered as strings, not integers, with the hastag/pound-symbol proceeding the 6 digit number.
"#000020",
"#000030",
"#000050",
"#000091",
"#1E90FF",
"#FFFFFF",
"#FFFF00",
"#FE6D16",
"#FE6D16",
"#FF0000",
"#FF0000",
"#C60000",
"#9F0000",
"#750000",
"#4A0000"
]
}
```
# Best Practices
* All additions and/or bug fixes to the core must not add additional dependencies.
* Use VSCode for development, VS seems to cause issues.
* DO NOT use libboost for any code meant for this repository

101
core/CMakeLists.txt Normal file
View File

@ -0,0 +1,101 @@
cmake_minimum_required(VERSION 3.13)
project(sdrpp_core)
# Set compiler options
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup")
else ()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif ()
add_definitions(-DSDRPP_IS_CORE)
# Main code
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
# Add code to dyn lib
add_library(sdrpp_core SHARED ${SRC})
# Set the install prefix
target_compile_definitions(sdrpp_core PUBLIC INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
# Include core headers
target_include_directories(sdrpp_core PUBLIC "src/")
target_include_directories(sdrpp_core PUBLIC "src/imgui")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/lib/")
# Misc headers
target_include_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/include/")
# Volk
target_link_libraries(sdrpp_core PUBLIC volk)
# Glew
find_package(GLEW REQUIRED)
target_link_libraries(sdrpp_core PUBLIC GLEW::GLEW)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC glfw)
# FFTW3
find_package(FFTW3f CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC FFTW3::fftw3f)
# WinSock2
target_link_libraries(sdrpp_core PUBLIC wsock32 ws2_32)
else()
find_package(PkgConfig)
find_package(OpenGL REQUIRED)
pkg_check_modules(GLEW REQUIRED glew)
pkg_check_modules(FFTW3 REQUIRED fftw3f)
pkg_check_modules(VOLK REQUIRED volk)
pkg_check_modules(GLFW3 REQUIRED glfw3)
target_include_directories(sdrpp_core PUBLIC
${GLEW_INCLUDE_DIRS}
${FFTW3_INCLUDE_DIRS}
${GLFW3_INCLUDE_DIRS}
${VOLK_INCLUDE_DIRS}
)
target_link_directories(sdrpp_core PUBLIC
${GLEW_LIBRARY_DIRS}
${FFTW3_LIBRARY_DIRS}
${GLFW3_LIBRARY_DIRS}
${VOLK_LIBRARY_DIRS}
)
target_link_libraries(sdrpp_core PUBLIC
${OPENGL_LIBRARIES}
${GLEW_LIBRARIES}
${FFTW3_LIBRARIES}
${GLFW3_LIBRARIES}
${VOLK_LIBRARIES}
)
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries(sdrpp_core PUBLIC stdc++fs)
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++")
endif ()
endif ()
set(CORE_FILES ${RUNTIME_OUTPUT_DIRECTORY} PARENT_SCOPE)
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"
# Install directives
install(TARGETS sdrpp_core DESTINATION lib)

95
core/src/config.cpp Normal file
View File

@ -0,0 +1,95 @@
#include <config.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <filesystem>
ConfigManager::ConfigManager() {
}
ConfigManager::~ConfigManager() {
disableAutoSave();
}
void ConfigManager::setPath(std::string file) {
path = file;
}
void ConfigManager::load(json def, bool lock) {
if (lock) { mtx.lock(); }
if (path == "") {
spdlog::error("Config manager tried to load file with no path specified");
return;
}
if (!std::filesystem::exists(path)) {
spdlog::warn("Config file '{0}' does not exist, creating it", path);
conf = def;
save(false);
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Config file '{0}' isn't a file", path);
return;
}
std::ifstream file(path.c_str());
file >> conf;
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::save(bool lock) {
if (lock) { mtx.lock(); }
std::ofstream file(path.c_str());
file << conf.dump(4);
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::enableAutoSave() {
if (autoSaveEnabled) { return; }
autoSaveEnabled = true;
termFlag = false;
autoSaveThread = std::thread(&ConfigManager::autoSaveWorker, this);
}
void ConfigManager::disableAutoSave() {
if (!autoSaveEnabled) { return; }
{
std::lock_guard<std::mutex> lock(termMtx);
autoSaveEnabled = false;
termFlag = true;
}
termCond.notify_one();
if (autoSaveThread.joinable()) { autoSaveThread.join(); }
}
void ConfigManager::acquire() {
mtx.lock();
}
void ConfigManager::release(bool modified) {
changed |= modified;
mtx.unlock();
}
void ConfigManager::autoSaveWorker() {
while (autoSaveEnabled) {
if (!mtx.try_lock()) {
spdlog::warn("ConfigManager locked, waiting...");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
continue;
}
if (changed) {
changed = false;
save(false);
}
mtx.unlock();
// Sleep but listen for wakeup call
{
std::unique_lock<std::mutex> lock(termMtx);
termCond.wait_for(lock, std::chrono::milliseconds(1000), [this]() { return termFlag; } );
}
}
}

37
core/src/config.h Normal file
View File

@ -0,0 +1,37 @@
#pragma once
#include <json.hpp>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
using nlohmann::json;
class ConfigManager {
public:
ConfigManager();
~ConfigManager();
void setPath(std::string file);
void load(json def, bool lock = true);
void save(bool lock = true);
void enableAutoSave();
void disableAutoSave();
void acquire();
void release(bool modified = false);
json conf;
private:
void autoSaveWorker();
std::string path = "";
volatile bool changed = false;
volatile bool autoSaveEnabled = false;
std::thread autoSaveThread;
std::mutex mtx;
std::mutex termMtx;
std::condition_variable termCond;
volatile bool termFlag = false;
};

477
core/src/core.cpp Normal file
View File

@ -0,0 +1,477 @@
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <gui/icons.h>
#include <version.h>
#include <spdlog/spdlog.h>
#include <gui/widgets/bandplan.h>
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <glfw_window.h>
#include <options.h>
#include <filesystem>
#include <gui/menus/theme.h>
#include <server.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#ifdef _WIN32
#include <Windows.h>
#endif
#ifndef INSTALL_PREFIX
#ifdef __APPLE__
#define INSTALL_PREFIX "/usr/local"
#else
#define INSTALL_PREFIX "/usr"
#endif
#endif
namespace core {
ConfigManager configManager;
ModuleManager moduleManager;
ModuleComManager modComManager;
GLFWwindow* window;
void setInputSampleRate(double samplerate) {
sigpath::signalPath.sourceSampleRate = samplerate;
double effectiveSr = samplerate / ((double)(1 << sigpath::signalPath.decimation));
// NOTE: Zoom controls won't work
spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
gui::waterfall.setBandwidth(effectiveSr);
gui::waterfall.setViewOffset(0);
gui::waterfall.setViewBandwidth(effectiveSr);
sigpath::signalPath.setSampleRate(effectiveSr);
gui::mainWindow.setViewBandwidthSlider(effectiveSr);
}
};
bool maximized = false;
bool fullScreen = false;
static void glfw_error_callback(int error, const char* description) {
spdlog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
// main
int sdrpp_main(int argc, char *argv[]) {
spdlog::info("SDR++ v" VERSION_STR);
// Load default options and parse command line
options::loadDefaults();
if (!options::parse(argc, argv)) { return -1; }
#ifdef _WIN32
if (!options::opts.showConsole) { FreeConsole(); }
#endif
// Check root directory
if (!std::filesystem::exists(options::opts.root)) {
spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root);
if (!std::filesystem::create_directory(options::opts.root)) {
spdlog::error("Could not create root directory {0}", options::opts.root);
return -1;
}
}
if (!std::filesystem::is_directory(options::opts.root)) {
spdlog::error("{0} is not a directory", options::opts.root);
return -1;
}
// ======== DEFAULT CONFIG ========
json defConfig;
defConfig["bandColors"]["amateur"] = "#FF0000FF";
defConfig["bandColors"]["aviation"] = "#00FF00FF";
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
defConfig["bandColors"]["marine"] = "#00FFFFFF";
defConfig["bandColors"]["military"] = "#FFFF00FF";
defConfig["bandPlan"] = "General";
defConfig["bandPlanEnabled"] = true;
defConfig["bandPlanPos"] = 0;
defConfig["centerTuning"] = false;
defConfig["colorMap"] = "Classic";
defConfig["fastFFT"] = false;
defConfig["fftHeight"] = 300;
defConfig["fftRate"] = 20;
defConfig["fftSize"] = 65536;
defConfig["fftWindow"] = 1;
defConfig["frequency"] = 100000000.0;
defConfig["fullWaterfallUpdate"] = false;
defConfig["max"] = 0.0;
defConfig["maximized"] = false;
// Menu
defConfig["menuElements"] = json::array();
defConfig["menuElements"][0]["name"] = "Source";
defConfig["menuElements"][0]["open"] = true;
defConfig["menuElements"][1]["name"] = "Radio";
defConfig["menuElements"][1]["open"] = true;
defConfig["menuElements"][2]["name"] = "Recorder";
defConfig["menuElements"][2]["open"] = true;
defConfig["menuElements"][3]["name"] = "Sinks";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][3]["name"] = "Frequency Manager";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][4]["name"] = "VFO Color";
defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][5]["name"] = "Scripting";
defConfig["menuElements"][5]["open"] = false;
defConfig["menuElements"][6]["name"] = "Band Plan";
defConfig["menuElements"][6]["open"] = true;
defConfig["menuElements"][7]["name"] = "Display";
defConfig["menuElements"][7]["open"] = true;
defConfig["menuWidth"] = 300;
defConfig["min"] = -120.0;
// Module instances
defConfig["moduleInstances"]["Airspy Source"]["module"] = "airspy_source";
defConfig["moduleInstances"]["Airspy Source"]["enabled"] = true;
defConfig["moduleInstances"]["AirspyHF+ Source"]["module"] = "airspyhf_source";
defConfig["moduleInstances"]["AirspyHF+ Source"]["enabled"] = true;
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true;
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
defConfig["moduleInstances"]["RTL-SDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-TCP Source"]["module"] = "rtl_tcp_source";
defConfig["moduleInstances"]["RTL-TCP Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDRplay Source"]["module"] = "sdrplay_source";
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["Radio"] = "radio";
defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager";
defConfig["moduleInstances"]["Recorder"] = "recorder";
defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server";
// Themes
defConfig["theme"] = "Dark";
defConfig["modules"] = json::array();
defConfig["offsetMode"] = (int)0; // Off
defConfig["offset"] = 0.0;
defConfig["showMenu"] = true;
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["decimationPower"] = 0;
defConfig["iqCorrection"] = false;
defConfig["streams"]["Radio"]["muted"] = false;
defConfig["streams"]["Radio"]["sink"] = "Audio";
defConfig["streams"]["Radio"]["volume"] = 1.0f;
defConfig["windowSize"]["h"] = 720;
defConfig["windowSize"]["w"] = 1280;
defConfig["vfoOffsets"] = json::object();
defConfig["vfoColors"]["Radio"] = "#FFFFFF";
#ifdef _WIN32
defConfig["modulesDirectory"] = "./modules";
defConfig["resourcesDirectory"] = "./res";
#else
defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins";
defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp";
#endif
// Load config
spdlog::info("Loading config");
core::configManager.setPath(options::opts.root + "/config.json");
core::configManager.load(defConfig);
core::configManager.enableAutoSave();
core::configManager.acquire();
// Fix missing elements in config
for (auto const& item : defConfig.items()) {
if (!core::configManager.conf.contains(item.key())) {
spdlog::info("Missing key in config {0}, repairing", item.key());
core::configManager.conf[item.key()] = defConfig[item.key()];
}
}
// Remove unused elements
auto items = core::configManager.conf.items();
for (auto const& item : items) {
if (!defConfig.contains(item.key())) {
spdlog::info("Unused key in config {0}, repairing", item.key());
core::configManager.conf.erase(item.key());
}
}
// Update to new module representation in config if needed
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {
if (!inst.is_string()) { continue; }
std::string mod = inst;
json newMod;
newMod["module"] = mod;
newMod["enabled"] = true;
core::configManager.conf["moduleInstances"][_name] = newMod;
}
core::configManager.release(true);
if (options::opts.serverMode) { return server_main(); }
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
return 1;
}
#ifdef __APPLE__
// GL 3.2 + GLSL 150
const char* glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
// GL 3.0 + GLSL 120
const char* glsl_version = "#version 120";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
core::configManager.acquire();
int winWidth = core::configManager.conf["windowSize"]["w"];
int winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
std::string resDir = core::configManager.conf["resourcesDirectory"];
json bandColors = core::configManager.conf["bandColors"];
core::configManager.release();
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)");
return 1;
}
// Create window with graphics context
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (core::window == NULL)
return 1;
glfwMakeContextCurrent(core::window);
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(core::window);
}
glfwSetWindowMaximizeCallback(core::window, maximized_callback);
#endif
// Load app icon
if (!std::filesystem::is_regular_file(resDir + "/icons/sdrpp.png")) {
spdlog::error("Icon file '{0}' doesn't exist!", resDir + "/icons/sdrpp.png");
return 1;
}
GLFWimage icons[10];
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(core::window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
}
if (glewInit() != GLEW_OK) {
spdlog::error("Failed to initialize OpenGL loader!");
return 1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(core::window, true);
if (!ImGui_ImplOpenGL3_Init(glsl_version)) {
// If init fail, try to fall back on GLSL 1.2
spdlog::warn("Could not init using OpenGL with normal GLSL version, falling back to GLSL 1.2");
if (!ImGui_ImplOpenGL3_Init("#version 120")) {
spdlog::error("Failed to initialize OpenGL with GLSL 1.2");
return -1;
}
}
if (!style::loadFonts(resDir)) { return -1; }
thememenu::init(resDir);
LoadingScreen::setWindow(core::window);
LoadingScreen::show("Loading icons");
spdlog::info("Loading icons");
if (!icons::load(resDir)) { return -1; }
LoadingScreen::show("Loading band plans");
spdlog::info("Loading band plans");
bandplan::loadFromDir(resDir + "/bandplans");
LoadingScreen::show("Loading band plan colors");
spdlog::info("Loading band plans color table");
bandplan::loadColorTable(bandColors);
gui::mainWindow.init();
spdlog::info("Ready.");
bool _maximized = maximized;
int fsWidth, fsHeight, fsPosX, fsPosY;
// Main loop
while (!glfwWindowShouldClose(core::window)) {
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow();
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.acquire();
core::configManager.conf["maximized"]= _maximized;
if (!maximized) {
glfwSetWindowSize(core::window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
int _winWidth, _winHeight;
glfwGetWindowSize(core::window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
if (fullScreen) {
spdlog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(core::window, &fsPosX, &fsPosY);
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(core::window, monitor, 0, 0, mode->width, mode->height, 0);
}
else {
spdlog::info("Fullscreen: OFF");
glfwSetWindowMonitor(core::window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
}
}
if ((_winWidth != winWidth || _winHeight != winHeight) && !maximized && _winWidth > 0 && _winHeight > 0) {
winWidth = _winWidth;
winHeight = _winHeight;
core::configManager.acquire();
core::configManager.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
gui::mainWindow.draw();
}
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(core::window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
//glClearColor(0.0666f, 0.0666f, 0.0666f, 1.0f);
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapInterval(1); // Enable vsync
glfwSwapBuffers(core::window);
}
// Shut down all modules
for (auto& [name, mod] : core::moduleManager.modules) {
mod.end();
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(core::window);
glfwTerminate();
sigpath::signalPath.stop();
core::configManager.disableAutoSave();
core::configManager.save();
return 0;
}

15
core/src/core.h Normal file
View File

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

51
core/src/credits.cpp Normal file
View File

@ -0,0 +1,51 @@
#include <credits.h>
namespace sdrpp_credits {
const char* contributors[] = {
"Aang23",
"Alexsey Shestacov",
"Aosync",
"Benjamin Kyd",
"Benjamin Vernoux",
"Cropinghigh",
"Fred F4EED",
"Howard0su",
"Joshua Kimsey",
"Martin Hauke",
"Marvin Sinister",
"Maxime Biette",
"Paulo Matias",
"Raov",
"Cam K.",
"Szymon Zakrent",
"Tobias Mädel",
"Zimm"
};
const char* libraries[] = {
"Dear ImGui (ocornut)",
"fftw3 (fftw.org)",
"glew (Nigel Stewart)",
"glfw (Camilla Löwy)",
"json (nlohmann)",
"spdlog (gabime)",
"Portable File Dialogs"
};
const char* patrons[] = {
"Croccydile",
"Daniele D'Agnelli",
"Eric Johnson",
"W4IPA",
"Lee Donaghy",
"ON4MU",
"Passion-Radio.com",
"Scanner School",
"SignalsEverywhere",
"Syne Ardwin (WI9SYN)"
};
const int contributorCount = sizeof(contributors) / sizeof(char*);
const int libraryCount = sizeof(libraries) / sizeof(char*);
const int patronCount = sizeof(patrons) / sizeof(char*);
}

11
core/src/credits.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <module.h>
namespace sdrpp_credits {
SDRPP_EXPORT const char* contributors[];
SDRPP_EXPORT const char* libraries[];
SDRPP_EXPORT const char* patrons[];
SDRPP_EXPORT const int contributorCount;
SDRPP_EXPORT const int libraryCount;
SDRPP_EXPORT const int patronCount;
}

202
core/src/dsp/audio.h Normal file
View File

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

229
core/src/dsp/block.h Normal file
View File

@ -0,0 +1,229 @@
#pragma once
#include <stdio.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <thread>
#include <vector>
#include <algorithm>
#include <spdlog/spdlog.h>
namespace dsp {
class generic_unnamed_block {
public:
virtual void start() {}
virtual void stop() {}
virtual int calcOutSize(int inSize) { return inSize; }
virtual int run() { return -1; }
};
template <class BLOCK>
class generic_block : public generic_unnamed_block {
public:
virtual void init() {}
virtual ~generic_block() {
if (!_block_init) { return; }
stop();
_block_init = false;
}
virtual void start() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (running) {
return;
}
running = true;
doStart();
}
virtual void stop() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (!running) {
return;
}
doStop();
running = false;
}
void tempStart() {
assert(_block_init);
if (tempStopped) {
doStart();
tempStopped = false;
}
}
void tempStop() {
assert(_block_init);
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
virtual int calcOutSize(int inSize) {
assert(_block_init);
return inSize;
}
virtual int run() = 0;
friend BLOCK;
private:
void workerLoop() {
while (run() >= 0);
}
void acquire() {
ctrlMtx.lock();
}
void release() {
ctrlMtx.unlock();
}
void registerInput(untyped_stream* inStream) {
inputs.push_back(inStream);
}
void unregisterInput(untyped_stream* inStream) {
inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end());
}
void registerOutput(untyped_stream* outStream) {
outputs.push_back(outStream);
}
void unregisterOutput(untyped_stream* outStream) {
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
}
virtual void doStart() {
workerThread = std::thread(&generic_block<BLOCK>::workerLoop, this);
}
virtual void doStop() {
for (auto& in : inputs) {
in->stopReader();
}
for (auto& out : outputs) {
out->stopWriter();
}
// TODO: Make sure this isn't needed, I don't know why it stops
if (workerThread.joinable()) {
workerThread.join();
}
for (auto& in : inputs) {
in->clearReadStop();
}
for (auto& out : outputs) {
out->clearWriteStop();
}
}
protected:
bool _block_init = false;
std::mutex ctrlMtx;
std::vector<untyped_stream*> inputs;
std::vector<untyped_stream*> outputs;
bool running = false;
bool tempStopped = false;
std::thread workerThread;
};
template <class BLOCK>
class generic_hier_block {
public:
virtual void init() {}
virtual ~generic_hier_block() {
if (!_block_init) { return; }
stop();
_block_init = false;
}
virtual void start() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (running) {
return;
}
running = true;
doStart();
}
virtual void stop() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (!running) {
return;
}
doStop();
running = false;
}
void tempStart() {
assert(_block_init);
if (tempStopped) {
doStart();
tempStopped = false;
}
}
void tempStop() {
assert(_block_init);
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
virtual int calcOutSize(int inSize) {
assert(_block_init);
return inSize;
}
friend BLOCK;
private:
void registerBlock(generic_unnamed_block* block) {
blocks.push_back(block);
}
void unregisterBlock(generic_unnamed_block* block) {
blocks.erase(std::remove(blocks.begin(), blocks.end(), block), blocks.end());
}
virtual void doStart() {
for (auto& block : blocks) {
block->start();
}
}
virtual void doStop() {
for (auto& block : blocks) {
block->stop();
}
}
std::vector<generic_unnamed_block*> blocks;
bool tempStopped = false;
bool running = false;
protected:
bool _block_init = false;
std::mutex ctrlMtx;
};
}

View File

@ -1,33 +1,25 @@
#pragma once
#include <condition_variable>
#include <algorithm>
#include <math.h>
#include <dsp/block.h>
#include <string.h>
#define STREAM_BUF_SZ 1000000
#define RING_BUF_SZ 1000000
namespace dsp {
template <class T>
class stream {
class RingBuffer {
public:
stream() {
RingBuffer() {}
}
RingBuffer(int maxLatency) { init(maxLatency); }
stream(int maxLatency) {
size = STREAM_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
writec = 0;
readc = 0;
readable = 0;
writable = size;
memset(_buffer, 0, size * sizeof(T));
~RingBuffer() {
if (!_init) { return; }
delete _buffer;
_init = false;
}
void init(int maxLatency) {
size = STREAM_BUF_SZ;
size = RING_BUF_SZ;
_buffer = new T[size];
_stopReader = false;
_stopWriter = false;
@ -37,9 +29,11 @@ namespace dsp {
readable = 0;
writable = size;
memset(_buffer, 0, size * sizeof(T));
_init = true;
}
int read(T* data, int len) {
assert(_init);
int dataRead = 0;
int toRead = 0;
while (dataRead < len) {
@ -69,6 +63,7 @@ namespace dsp {
}
int readAndSkip(T* data, int len, int skip) {
assert(_init);
int dataRead = 0;
int toRead = 0;
while (dataRead < len) {
@ -114,6 +109,7 @@ namespace dsp {
}
int waitUntilReadable() {
assert(_init);
if (_stopReader) { return -1; }
int _r = getReadable();
if (_r != 0) { return _r; }
@ -124,6 +120,7 @@ namespace dsp {
}
int getReadable(bool lock = true) {
assert(_init);
if (lock) { _readable_mtx.lock(); };
int _r = readable;
if (lock) { _readable_mtx.unlock(); };
@ -131,6 +128,7 @@ namespace dsp {
}
int write(T* data, int len) {
assert(_init);
int dataWritten = 0;
int toWrite = 0;
while (dataWritten < len) {
@ -161,6 +159,7 @@ namespace dsp {
}
int waitUntilwritable() {
assert(_init);
if (_stopWriter) { return -1; }
int _w = getWritable();
if (_w != 0) { return _w; }
@ -171,6 +170,7 @@ namespace dsp {
}
int getWritable(bool lock = true) {
assert(_init);
if (lock) { _writable_mtx.lock(); };
int _w = writable;
if (lock) { _writable_mtx.unlock(); _readable_mtx.lock(); };
@ -180,36 +180,44 @@ namespace dsp {
}
void stopReader() {
assert(_init);
_stopReader = true;
canReadVar.notify_one();
}
void stopWriter() {
assert(_init);
_stopWriter = true;
canWriteVar.notify_one();
}
bool getReadStop() {
assert(_init);
return _stopReader;
}
bool getWriteStop() {
assert(_init);
return _stopWriter;
}
void clearReadStop() {
assert(_init);
_stopReader = false;
}
void clearWriteStop() {
assert(_init);
_stopWriter = false;
}
void setMaxLatency(int maxLatency) {
assert(_init);
this->maxLatency = maxLatency;
}
private:
bool _init = false;
T* _buffer;
int size;
int readc;
@ -224,4 +232,128 @@ namespace dsp {
std::condition_variable canReadVar;
std::condition_variable canWriteVar;
};
};
#define TEST_BUFFER_SIZE 32
template <class T>
class SampleFrameBuffer : public generic_block<SampleFrameBuffer<T>> {
public:
SampleFrameBuffer() {}
SampleFrameBuffer(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
for (int i = 0; i < TEST_BUFFER_SIZE; i++) {
buffers[i] = new T[STREAM_BUFFER_SIZE];
}
generic_block<SampleFrameBuffer<T>>::registerInput(in);
generic_block<SampleFrameBuffer<T>>::registerOutput(&out);
generic_block<SampleFrameBuffer<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<SampleFrameBuffer<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<SampleFrameBuffer<T>>::ctrlMtx);
generic_block<SampleFrameBuffer<T>>::tempStop();
generic_block<SampleFrameBuffer<T>>::unregisterInput(_in);
_in = in;
generic_block<SampleFrameBuffer<T>>::registerInput(_in);
generic_block<SampleFrameBuffer<T>>::tempStart();
}
void flush() {
std::unique_lock lck(bufMtx);
readCur = writeCur;
}
int run() {
// Wait for data
int count = _in->read();
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(T));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
// Push it on the ring buffer
{
std::lock_guard<std::mutex> lck(bufMtx);
memcpy(buffers[writeCur], _in->readBuf, count * sizeof(T));
uintptr_t ptr = (uintptr_t)buffers[writeCur];
sizes[writeCur] = count;
writeCur++;
writeCur = ((writeCur) % TEST_BUFFER_SIZE);
// if (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) >= (TEST_BUFFER_SIZE-2)) {
// spdlog::warn("Overflow");
// }
}
cnd.notify_all();
_in->flush();
return count;
}
void worker() {
while (true) {
// Wait for data
std::unique_lock lck(bufMtx);
cnd.wait(lck, [this](){ return (((writeCur - readCur + TEST_BUFFER_SIZE) % TEST_BUFFER_SIZE) > 0) || stopWorker; });
if (stopWorker) { break; }
// Write one to output buffer and unlock in preparation to swap buffers
int count = sizes[readCur];
memcpy(out.writeBuf, buffers[readCur], count * sizeof(T));
readCur++;
readCur = ((readCur) % TEST_BUFFER_SIZE);
lck.unlock();
// Swap
if (!out.swap(count)) { break; }
}
}
stream<T> out;
int writeCur = 0;
int readCur = 0;
bool bypass = false;
private:
void doStart() {
generic_block<SampleFrameBuffer<T>>::workerThread = std::thread(&generic_block<SampleFrameBuffer<T>>::workerLoop, this);
readWorkerThread = std::thread(&SampleFrameBuffer<T>::worker, this);
}
void doStop() {
_in->stopReader();
out.stopWriter();
stopWorker = true;
cnd.notify_all();
if (generic_block<SampleFrameBuffer<T>>::workerThread.joinable()) { generic_block<SampleFrameBuffer<T>>::workerThread.join(); }
if (readWorkerThread.joinable()) { readWorkerThread.join(); }
_in->clearReadStop();
out.clearWriteStop();
stopWorker = false;
}
stream<T>* _in;
std::thread readWorkerThread;
std::mutex bufMtx;
std::condition_variable cnd;
T* buffers[TEST_BUFFER_SIZE];
int sizes[TEST_BUFFER_SIZE];
bool stopWorker = false;
};
};

View File

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

345
core/src/dsp/convertion.h Normal file
View File

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

76
core/src/dsp/correction.h Normal file
View File

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

107
core/src/dsp/decimation.h Normal file
View File

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

420
core/src/dsp/deframing.h Normal file
View File

@ -0,0 +1,420 @@
#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;
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;
}
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;
};
}

729
core/src/dsp/demodulator.h Normal file
View File

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

134
core/src/dsp/falcon_fec.h Normal file
View File

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

View File

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

272
core/src/dsp/filter.h Normal file
View File

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

View File

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

248
core/src/dsp/math.h Normal file
View File

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

82
core/src/dsp/measure.h Normal file
View File

@ -0,0 +1,82 @@
#pragma once
#include <dsp/block.h>
#include <fftw3.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <dsp/types.h>
#include <string.h>
namespace dsp {
class LevelMeter : public generic_block<LevelMeter> {
public:
LevelMeter() {}
LevelMeter(stream<stereo_t>* in) { init(in); }
void init(stream<stereo_t>* in) {
_in = in;
generic_block<LevelMeter>::registerInput(_in);
generic_block<LevelMeter>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
assert(generic_block<LevelMeter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<LevelMeter>::ctrlMtx);
generic_block<LevelMeter>::tempStop();
generic_block<LevelMeter>::unregisterInput(_in);
_in = in;
generic_block<LevelMeter>::registerInput(_in);
generic_block<LevelMeter>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float maxL = 0, maxR = 0;
float absL, absR;
for (int i = 0; i < count; i++) {
absL = fabs(_in->readBuf[i].l);
absR = fabs(_in->readBuf[i].r);
if (absL > maxL) { maxL = absL; }
if (absR > maxR) { maxR = absR; }
}
_in->flush();
float _lvlL = 10.0f * logf(maxL);
float _lvlR = 10.0f * logf(maxR);
// Update max values
{
std::lock_guard<std::mutex> lck(lvlMtx);
if (_lvlL > lvlL) { lvlL = _lvlL; }
if (_lvlR > lvlR) { lvlR = _lvlR; }
}
return count;
}
float getLeftLevel() {
std::lock_guard<std::mutex> lck(lvlMtx);
float ret = lvlL;
lvlL = -90.0f;
return ret;
}
float getRightLevel() {
std::lock_guard<std::mutex> lck(lvlMtx);
float ret = lvlR;
lvlR = -90.0f;
return ret;
}
private:
float lvlL = -90.0f;
float lvlR = -90.0f;
stream<stereo_t>* _in;
std::mutex lvlMtx;
};
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <dsp/block.h>
#include <dsp/utils/bitstream.h>
namespace dsp {
namespace meteor {
class HRPTDemux : public generic_block<HRPTDemux> {
public:
HRPTDemux() {}
HRPTDemux(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<HRPTDemux>::registerInput(_in);
generic_block<HRPTDemux>::registerOutput(&telemOut);
generic_block<HRPTDemux>::registerOutput(&BISMout);
generic_block<HRPTDemux>::registerOutput(&SSPDOut);
generic_block<HRPTDemux>::registerOutput(&MTVZAOut);
generic_block<HRPTDemux>::registerOutput(&MSUMROut);
generic_block<HRPTDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<HRPTDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HRPTDemux>::ctrlMtx);
generic_block<HRPTDemux>::tempStop();
generic_block<HRPTDemux>::unregisterInput(_in);
_in = in;
generic_block<HRPTDemux>::registerInput(_in);
generic_block<HRPTDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < 4; i++) {
memcpy(telemOut.writeBuf + (i * 2), _in->readBuf + 4 + (i * 256), 2);
memcpy(BISMout.writeBuf + (i * 4), _in->readBuf + 4 + (i * 256) + 2, 4);
memcpy(SSPDOut.writeBuf + (i * 4), _in->readBuf + 4 + (i * 256) + 6, 4);
memcpy(MTVZAOut.writeBuf + (i * 8), _in->readBuf + 4 + (i * 256) + 10, 8);
memcpy(MSUMROut.writeBuf + (i * 238), _in->readBuf + 4 + (i * 256) + 18, (i == 3) ? 234 : 238);
}
if (!telemOut.swap(8)) { return -1; }
if (!BISMout.swap(16)) { return -1; }
if (!SSPDOut.swap(16)) { return -1; }
if (!MTVZAOut.swap(32)) { return -1; }
if (!MSUMROut.swap(948)) { return -1; }
_in->flush();
return count;
}
stream<uint8_t> telemOut;
stream<uint8_t> BISMout;
stream<uint8_t> SSPDOut;
stream<uint8_t> MTVZAOut;
stream<uint8_t> MSUMROut;
private:
stream<uint8_t>* _in;
};
}
}

View File

@ -0,0 +1,78 @@
#pragma once
#include <dsp/block.h>
#include <dsp/utils/bitstream.h>
namespace dsp {
namespace meteor {
const uint64_t MSUMR_SYNC_WORD = 0x0218A7A392DD9ABF;
const uint8_t MSUMR_SYNC_BYTES[8] = { 0x02, 0x18, 0xA7, 0xA3, 0x92, 0xDD, 0x9A, 0xBF };
class MSUMRDemux : public generic_block<MSUMRDemux> {
public:
MSUMRDemux() {}
MSUMRDemux(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<MSUMRDemux>::registerInput(_in);
generic_block<MSUMRDemux>::registerOutput(&msumr1Out);
generic_block<MSUMRDemux>::registerOutput(&msumr2Out);
generic_block<MSUMRDemux>::registerOutput(&msumr3Out);
generic_block<MSUMRDemux>::registerOutput(&msumr4Out);
generic_block<MSUMRDemux>::registerOutput(&msumr5Out);
generic_block<MSUMRDemux>::registerOutput(&msumr6Out);
generic_block<MSUMRDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<MSUMRDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<MSUMRDemux>::ctrlMtx);
generic_block<MSUMRDemux>::tempStop();
generic_block<MSUMRDemux>::unregisterInput(_in);
_in = in;
generic_block<MSUMRDemux>::registerInput(_in);
generic_block<MSUMRDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
int pixels = 0;
for (int i = 0; i < 11790; i += 30) {
for (int j = 0; j < 4; j++) {
msumr1Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10), 10, _in->readBuf);
msumr2Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 1), 10, _in->readBuf);
msumr3Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 2), 10, _in->readBuf);
msumr4Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 3), 10, _in->readBuf);
msumr5Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 4), 10, _in->readBuf);
msumr6Out.writeBuf[pixels + j] = (uint16_t)readBits((50 * 8) + (i * 8) + (j * 10) + (40 * 5), 10, _in->readBuf);
}
pixels += 4;
}
if (!msumr1Out.swap(1572)) { return -1; }
if (!msumr2Out.swap(1572)) { return -1; }
if (!msumr3Out.swap(1572)) { return -1; }
if (!msumr4Out.swap(1572)) { return -1; }
if (!msumr5Out.swap(1572)) { return -1; }
if (!msumr6Out.swap(1572)) { return -1; }
_in->flush();
return count;
}
stream<uint16_t> msumr1Out;
stream<uint16_t> msumr2Out;
stream<uint16_t> msumr3Out;
stream<uint16_t> msumr4Out;
stream<uint16_t> msumr5Out;
stream<uint16_t> msumr6Out;
private:
stream<uint8_t>* _in;
};
}
}

111
core/src/dsp/noaa/hrpt.h Normal file
View File

@ -0,0 +1,111 @@
#pragma once
#include <dsp/block.h>
#include <dsp/utils/bitstream.h>
namespace dsp {
namespace noaa {
inline uint16_t HRPTReadWord(int offset, uint8_t* buffer) {
return (uint16_t)readBits(offset * 10, 10, buffer);
}
const uint8_t HRPTSyncWord[] = {
1,0,1,0,0,0,0,1,0,0,
0,1,0,1,1,0,1,1,1,1,
1,1,0,1,0,1,1,1,0,0,
0,1,1,0,0,1,1,1,0,1,
1,0,0,0,0,0,1,1,1,1,
0,0,1,0,0,1,0,1,0,1
};
class HRPTDemux : public generic_block<HRPTDemux> {
public:
HRPTDemux() {}
HRPTDemux(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<HRPTDemux>::registerInput(_in);
generic_block<HRPTDemux>::registerOutput(&AVHRRChan1Out);
generic_block<HRPTDemux>::registerOutput(&AVHRRChan2Out);
generic_block<HRPTDemux>::registerOutput(&AVHRRChan3Out);
generic_block<HRPTDemux>::registerOutput(&AVHRRChan4Out);
generic_block<HRPTDemux>::registerOutput(&AVHRRChan5Out);
generic_block<HRPTDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<HRPTDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HRPTDemux>::ctrlMtx);
generic_block<HRPTDemux>::tempStop();
generic_block<HRPTDemux>::unregisterInput(_in);
_in = in;
generic_block<HRPTDemux>::registerInput(_in);
generic_block<HRPTDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
int minFrame = readBits(61, 2, _in->readBuf);
// If GAC frame, reject
if (minFrame == 0) {
_in->flush();
return count;
}
// Extract TIP frames if present
if (minFrame == 1) {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 104; j++) {
TIPOut.writeBuf[j] = (HRPTReadWord(103 + (i * 104) + j, _in->readBuf) >> 2) & 0xFF;
}
if (!TIPOut.swap(104)) { return -1; };
}
}
// Exctact AIP otherwise
else if (minFrame == 3) {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 104; j++) {
AIPOut.writeBuf[j] = (HRPTReadWord(103 + (i * 104) + j, _in->readBuf) >> 2) & 0xFF;
}
if (!AIPOut.swap(104)) { return -1; };
}
}
// Extract AVHRR data
for (int i = 0; i < 2048; i++) {
AVHRRChan1Out.writeBuf[i] = HRPTReadWord(750 + (i * 5), _in->readBuf);
AVHRRChan2Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 1, _in->readBuf);
AVHRRChan3Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 2, _in->readBuf);
AVHRRChan4Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 3, _in->readBuf);
AVHRRChan5Out.writeBuf[i] = HRPTReadWord(750 + (i * 5) + 4, _in->readBuf);
}
if (!AVHRRChan1Out.swap(2048)) { return -1; };
if (!AVHRRChan2Out.swap(2048)) { return -1; };
if (!AVHRRChan3Out.swap(2048)) { return -1; };
if (!AVHRRChan4Out.swap(2048)) { return -1; };
if (!AVHRRChan5Out.swap(2048)) { return -1; };
_in->flush();
return count;
}
stream<uint8_t> TIPOut;
stream<uint8_t> AIPOut;
stream<uint16_t> AVHRRChan1Out;
stream<uint16_t> AVHRRChan2Out;
stream<uint16_t> AVHRRChan3Out;
stream<uint16_t> AVHRRChan4Out;
stream<uint16_t> AVHRRChan5Out;
private:
stream<uint8_t>* _in;
};
}
}

242
core/src/dsp/noaa/tip.h Normal file
View File

@ -0,0 +1,242 @@
#pragma once
#include <dsp/block.h>
#include <dsp/utils/bitstream.h>
namespace dsp {
namespace noaa {
class TIPDemux : public generic_block<TIPDemux> {
public:
TIPDemux() {}
TIPDemux(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<TIPDemux>::registerInput(_in);
generic_block<TIPDemux>::registerOutput(&HIRSOut);
generic_block<TIPDemux>::registerOutput(&SEMOut);
generic_block<TIPDemux>::registerOutput(&DCSOut);
generic_block<TIPDemux>::registerOutput(&SBUVOut);
generic_block<TIPDemux>::_block_init = true;
}
void setInput(stream<uint8_t>* in) {
assert(generic_block<TIPDemux>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<TIPDemux>::ctrlMtx);
generic_block<TIPDemux>::tempStop();
generic_block<TIPDemux>::unregisterInput(_in);
_in = in;
generic_block<TIPDemux>::registerInput(_in);
generic_block<TIPDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// Extract HIRS
HIRSOut.writeBuf[0] = _in->readBuf[16];
HIRSOut.writeBuf[1] = _in->readBuf[17];
HIRSOut.writeBuf[2] = _in->readBuf[22];
HIRSOut.writeBuf[3] = _in->readBuf[23];
HIRSOut.writeBuf[4] = _in->readBuf[26];
HIRSOut.writeBuf[5] = _in->readBuf[27];
HIRSOut.writeBuf[6] = _in->readBuf[30];
HIRSOut.writeBuf[7] = _in->readBuf[31];
HIRSOut.writeBuf[8] = _in->readBuf[34];
HIRSOut.writeBuf[9] = _in->readBuf[35];
HIRSOut.writeBuf[10] = _in->readBuf[38];
HIRSOut.writeBuf[11] = _in->readBuf[39];
HIRSOut.writeBuf[12] = _in->readBuf[42];
HIRSOut.writeBuf[13] = _in->readBuf[43];
HIRSOut.writeBuf[14] = _in->readBuf[54];
HIRSOut.writeBuf[15] = _in->readBuf[55];
HIRSOut.writeBuf[16] = _in->readBuf[58];
HIRSOut.writeBuf[17] = _in->readBuf[59];
HIRSOut.writeBuf[18] = _in->readBuf[62];
HIRSOut.writeBuf[19] = _in->readBuf[63];
HIRSOut.writeBuf[20] = _in->readBuf[66];
HIRSOut.writeBuf[21] = _in->readBuf[67];
HIRSOut.writeBuf[22] = _in->readBuf[70];
HIRSOut.writeBuf[23] = _in->readBuf[71];
HIRSOut.writeBuf[24] = _in->readBuf[74];
HIRSOut.writeBuf[25] = _in->readBuf[75];
HIRSOut.writeBuf[26] = _in->readBuf[78];
HIRSOut.writeBuf[27] = _in->readBuf[79];
HIRSOut.writeBuf[28] = _in->readBuf[82];
HIRSOut.writeBuf[29] = _in->readBuf[83];
HIRSOut.writeBuf[30] = _in->readBuf[84];
HIRSOut.writeBuf[31] = _in->readBuf[85];
HIRSOut.writeBuf[32] = _in->readBuf[88];
HIRSOut.writeBuf[33] = _in->readBuf[89];
HIRSOut.writeBuf[34] = _in->readBuf[92];
HIRSOut.writeBuf[35] = _in->readBuf[93];
if (!HIRSOut.swap(36)) { return -1; };
// Extract SEM
SEMOut.writeBuf[0] = _in->readBuf[20];
SEMOut.writeBuf[1] = _in->readBuf[21];
if (!SEMOut.swap(2)) { return -1; };
// Extract DCS
DCSOut.writeBuf[0] = _in->readBuf[18];
DCSOut.writeBuf[1] = _in->readBuf[19];
DCSOut.writeBuf[2] = _in->readBuf[24];
DCSOut.writeBuf[3] = _in->readBuf[25];
DCSOut.writeBuf[4] = _in->readBuf[28];
DCSOut.writeBuf[5] = _in->readBuf[29];
DCSOut.writeBuf[6] = _in->readBuf[32];
DCSOut.writeBuf[7] = _in->readBuf[33];
DCSOut.writeBuf[8] = _in->readBuf[40];
DCSOut.writeBuf[9] = _in->readBuf[41];
DCSOut.writeBuf[10] = _in->readBuf[44];
DCSOut.writeBuf[11] = _in->readBuf[45];
DCSOut.writeBuf[12] = _in->readBuf[52];
DCSOut.writeBuf[13] = _in->readBuf[53];
DCSOut.writeBuf[14] = _in->readBuf[56];
DCSOut.writeBuf[15] = _in->readBuf[57];
DCSOut.writeBuf[16] = _in->readBuf[60];
DCSOut.writeBuf[17] = _in->readBuf[61];
DCSOut.writeBuf[18] = _in->readBuf[64];
DCSOut.writeBuf[19] = _in->readBuf[65];
DCSOut.writeBuf[20] = _in->readBuf[68];
DCSOut.writeBuf[21] = _in->readBuf[69];
DCSOut.writeBuf[22] = _in->readBuf[72];
DCSOut.writeBuf[23] = _in->readBuf[73];
DCSOut.writeBuf[24] = _in->readBuf[76];
DCSOut.writeBuf[25] = _in->readBuf[77];
DCSOut.writeBuf[26] = _in->readBuf[86];
DCSOut.writeBuf[27] = _in->readBuf[87];
DCSOut.writeBuf[28] = _in->readBuf[90];
DCSOut.writeBuf[29] = _in->readBuf[91];
DCSOut.writeBuf[30] = _in->readBuf[94];
DCSOut.writeBuf[31] = _in->readBuf[95];
if (!DCSOut.swap(32)) { return -1; };
// Extract SBUV
SBUVOut.writeBuf[0] = _in->readBuf[36];
SBUVOut.writeBuf[1] = _in->readBuf[37];
SBUVOut.writeBuf[2] = _in->readBuf[80];
SBUVOut.writeBuf[3] = _in->readBuf[81];
if (!SBUVOut.swap(4)) { return -1; };
_in->flush();
return count;
}
stream<uint8_t> HIRSOut;
stream<uint8_t> SEMOut;
stream<uint8_t> DCSOut;
stream<uint8_t> SBUVOut;
private:
stream<uint8_t>* _in;
};
inline uint16_t HIRSSignedToUnsigned(uint16_t n) {
return (n & 0x1000) ? (0x1000 + (n & 0xFFF)) : (0xFFF - (n & 0xFFF));
}
class HIRSDemux : public generic_block<HIRSDemux> {
public:
HIRSDemux() {}
HIRSDemux(stream<uint8_t>* in) { init(in); }
void init(stream<uint8_t>* in) {
_in = in;
generic_block<HIRSDemux>::registerInput(_in);
for (int i = 0; i < 20; i++) {
generic_block<HIRSDemux>::registerOutput(&radChannels[i]);
}
// Clear buffers
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
}
}
void setInput(stream<uint8_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<HIRSDemux>::ctrlMtx);
generic_block<HIRSDemux>::tempStop();
generic_block<HIRSDemux>::unregisterInput(_in);
_in = in;
generic_block<HIRSDemux>::registerInput(_in);
generic_block<HIRSDemux>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
int element = readBits(19, 6, _in->readBuf);
// If we've skipped or are on a non image element and there's data avilable, send it
if ((element < lastElement || element > 55) && newImageData) {
newImageData = false;
for (int i = 0; i < 20; i++) {
if (!radChannels[i].swap(56)) { return -1; }
}
// Clear buffers
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
}
}
lastElement = element;
// If data is part of a line, save it
if (element <= 55) {
newImageData = true;
radChannels[0].writeBuf[element] = HIRSSignedToUnsigned(readBits(26, 13, _in->readBuf));
radChannels[1].writeBuf[element] = HIRSSignedToUnsigned(readBits(52, 13, _in->readBuf));
radChannels[2].writeBuf[element] = HIRSSignedToUnsigned(readBits(65, 13, _in->readBuf));
radChannels[3].writeBuf[element] = HIRSSignedToUnsigned(readBits(91, 13, _in->readBuf));
radChannels[4].writeBuf[element] = HIRSSignedToUnsigned(readBits(221, 13, _in->readBuf));
radChannels[5].writeBuf[element] = HIRSSignedToUnsigned(readBits(208, 13, _in->readBuf));
radChannels[6].writeBuf[element] = HIRSSignedToUnsigned(readBits(143, 13, _in->readBuf));
radChannels[7].writeBuf[element] = HIRSSignedToUnsigned(readBits(156, 13, _in->readBuf));
radChannels[8].writeBuf[element] = HIRSSignedToUnsigned(readBits(273, 13, _in->readBuf));
radChannels[9].writeBuf[element] = HIRSSignedToUnsigned(readBits(182, 13, _in->readBuf));
radChannels[10].writeBuf[element] = HIRSSignedToUnsigned(readBits(119, 13, _in->readBuf));
radChannels[11].writeBuf[element] = HIRSSignedToUnsigned(readBits(247, 13, _in->readBuf));
radChannels[12].writeBuf[element] = HIRSSignedToUnsigned(readBits(78, 13, _in->readBuf));
radChannels[13].writeBuf[element] = HIRSSignedToUnsigned(readBits(195, 13, _in->readBuf));
radChannels[14].writeBuf[element] = HIRSSignedToUnsigned(readBits(234, 13, _in->readBuf));
radChannels[15].writeBuf[element] = HIRSSignedToUnsigned(readBits(260, 13, _in->readBuf));
radChannels[16].writeBuf[element] = HIRSSignedToUnsigned(readBits(39, 13, _in->readBuf));
radChannels[17].writeBuf[element] = HIRSSignedToUnsigned(readBits(104, 13, _in->readBuf));
radChannels[18].writeBuf[element] = HIRSSignedToUnsigned(readBits(130, 13, _in->readBuf));
radChannels[19].writeBuf[element] = HIRSSignedToUnsigned(readBits(169, 13, _in->readBuf));
}
// If we are writing the last pixel of a line, send it already
if (element == 55) {
newImageData = false;
for (int i = 0; i < 20; i++) {
if (!radChannels[i].swap(56)) { return -1; }
}
// Clear buffers
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 56; j++) { radChannels[i].writeBuf[j] = 0xFFF; }
}
}
_in->flush();
return count;
}
stream<uint16_t> radChannels[20];
private:
stream<uint8_t>* _in;
int lastElement = 0;
bool newImageData = false;
};
}
}

325
core/src/dsp/pll.h Normal file
View File

@ -0,0 +1,325 @@
#pragma once
#include <dsp/block.h>
#include <dsp/interpolation_taps.h>
#include <math.h>
#include <dsp/utils/macros.h>
#include <dsp/math.h>
namespace dsp {
template <int ORDER>
class CostasLoop: public generic_block<CostasLoop<ORDER>> {
public:
CostasLoop() {}
CostasLoop(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
void init(stream<complex_t>* in, float loopBandwidth) {
_in = in;
lastVCO.re = 1.0f;
lastVCO.im = 0.0f;
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<CostasLoop<ORDER>>::registerInput(_in);
generic_block<CostasLoop<ORDER>>::registerOutput(&out);
generic_block<CostasLoop<ORDER>>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<CostasLoop<ORDER>>::_block_init);
generic_block<CostasLoop<ORDER>>::tempStop();
generic_block<CostasLoop<ORDER>>::unregisterInput(_in);
_in = in;
generic_block<CostasLoop<ORDER>>::registerInput(_in);
generic_block<CostasLoop<ORDER>>::tempStart();
}
void setLoopBandwidth(float loopBandwidth) {
assert(generic_block<CostasLoop<ORDER>>::_block_init);
generic_block<CostasLoop<ORDER>>::tempStop();
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<CostasLoop<ORDER>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
complex_t outVal;
float error;
for (int i = 0; i < count; i++) {
// Mix the VFO with the input to create the output value
outVal.re = (lastVCO.re*_in->readBuf[i].re) - (lastVCO.im*_in->readBuf[i].im);
outVal.im = (lastVCO.im*_in->readBuf[i].re) + (lastVCO.re*_in->readBuf[i].im);
out.writeBuf[i] = outVal;
// Calculate the phase error estimation
if constexpr (ORDER == 2) {
error = outVal.re * outVal.im;
}
if constexpr (ORDER == 4) {
error = (DSP_STEP(outVal.re) * outVal.im) - (DSP_STEP(outVal.im) * outVal.re);
}
if constexpr (ORDER == 8) {
// This is taken from GR, I have no idea how it works but it does...
const float K = (sqrtf(2.0) - 1);
if (fabsf(outVal.re) >= fabsf(outVal.im)) {
error = ((outVal.re > 0.0f ? 1.0f : -1.0f) * outVal.im -
(outVal.im > 0.0f ? 1.0f : -1.0f) * outVal.re * K);
} else {
error = ((outVal.re > 0.0f ? 1.0f : -1.0f) * outVal.im * K -
(outVal.im > 0.0f ? 1.0f : -1.0f) * outVal.re);
}
}
if (error > 1.0f) { error = 1.0f; }
else if (error < -1.0f) { error = -1.0f; }
// Integrate frequency and clamp it
vcoFrequency += _beta * error;
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
// Calculate new phase and wrap it
vcoPhase += vcoFrequency + (_alpha * error);
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
// Calculate output
lastVCO.re = cosf(-vcoPhase);
lastVCO.im = sinf(-vcoPhase);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float _loopBandwidth = 1.0f;
float _alpha; // Integral coefficient
float _beta; // Proportional coefficient
float vcoFrequency = 0.0f;
float vcoPhase = 0.0f;
complex_t lastVCO;
stream<complex_t>* _in;
};
template <class T>
class CarrierTrackingPLL: public generic_block<CarrierTrackingPLL<T>> {
public:
CarrierTrackingPLL() {}
CarrierTrackingPLL(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
void init(stream<complex_t>* in, float loopBandwidth) {
_in = in;
lastVCO.re = 1.0f;
lastVCO.im = 0.0f;
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<CarrierTrackingPLL<T>>::registerInput(_in);
generic_block<CarrierTrackingPLL<T>>::registerOutput(&out);
generic_block<CarrierTrackingPLL<T>>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<CarrierTrackingPLL<T>>::_block_init);
generic_block<CarrierTrackingPLL<T>>::tempStop();
generic_block<CarrierTrackingPLL<T>>::unregisterInput(_in);
_in = in;
generic_block<CarrierTrackingPLL<T>>::registerInput(_in);
generic_block<CarrierTrackingPLL<T>>::tempStart();
}
void setLoopBandwidth(float loopBandwidth) {
assert(generic_block<CarrierTrackingPLL<T>>::_block_init);
generic_block<CarrierTrackingPLL<T>>::tempStop();
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<CarrierTrackingPLL<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
complex_t outVal;
float error;
for (int i = 0; i < count; i++) {
// Mix the VFO with the input to create the output value
outVal.re = (lastVCO.re*_in->readBuf[i].re) - ((-lastVCO.im)*_in->readBuf[i].im);
outVal.im = ((-lastVCO.im)*_in->readBuf[i].re) + (lastVCO.re*_in->readBuf[i].im);
if constexpr (std::is_same_v<T, float>) {
out.writeBuf[i] = outVal.fastPhase();
}
if constexpr (std::is_same_v<T, complex_t>) {
out.writeBuf[i] = outVal;
}
// Calculate the phase error estimation
// TODO: Figure out why fastPhase doesn't work
error = _in->readBuf[i].phase() - vcoPhase;
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
// if (error > 1.0f) { error = 1.0f; }
// else if (error < -1.0f) { error = -1.0f; }
// Integrate frequency and clamp it
vcoFrequency += _beta * error;
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
// Calculate new phase and wrap it
vcoPhase += vcoFrequency + (_alpha * error);
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
// Calculate output
lastVCO.re = cosf(vcoPhase);
lastVCO.im = sinf(vcoPhase);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<T> out;
private:
float _loopBandwidth = 1.0f;
float _alpha; // Integral coefficient
float _beta; // Proportional coefficient
float vcoFrequency = 0.0f;
float vcoPhase = 0.0f;
complex_t lastVCO;
stream<complex_t>* _in;
};
class PLL: public generic_block<PLL> {
public:
PLL() {}
PLL(stream<complex_t>* in, float loopBandwidth) { init(in, loopBandwidth); }
void init(stream<complex_t>* in, float loopBandwidth) {
_in = in;
lastVCO.re = 1.0f;
lastVCO.im = 0.0f;
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<PLL>::registerInput(_in);
generic_block<PLL>::registerOutput(&out);
generic_block<PLL>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<PLL>::_block_init);
generic_block<PLL>::tempStop();
generic_block<PLL>::unregisterInput(_in);
_in = in;
generic_block<PLL>::registerInput(_in);
generic_block<PLL>::tempStart();
}
void setLoopBandwidth(float loopBandwidth) {
assert(generic_block<PLL>::_block_init);
generic_block<PLL>::tempStop();
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<PLL>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
complex_t outVal;
float error;
for (int i = 0; i < count; i++) {
out.writeBuf[i] = lastVCO;
// Calculate the phase error estimation
// TODO: Figure out why fastPhase doesn't work
error = _in->readBuf[i].phase() - vcoPhase;
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
// Integrate frequency and clamp it
vcoFrequency += _beta * error;
if (vcoFrequency > 1.0f) { vcoFrequency = 1.0f; }
else if (vcoFrequency < -1.0f) { vcoFrequency = -1.0f; }
// Calculate new phase and wrap it
vcoPhase += vcoFrequency + (_alpha * error);
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
// Calculate output
lastVCO.re = cosf(vcoPhase);
lastVCO.im = sinf(vcoPhase);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float _loopBandwidth = 1.0f;
float _alpha; // Integral coefficient
float _beta; // Proportional coefficient
float vcoFrequency = ((19000.0f / 250000.0f) * 2.0f * FL_M_PI);
float vcoPhase = 0.0f;
complex_t lastVCO;
stream<complex_t>* _in;
};
}

618
core/src/dsp/processing.h Normal file
View File

@ -0,0 +1,618 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <string.h>
#include <stdint.h>
#include <dsp/math.h>
namespace dsp {
template <class T>
class FrequencyXlator : public generic_block<FrequencyXlator<T>> {
public:
FrequencyXlator() {}
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
void init(stream<complex_t>* in, float sampleRate, float freq) {
_in = in;
_sampleRate = sampleRate;
_freq = freq;
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::registerOutput(&out);
generic_block<FrequencyXlator<T>>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<FrequencyXlator<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx);
generic_block<FrequencyXlator<T>>::tempStop();
generic_block<FrequencyXlator<T>>::unregisterInput(_in);
_in = in;
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<FrequencyXlator<T>>::_block_init);
_sampleRate = sampleRate;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getSampleRate() {
assert(generic_block<FrequencyXlator<T>>::_block_init);
return _sampleRate;
}
void setFrequency(float freq) {
assert(generic_block<FrequencyXlator<T>>::_block_init);
_freq = freq;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getFrequency() {
assert(generic_block<FrequencyXlator<T>>::_block_init);
return _freq;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
// TODO: Do float xlation
if constexpr (std::is_same_v<T, float>) {
spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT");
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
stream<complex_t>* _in;
};
class AGC : public generic_block<AGC> {
public:
AGC() {}
AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); }
void init(stream<float>* in, float fallRate, float sampleRate) {
_in = in;
_sampleRate = sampleRate;
_fallRate = fallRate;
_CorrectedFallRate = _fallRate / _sampleRate;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::registerOutput(&out);
generic_block<AGC>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<AGC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
generic_block<AGC>::tempStop();
generic_block<AGC>::unregisterInput(_in);
_in = in;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::tempStart();
}
void setSampleRate(float sampleRate) {
assert(generic_block<AGC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
_sampleRate = sampleRate;
_CorrectedFallRate = _fallRate / _sampleRate;
}
void setFallRate(float fallRate) {
assert(generic_block<AGC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
_fallRate = fallRate;
_CorrectedFallRate = _fallRate / _sampleRate;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
level = pow(10, ((10.0f * log10f(level)) - (_CorrectedFallRate * count)) / 10.0f);
if (level < 10e-14) { level = 10e-14; }
float absVal;
for (int i = 0; i < count; i++) {
absVal = fabsf(_in->readBuf[i]);
if (absVal > level) { level = absVal; }
}
volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float level = 0.0f;
float _fallRate;
float _CorrectedFallRate;
float _sampleRate;
stream<float>* _in;
};
class ComplexAGC : public generic_block<ComplexAGC> {
public:
ComplexAGC() {}
ComplexAGC(stream<complex_t>* in, float setPoint, float maxGain, float rate) { init(in, setPoint, maxGain, rate); }
void init(stream<complex_t>* in, float setPoint, float maxGain, float rate) {
_in = in;
_setPoint = setPoint;
_maxGain = maxGain;
_rate = rate;
generic_block<ComplexAGC>::registerInput(_in);
generic_block<ComplexAGC>::registerOutput(&out);
generic_block<ComplexAGC>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<ComplexAGC>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<ComplexAGC>::ctrlMtx);
generic_block<ComplexAGC>::tempStop();
generic_block<ComplexAGC>::unregisterInput(_in);
_in = in;
generic_block<ComplexAGC>::registerInput(_in);
generic_block<ComplexAGC>::tempStart();
}
void setSetPoint(float setPoint) {
assert(generic_block<ComplexAGC>::_block_init);
_setPoint = setPoint;
}
void setMaxGain(float maxGain) {
assert(generic_block<ComplexAGC>::_block_init);
_maxGain = maxGain;
}
void setRate(float rate) {
assert(generic_block<ComplexAGC>::_block_init);
_rate = rate;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
dsp::complex_t val;
for (int i = 0; i < count; i++) {
val = _in->readBuf[i] * _gain;
out.writeBuf[i] = val;
_gain += (_setPoint - val.amplitude()) * _rate;
if (_gain > _maxGain) { _gain = _maxGain; }
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float _gain = 1.0f;
float _setPoint = 1.0f;
float _maxGain = 10e4;
float _rate = 10e-4;
stream<complex_t>* _in;
};
class DelayImag : public generic_block<DelayImag> {
public:
DelayImag() {}
DelayImag(stream<complex_t>* in) { init(in); }
void init(stream<complex_t>* in) {
_in = in;
generic_block<DelayImag>::registerInput(_in);
generic_block<DelayImag>::registerOutput(&out);
generic_block<DelayImag>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<DelayImag>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<DelayImag>::ctrlMtx);
generic_block<DelayImag>::tempStop();
generic_block<DelayImag>::unregisterInput(_in);
_in = in;
generic_block<DelayImag>::registerInput(_in);
generic_block<DelayImag>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
dsp::complex_t val;
for (int i = 0; i < count; i++) {
val = _in->readBuf[i];
out.writeBuf[i].re = val.re;
out.writeBuf[i].im = lastIm;
lastIm = val.im;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float lastIm = 0.0f;
stream<complex_t>* _in;
};
template <class T>
class Volume : public generic_block<Volume<T>> {
public:
Volume() {}
Volume(stream<T>* in, float volume) { init(in, volume); }
void init(stream<T>* in, float volume) {
_in = in;
_volume = volume;
generic_block<Volume<T>>::registerInput(_in);
generic_block<Volume<T>>::registerOutput(&out);
generic_block<Volume<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<Volume<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx);
generic_block<Volume<T>>::tempStop();
generic_block<Volume<T>>::unregisterInput(_in);
_in = in;
generic_block<Volume<T>>::registerInput(_in);
generic_block<Volume<T>>::tempStart();
}
void setVolume(float volume) {
assert(generic_block<Volume<T>>::_block_init);
_volume = volume;
level = powf(_volume, 2);
}
float getVolume() {
assert(generic_block<Volume<T>>::_block_init);
return _volume;
}
void setMuted(bool muted) {
assert(generic_block<Volume<T>>::_block_init);
_muted = muted;
}
bool getMuted() {
assert(generic_block<Volume<T>>::_block_init);
return _muted;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (_muted) {
if constexpr (std::is_same_v<T, stereo_t>) {
memset(out.writeBuf, 0, sizeof(stereo_t) * count);
}
else {
memset(out.writeBuf, 0, sizeof(float) * count);
}
}
else {
if constexpr (std::is_same_v<T, stereo_t>) {
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count * 2);
}
else {
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count);
}
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<T> out;
private:
float level = 1.0f;
float _volume = 1.0f;
bool _muted = false;
stream<T>* _in;
};
class Squelch : public generic_block<Squelch> {
public:
Squelch() {}
Squelch(stream<complex_t>* in, float level) { init(in, level); }
~Squelch() {
if (!generic_block<Squelch>::_block_init) { return; }
generic_block<Squelch>::stop();
delete[] normBuffer;
generic_block<Squelch>::_block_init = false;
}
void init(stream<complex_t>* in, float level) {
_in = in;
_level = level;
normBuffer = new float[STREAM_BUFFER_SIZE];
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::registerOutput(&out);
generic_block<Squelch>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<Squelch>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx);
generic_block<Squelch>::tempStop();
generic_block<Squelch>::unregisterInput(_in);
_in = in;
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::tempStart();
}
void setLevel(float level) {
assert(generic_block<Squelch>::_block_init);
_level = level;
}
float getLevel() {
assert(generic_block<Squelch>::_block_init);
return _level;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
float sum;
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count);
volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count;
if (10.0f * log10f(sum) >= _level) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
}
else {
memset(out.writeBuf, 0, count * sizeof(complex_t));
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float* normBuffer;
float _level = -50.0f;
stream<complex_t>* _in;
};
template <class T>
class Packer : public generic_block<Packer<T>> {
public:
Packer() {}
Packer(stream<T>* in, int count) { init(in, count); }
void init(stream<T>* in, int count) {
_in = in;
samples = count;
generic_block<Packer<T>>::registerInput(_in);
generic_block<Packer<T>>::registerOutput(&out);
generic_block<Packer<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<Packer<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Packer<T>>::ctrlMtx);
generic_block<Packer<T>>::tempStop();
generic_block<Packer<T>>::unregisterInput(_in);
_in = in;
generic_block<Packer<T>>::registerInput(_in);
generic_block<Packer<T>>::tempStart();
}
void setSampleCount(int count) {
assert(generic_block<Packer<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Packer<T>>::ctrlMtx);
generic_block<Packer<T>>::tempStop();
samples = count;
generic_block<Packer<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) {
read = 0;
return -1;
}
for (int i = 0; i < count; i++) {
out.writeBuf[read++] = _in->readBuf[i];
if (read >= samples) {
read = 0;
if (!out.swap(samples)) {
_in->flush();
read = 0;
return -1;
}
}
}
_in->flush();
return count;
}
stream<T> out;
private:
int samples = 1;
int read = 0;
stream<T>* _in;
};
class Threshold : public generic_block<Threshold> {
public:
Threshold() {}
Threshold(stream<float>* in) { init(in); }
~Threshold() {
if (!generic_block<Threshold>::_block_init) { return; }
generic_block<Threshold>::stop();
delete[] normBuffer;
generic_block<Threshold>::_block_init = false;
}
void init(stream<float>* in) {
_in = in;
normBuffer = new float[STREAM_BUFFER_SIZE];
generic_block<Threshold>::registerInput(_in);
generic_block<Threshold>::registerOutput(&out);
generic_block<Threshold>::_block_init = true;
}
void setInput(stream<float>* in) {
assert(generic_block<Threshold>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Threshold>::ctrlMtx);
generic_block<Threshold>::tempStop();
generic_block<Threshold>::unregisterInput(_in);
_in = in;
generic_block<Threshold>::registerInput(_in);
generic_block<Threshold>::tempStart();
}
void setLevel(float level) {
assert(generic_block<Threshold>::_block_init);
_level = level;
}
float getLevel() {
assert(generic_block<Threshold>::_block_init);
return _level;
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < count; i++) {
out.writeBuf[i] = (_in->readBuf[i] > 0.0f);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<uint8_t> out;
private:
float* normBuffer;
float _level = -50.0f;
stream<float>* _in;
};
class BFMPilotToStereo : public generic_block<BFMPilotToStereo> {
public:
BFMPilotToStereo() {}
BFMPilotToStereo(stream<complex_t>* in) { init(in); }
~BFMPilotToStereo() {
generic_block<BFMPilotToStereo>::stop();
delete[] buffer;
}
void init(stream<complex_t>* in) {
_in = in;
buffer = new complex_t[STREAM_BUFFER_SIZE];
generic_block<BFMPilotToStereo>::registerInput(_in);
generic_block<BFMPilotToStereo>::registerOutput(&out);
generic_block<BFMPilotToStereo>::_block_init = true;
}
void setInputs(stream<complex_t>* in) {
assert(generic_block<BFMPilotToStereo>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<BFMPilotToStereo>::ctrlMtx);
generic_block<BFMPilotToStereo>::tempStop();
generic_block<BFMPilotToStereo>::unregisterInput(_in);
_in = in;
generic_block<BFMPilotToStereo>::registerInput(_in);
generic_block<BFMPilotToStereo>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_x2_multiply_32fc((lv_32fc_t*)buffer, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
volk_32fc_conjugate_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)buffer, count);
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
stream<complex_t>* _in;
complex_t* buffer;
};
}

219
core/src/dsp/resampling.h Normal file
View File

@ -0,0 +1,219 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <numeric>
#include <string.h>
#include <dsp/math.h>
namespace dsp {
template <class T>
class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> {
public:
PolyphaseResampler() {}
PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); }
~PolyphaseResampler() {
if (!generic_block<PolyphaseResampler<T>>::_block_init) { return; }
generic_block<PolyphaseResampler<T>>::stop();
volk_free(buffer);
volk_free(taps);
freeTapPhases();
generic_block<PolyphaseResampler<T>>::_block_init = false;
}
void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) {
_in = in;
_window = window;
_inSampleRate = inSampleRate;
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
tapCount = _window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
_window->createTaps(taps, tapCount, _interp);
buildTapPhases();
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2);
counter = 0;
offset = 0;
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::registerOutput(&out);
generic_block<PolyphaseResampler<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
generic_block<PolyphaseResampler<T>>::unregisterInput(_in);
_in = in;
counter = 0;
offset = 0;
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setInSampleRate(float inSampleRate) {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_inSampleRate = inSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
buildTapPhases();
counter = 0;
offset = 0;
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setOutSampleRate(float outSampleRate) {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
buildTapPhases();
counter = 0;
offset = 0;
generic_block<PolyphaseResampler<T>>::tempStart();
}
int getInterpolation() {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
return _interp;
}
int getDecimation() {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
return _decim;
}
void updateWindow(dsp::filter_window::generic_window* window) {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount, _interp);
buildTapPhases();
counter = 0;
offset = 0;
generic_block<PolyphaseResampler<T>>::tempStart();
}
int calcOutSize(int in) {
assert(generic_block<PolyphaseResampler<T>>::_block_init);
return (in * _interp) / _decim;
}
virtual int run() override {
int count = _in->read();
if (count < 0) {
return -1;
}
memcpy(&buffer[tapsPerPhase], _in->readBuf, count * sizeof(T));
_in->flush();
// Write to output
int outIndex = 0;
int inOffset = offset;
int _counter = counter;
if constexpr (std::is_same_v<T, float>) {
while (inOffset < count) {
volk_32f_x2_dot_prod_32f(&out.writeBuf[outIndex++], &buffer[inOffset], tapPhases[_counter], tapsPerPhase);
_counter += _decim;
inOffset += (_counter / _interp);
_counter = (_counter % _interp);
}
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
while (inOffset < count) {
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex++], (lv_32fc_t*)&buffer[inOffset], tapPhases[_counter], tapsPerPhase);
_counter += _decim;
inOffset += (_counter / _interp);
_counter = (_counter % _interp);
}
}
if (!out.swap(outIndex)) { return -1; }
offset = inOffset - count;
counter = _counter;
memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T));
return count;
}
stream<T> out;
private:
void buildTapPhases(){
if(!taps){
return;
}
if(!tapPhases.empty()){
freeTapPhases();
}
int phases = _interp;
tapsPerPhase = (tapCount+phases-1)/phases; //Integer division ceiling
bufStart = &buffer[tapsPerPhase];
for(int i = 0; i < phases; i++){
tapPhases.push_back((float*)volk_malloc(tapsPerPhase * sizeof(float), volk_get_alignment()));
}
int currentTap = 0;
for(int tap = 0; tap < tapsPerPhase; tap++) {
for (int phase = 0; phase < phases; phase++) {
if(currentTap < tapCount) {
tapPhases[(_interp - 1) - phase][tap] = taps[currentTap++];
}
else{
tapPhases[(_interp - 1) - phase][tap] = 0;
}
}
}
}
void freeTapPhases(){
for(auto & tapPhase : tapPhases){
volk_free(tapPhase);
}
tapPhases.clear();
}
stream<T>* _in;
dsp::filter_window::generic_window* _window;
T* bufStart;
T* buffer;
int tapCount;
int _interp, _decim;
float _inSampleRate, _outSampleRate;
float* taps;
int counter = 0;
int offset = 0;
int tapsPerPhase;
std::vector<float*> tapPhases;
};
}

197
core/src/dsp/routing.h Normal file
View File

@ -0,0 +1,197 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
#include <string.h>
#include <numeric>
#include <spdlog/spdlog.h>
namespace dsp {
template <class T>
class Splitter : public generic_block<Splitter<T>> {
public:
Splitter() {}
Splitter(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
generic_block<Splitter>::registerInput(_in);
generic_block<Splitter>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<Splitter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterInput(_in);
_in = in;
generic_block<Splitter>::registerInput(_in);
generic_block<Splitter>::tempStart();
}
void bindStream(stream<T>* stream) {
assert(generic_block<Splitter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
out.push_back(stream);
generic_block<Splitter>::registerOutput(stream);
generic_block<Splitter>::tempStart();
}
void unbindStream(stream<T>* stream) {
assert(generic_block<Splitter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterOutput(stream);
out.erase(std::remove(out.begin(), out.end(), stream), out.end());
generic_block<Splitter>::tempStart();
}
private:
int run() {
// TODO: If too slow, buffering might be necessary
int count = _in->read();
if (count < 0) { return -1; }
for (const auto& stream : out) {
memcpy(stream->writeBuf, _in->readBuf, count * sizeof(T));
if (!stream->swap(count)) { return -1; }
}
_in->flush();
return count;
}
stream<T>* _in;
std::vector<stream<T>*> out;
};
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
template <class T>
class Reshaper : public generic_block<Reshaper<T>> {
public:
Reshaper() {}
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
// NOTE: For some reason, the base class destrcutor doesn't get called.... this is a temporary fix I guess
// I also don't check for _block_init for the exact sample reason, something's weird
~Reshaper() {
if (!generic_block<Reshaper<T>>::_block_init) { return; }
generic_block<Reshaper<T>>::stop();
}
void init(stream<T>* in, int keep, int skip) {
_in = in;
_keep = keep;
_skip = skip;
ringBuf.init(keep * 2);
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::registerOutput(&out);
generic_block<Reshaper<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<Reshaper<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
generic_block<Reshaper<T>>::unregisterInput(_in);
_in = in;
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
void setKeep(int keep) {
assert(generic_block<Reshaper<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
_keep = keep;
ringBuf.setMaxLatency(keep * 2);
generic_block<Reshaper<T>>::tempStart();
}
void setSkip(int skip) {
assert(generic_block<Reshaper<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
_skip = skip;
generic_block<Reshaper<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
ringBuf.write(_in->readBuf, count);
_in->flush();
return count;
}
stream<T> out;
private:
void doStart() override {
workThread = std::thread(&Reshaper<T>::loop, this);
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
}
void loop() {
while (run() >= 0);
}
void doStop() override {
_in->stopReader();
ringBuf.stopReader();
out.stopWriter();
ringBuf.stopWriter();
if (workThread.joinable()) {
workThread.join();
}
if (bufferWorkerThread.joinable()) {
bufferWorkerThread.join();
}
_in->clearReadStop();
ringBuf.clearReadStop();
out.clearWriteStop();
ringBuf.clearWriteStop();
}
void bufferWorker() {
T* buf = new T[_keep];
bool delay = _skip < 0;
int readCount = std::min<int>(_keep + _skip, _keep);
int skip = std::max<int>(_skip, 0);
int delaySize = (-_skip) * sizeof(T);
int delayCount = (-_skip);
T* start = &buf[std::max<int>(-_skip, 0)];
T* delayStart = &buf[_keep + _skip];
while (true) {
if (delay) {
memmove(buf, delayStart, delaySize);
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
for (int i = 0; i < delayCount; i++) {
buf[i].re /= 10.0f;
buf[i].im /= 10.0f;
}
}
}
if (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
memcpy(out.writeBuf, buf, _keep * sizeof(T));
if (!out.swap(_keep)) { break; }
}
delete[] buf;
}
stream<T>* _in;
int _outBlockSize;
RingBuffer<T> ringBuf;
std::thread bufferWorkerThread;
std::thread workThread;
int _keep, _skip;
};
}

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

@ -0,0 +1,193 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
#include <fstream>
namespace dsp {
template <class T>
class HandlerSink : public generic_block<HandlerSink<T>> {
public:
HandlerSink() {}
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
void init(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) {
_in = in;
_handler = handler;
_ctx = ctx;
generic_block<HandlerSink<T>>::registerInput(_in);
generic_block<HandlerSink<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<HandlerSink<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
generic_block<HandlerSink<T>>::unregisterInput(_in);
_in = in;
generic_block<HandlerSink<T>>::registerInput(_in);
generic_block<HandlerSink<T>>::tempStart();
}
void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) {
assert(generic_block<HandlerSink<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
_handler = handler;
_ctx = ctx;
generic_block<HandlerSink<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
_handler(_in->readBuf, count, _ctx);
_in->flush();
return count;
}
private:
stream<T>* _in;
void (*_handler)(T* data, int count, void* ctx);
void* _ctx;
};
template <class T>
class RingBufferSink : public generic_block<RingBufferSink<T>> {
public:
RingBufferSink() {}
RingBufferSink(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
data.init(480); // TODO: Use an argument
generic_block<RingBufferSink<T>>::registerInput(_in);
generic_block<RingBufferSink<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<RingBufferSink<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx);
generic_block<RingBufferSink<T>>::tempStop();
generic_block<RingBufferSink<T>>::unregisterInput(_in);
_in = in;
generic_block<RingBufferSink<T>>::registerInput(_in);
generic_block<RingBufferSink<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (data.write(_in->readBuf, count) < 0) { return -1; }
_in->flush();
return count;
}
RingBuffer<T> data;
private:
void doStop() {
_in->stopReader();
data.stopWriter();
if (generic_block<RingBufferSink<T>>::workerThread.joinable()) {
generic_block<RingBufferSink<T>>::workerThread.join();
}
_in->clearReadStop();
data.clearWriteStop();
}
stream<T>* _in;
};
template <class T>
class NullSink : public generic_block<NullSink<T>> {
public:
NullSink() {}
NullSink(stream<T>* in) { init(in); }
void init(stream<T>* in) {
_in = in;
generic_block<NullSink<T>>::registerInput(_in);
generic_block<NullSink<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<NullSink<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx);
generic_block<NullSink<T>>::tempStop();
generic_block<NullSink<T>>::unregisterInput(_in);
_in = in;
generic_block<NullSink<T>>::registerInput(_in);
generic_block<NullSink<T>>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
_in->flush();
return count;
}
private:
stream<T>* _in;
};
template <class T>
class FileSink : public generic_block<FileSink<T>> {
public:
FileSink() {}
FileSink(stream<T>* in, std::string path) { init(in, path); }
~FileSink() {
if (!generic_block<FileSink<T>>::_block_init) { return; }
generic_block<FileSink<T>>::stop();
if (file.is_open()) { file.close(); }
generic_block<FileSink<T>>::_block_init = false;
}
void init(stream<T>* in, std::string path) {
_in = in;
file = std::ofstream(path, std::ios::binary);
generic_block<FileSink<T>>::registerInput(_in);
generic_block<FileSink<T>>::_block_init = true;
}
void setInput(stream<T>* in) {
assert(generic_block<FileSink<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FileSink<T>>::ctrlMtx);
generic_block<FileSink<T>>::tempStop();
generic_block<FileSink<T>>::unregisterInput(_in);
_in = in;
generic_block<FileSink<T>>::registerInput(_in);
generic_block<FileSink<T>>::tempStart();
}
bool isOpen() {
assert(generic_block<FileSink<T>>::_block_init);
return file.is_open();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
if (file.is_open()) {
file.write((char*)_in->readBuf, count * sizeof(T));
}
_in->flush();
return count;
}
private:
stream<T>* _in;
std::ofstream file;
};
}

116
core/src/dsp/source.h Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include <dsp/block.h>
#include <dsp/math.h>
namespace dsp {
class SineSource : public generic_block<SineSource> {
public:
SineSource() {}
SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); }
void init(int blockSize, float sampleRate, float freq) {
_blockSize = blockSize;
_sampleRate = sampleRate;
_freq = freq;
zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment());
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) {
zeroPhase[i] = lv_cmake(1.0f, 0.0f);
}
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<SineSource>::registerOutput(&out);
generic_block<SineSource>::_block_init = true;
}
void setBlockSize(int blockSize) {
assert(generic_block<SineSource>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx);
generic_block<SineSource>::tempStop();
_blockSize = blockSize;
generic_block<SineSource>::tempStart();
}
int getBlockSize() {
assert(generic_block<SineSource>::_block_init);
return _blockSize;
}
void setSampleRate(float sampleRate) {
assert(generic_block<SineSource>::_block_init);
_sampleRate = sampleRate;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getSampleRate() {
assert(generic_block<SineSource>::_block_init);
return _sampleRate;
}
void setFrequency(float freq) {
assert(generic_block<SineSource>::_block_init);
_freq = freq;
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
}
float getFrequency() {
assert(generic_block<SineSource>::_block_init);
return _freq;
}
int run() {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, zeroPhase, phaseDelta, &phase, _blockSize);
if(!out.swap(_blockSize)) { return -1; }
return _blockSize;
}
stream<complex_t> out;
private:
int _blockSize;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
lv_32fc_t* zeroPhase;
};
template <class T>
class HandlerSource : public generic_block<HandlerSource<T>> {
public:
HandlerSource() {}
HandlerSource(int (*handler)(T* data, void* ctx), void* ctx) { init(handler, ctx); }
void init(int (*handler)(T* data, void* ctx), void* ctx) {
_handler = handler;
_ctx = ctx;
generic_block<HandlerSource<T>>::registerOutput(&out);
generic_block<HandlerSource<T>>::_block_init = true;
}
void setHandler(int (*handler)(T* data, void* ctx), void* ctx) {
assert(generic_block<HandlerSource<T>>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<HandlerSource<T>>::ctrlMtx);
generic_block<HandlerSource<T>>::tempStop();
_handler = handler;
_ctx = ctx;
generic_block<HandlerSource<T>>::tempStart();
}
int run() {
int count = _handler(out.writeBuf, _ctx);
if (count < 0) { return -1; }
out.swap(count);
return count;
}
stream<T> out;
private:
int (*_handler)(T* data, void* ctx);
void* _ctx;
};
}

289
core/src/dsp/stereo_fm.h Normal file
View File

@ -0,0 +1,289 @@
#pragma once
#include <dsp/block.h>
#include <dsp/stream.h>
#include <dsp/types.h>
namespace dsp {
class FMStereoDemuxPilotFilter : public generic_block<FMStereoDemuxPilotFilter> {
public:
FMStereoDemuxPilotFilter() {}
FMStereoDemuxPilotFilter(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) { init(in, window); }
~FMStereoDemuxPilotFilter() {
if (!generic_block<FMStereoDemuxPilotFilter>::_block_init) { return; }
generic_block<FMStereoDemuxPilotFilter>::stop();
volk_free(buffer);
volk_free(taps);
generic_block<FMStereoDemuxPilotFilter>::_block_init = false;
}
void init(stream<complex_t>* in, dsp::filter_window::generic_complex_window* window) {
_in = in;
tapCount = window->getTapCount();
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
window->createTaps(taps, tapCount);
buffer = (complex_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(complex_t) * 2, volk_get_alignment());
bufStart = &buffer[tapCount];
generic_block<FMStereoDemuxPilotFilter>::registerInput(_in);
generic_block<FMStereoDemuxPilotFilter>::registerOutput(&dataOut);
generic_block<FMStereoDemuxPilotFilter>::registerOutput(&pilotOut);
generic_block<FMStereoDemuxPilotFilter>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
assert(generic_block<FMStereoDemuxPilotFilter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FMStereoDemuxPilotFilter>::ctrlMtx);
generic_block<FMStereoDemuxPilotFilter>::tempStop();
generic_block<FMStereoDemuxPilotFilter>::unregisterInput(_in);
_in = in;
generic_block<FMStereoDemuxPilotFilter>::registerInput(_in);
generic_block<FMStereoDemuxPilotFilter>::tempStart();
}
void updateWindow(dsp::filter_window::generic_complex_window* window) {
assert(generic_block<FMStereoDemuxPilotFilter>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FMStereoDemuxPilotFilter>::ctrlMtx);
std::lock_guard<std::mutex> lck2(bufMtx);
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (complex_t*)volk_malloc(tapCount * sizeof(complex_t), volk_get_alignment());
bufStart = &buffer[tapCount];
window->createTaps(taps, tapCount);
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
bufMtx.lock();
memcpy(bufStart, _in->readBuf, count * sizeof(complex_t));
_in->flush();
for (int i = 0; i < count; i++) {
volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&pilotOut.writeBuf[i], (lv_32fc_t*)&buffer[i+1], (lv_32fc_t*)taps, tapCount);
}
memcpy(dataOut.writeBuf, &buffer[tapCount - ((tapCount-1)/2)], count * sizeof(complex_t));
if (!pilotOut.swap(count) || !dataOut.swap(count)) {
bufMtx.unlock();
return -1;
}
memmove(buffer, &buffer[count], tapCount * sizeof(complex_t));
bufMtx.unlock();
return count;
}
stream<complex_t> dataOut;
stream<complex_t> pilotOut;
private:
stream<complex_t>* _in;
dsp::filter_window::generic_complex_window* _window;
std::mutex bufMtx;
complex_t* bufStart;
complex_t* buffer;
int tapCount;
complex_t* taps;
};
class FMStereoDemux: public generic_block<FMStereoDemux> {
public:
FMStereoDemux() {}
FMStereoDemux(stream<complex_t>* data, stream<complex_t>* pilot, float loopBandwidth) { init(data, pilot, loopBandwidth); }
void init(stream<complex_t>* data, stream<complex_t>* pilot, float loopBandwidth) {
_data = data;
_pilot = pilot;
lastVCO.re = 1.0f;
lastVCO.im = 0.0f;
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<FMStereoDemux>::registerInput(_data);
generic_block<FMStereoDemux>::registerInput(_pilot);
generic_block<FMStereoDemux>::registerOutput(&AplusBOut);
generic_block<FMStereoDemux>::registerOutput(&AminusBOut);
generic_block<FMStereoDemux>::_block_init = true;
}
void setInput(stream<complex_t>* data, stream<complex_t>* pilot) {
assert(generic_block<FMStereoDemux>::_block_init);
generic_block<FMStereoDemux>::tempStop();
generic_block<FMStereoDemux>::unregisterInput(_data);
generic_block<FMStereoDemux>::unregisterInput(_pilot);
_data = data;
_pilot = pilot;
generic_block<FMStereoDemux>::registerInput(_data);
generic_block<FMStereoDemux>::registerInput(_pilot);
generic_block<FMStereoDemux>::tempStart();
}
void setLoopBandwidth(float loopBandwidth) {
assert(generic_block<FMStereoDemux>::_block_init);
generic_block<FMStereoDemux>::tempStop();
_loopBandwidth = loopBandwidth;
float dampningFactor = sqrtf(2.0f) / 2.0f;
float denominator = (1.0 + 2.0 * dampningFactor * _loopBandwidth + _loopBandwidth * _loopBandwidth);
_alpha = (4 * dampningFactor * _loopBandwidth) / denominator;
_beta = (4 * _loopBandwidth * _loopBandwidth) / denominator;
generic_block<FMStereoDemux>::tempStart();
}
int run() {
int count = _data->read();
if (count < 0) { return -1; }
int pCount = _pilot->read();
if (pCount < 0) { return -1; }
complex_t doubledVCO;
float error;
volk_32fc_deinterleave_real_32f(AplusBOut.writeBuf, (lv_32fc_t*)_data->readBuf, count);
for (int i = 0; i < count; i++) {
// Double the VCO, then mix it with the input data.
// IMPORTANT: THERE SHOULDN'T BE A NEED FOR A GAIN HERE
doubledVCO = lastVCO*lastVCO;
AminusBOut.writeBuf[i] = (_data->readBuf[i].re * doubledVCO.re) * 2.0f;
// Calculate the phase error estimation
error = _pilot->readBuf[i].phase() - vcoPhase;
if (error > 3.1415926535f) { error -= 2.0f * 3.1415926535f; }
else if (error <= -3.1415926535f) { error += 2.0f * 3.1415926535f; }
// Integrate frequency and clamp it
vcoFrequency += _beta * error;
if (vcoFrequency > upperLimit) { vcoFrequency = upperLimit; }
else if (vcoFrequency < lowerLimit) { vcoFrequency = lowerLimit; }
// Calculate new phase and wrap it
vcoPhase += vcoFrequency + (_alpha * error);
while (vcoPhase > (2.0f * FL_M_PI)) { vcoPhase -= (2.0f * FL_M_PI); }
while (vcoPhase < (-2.0f * FL_M_PI)) { vcoPhase += (2.0f * FL_M_PI); }
// Calculate output
lastVCO.re = cosf(vcoPhase);
lastVCO.im = sinf(vcoPhase);
}
_data->flush();
_pilot->flush();
if (!AplusBOut.swap(count)) { return -1; }
if (!AminusBOut.swap(count)) { return -1; }
return count;
}
stream<float> AplusBOut;
stream<float> AminusBOut;
float gain = 2.0f;
private:
float _loopBandwidth = 0.01f;
const float expectedFreq = ((19000.0f / 250000.0f) * 2.0f * FL_M_PI);
const float upperLimit = ((19200.0f / 250000.0f) * 2.0f * FL_M_PI);
const float lowerLimit = ((18800.0f / 250000.0f) * 2.0f * FL_M_PI);
float _alpha; // Integral coefficient
float _beta; // Proportional coefficient
float vcoFrequency = expectedFreq;
float vcoPhase = 0.0f;
complex_t lastVCO;
stream<complex_t>* _data;
stream<complex_t>* _pilot;
};
class FMStereoReconstruct : public generic_block<FMStereoReconstruct> {
public:
FMStereoReconstruct() {}
FMStereoReconstruct(stream<float>* a, stream<float>* b) { init(a, b); }
~FMStereoReconstruct() {
generic_block<FMStereoReconstruct>::stop();
delete[] leftBuf;
delete[] rightBuf;
}
void init(stream<float>* aplusb, stream<float>* aminusb) {
_aplusb = aplusb;
_aminusb = aminusb;
leftBuf = new float[STREAM_BUFFER_SIZE];
rightBuf = new float[STREAM_BUFFER_SIZE];
generic_block<FMStereoReconstruct>::registerInput(aplusb);
generic_block<FMStereoReconstruct>::registerInput(aminusb);
generic_block<FMStereoReconstruct>::registerOutput(&out);
generic_block<FMStereoReconstruct>::_block_init = true;
}
void setInputs(stream<float>* aplusb, stream<float>* aminusb) {
assert(generic_block<FMStereoReconstruct>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<FMStereoReconstruct>::ctrlMtx);
generic_block<FMStereoReconstruct>::tempStop();
generic_block<FMStereoReconstruct>::unregisterInput(_aplusb);
generic_block<FMStereoReconstruct>::unregisterInput(_aminusb);
_aplusb = aplusb;
_aminusb = aminusb;
generic_block<FMStereoReconstruct>::registerInput(_aplusb);
generic_block<FMStereoReconstruct>::registerInput(_aminusb);
generic_block<FMStereoReconstruct>::tempStart();
}
int run() {
int a_count = _aplusb->read();
if (a_count < 0) { return -1; }
int b_count = _aminusb->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_aplusb->flush();
_aminusb->flush();
return 0;
}
volk_32f_x2_add_32f(rightBuf, _aplusb->readBuf, _aminusb->readBuf, a_count);
volk_32f_x2_subtract_32f(leftBuf, _aplusb->readBuf, _aminusb->readBuf, a_count);
_aplusb->flush();
_aminusb->flush();
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, leftBuf, rightBuf, a_count);
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<stereo_t> out;
private:
stream<float>* _aplusb;
stream<float>* _aminusb;
float* leftBuf;
float* rightBuf;
};
}

126
core/src/dsp/stream.h Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <mutex>
#include <condition_variable>
#include <volk/volk.h>
// 1MB buffer
#define STREAM_BUFFER_SIZE 1000000
namespace dsp {
class untyped_stream {
public:
virtual bool swap(int size) { return false; }
virtual int read() { return -1; }
virtual void flush() {}
virtual void stopWriter() {}
virtual void clearWriteStop() {}
virtual void stopReader() {}
virtual void clearReadStop() {}
};
template <class T>
class stream : public untyped_stream {
public:
stream() {
writeBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
readBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());
}
~stream() {
volk_free(writeBuf);
volk_free(readBuf);
}
bool swap(int size) {
{
// Wait to either swap or stop
std::unique_lock<std::mutex> lck(swapMtx);
swapCV.wait(lck, [this]{ return (canSwap || writerStop); });
// If writer was stopped, abandon operation
if (writerStop) { return false; }
// Swap buffers
dataSize = size;
T* temp = writeBuf;
writeBuf = readBuf;
readBuf = temp;
canSwap = false;
}
// Notify reader that some data is ready
{
std::lock_guard<std::mutex> lck(rdyMtx);
dataReady = true;
}
rdyCV.notify_all();
return true;
}
int read() {
// Wait for data to be ready or to be stopped
std::unique_lock<std::mutex> lck(rdyMtx);
rdyCV.wait(lck, [this]{ return (dataReady || readerStop); });
return (readerStop ? -1 : dataSize);
}
void flush() {
// Clear data ready
{
std::lock_guard<std::mutex> lck(rdyMtx);
dataReady = false;
}
// Notify writer that buffers can be swapped
{
std::lock_guard<std::mutex> lck(swapMtx);
canSwap = true;
}
swapCV.notify_all();
}
void stopWriter() {
{
std::lock_guard<std::mutex> lck(swapMtx);
writerStop = true;
}
swapCV.notify_all();
}
void clearWriteStop() {
writerStop = false;
}
void stopReader() {
{
std::lock_guard<std::mutex> lck(rdyMtx);
readerStop = true;
}
rdyCV.notify_all();
}
void clearReadStop() {
readerStop = false;
}
T* writeBuf;
T* readBuf;
private:
std::mutex swapMtx;
std::condition_variable swapCV;
bool canSwap = true;
std::mutex rdyMtx;
std::condition_variable rdyCV;
bool dataReady = false;
bool readerStop = false;
bool writerStop = false;
int dataSize = 0;
};
}

84
core/src/dsp/types.h Normal file
View File

@ -0,0 +1,84 @@
#pragma once
#include <math.h>
#include <dsp/utils/math.h>
namespace dsp {
struct complex_t {
complex_t operator*(const float b) {
return complex_t{re*b, im*b};
}
complex_t operator/(const float b) {
return complex_t{re/b, im/b};
}
complex_t operator*(const complex_t& b) {
return complex_t{(re*b.re) - (im*b.im), (im*b.re) + (re*b.im)};
}
complex_t operator+(const complex_t& b) {
return complex_t{re+b.re, im+b.im};
}
complex_t operator-(const complex_t& b) {
return complex_t{re-b.re, im-b.im};
}
inline complex_t conj() {
return complex_t{re, -im};
}
inline float phase() {
return atan2f(im, re);
}
inline float fastPhase() {
float abs_im = fabsf(im);
float r, angle;
if (re == 0.0f && im == 0.0f) { return 0.0f; }
if (re>=0.0f) {
r = (re - abs_im) / (re + abs_im);
angle = (FL_M_PI / 4.0f) - (FL_M_PI / 4.0f) * r;
}
else {
r = (re + abs_im) / (abs_im - re);
angle = (3.0f * (FL_M_PI / 4.0f)) - (FL_M_PI / 4.0f) * r;
}
if (im < 0.0f) {
return -angle;
}
return angle;
}
inline float amplitude() {
return sqrt((re*re) + (im*im));
}
inline float fastAmplitude() {
float re_abs = fabsf(re);
float im_abs = fabsf(re);
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
return im_abs + 0.4f * re_abs;
}
float re;
float im;
};
struct stereo_t {
stereo_t operator*(const float b) {
return stereo_t{l*b, r*b};
}
stereo_t operator+(const stereo_t& b) {
return stereo_t{l+b.l, r+b.r};
}
stereo_t operator-(const stereo_t& b) {
return stereo_t{l-b.l, r-b.r};
}
float l;
float r;
};
}

View File

@ -0,0 +1,43 @@
#pragma once
namespace dsp {
inline uint64_t readBits(int offset, int length, uint8_t* buffer) {
uint64_t outputValue = 0;
int lastBit = offset + (length - 1);
int firstWord = offset / 8;
int lastWord = lastBit / 8;
int firstOffset = offset - (firstWord * 8);
int lastOffset = lastBit - (lastWord * 8);
int wordCount = (lastWord - firstWord) + 1;
// If the data fits in a single byte, just get it
if (wordCount == 1) {
return (buffer[firstWord] & (0xFF >> firstOffset)) >> (7 - lastOffset);
}
int bitsRead = length;
for (int i = 0; i < wordCount; i++) {
// First word
if (i == 0) {
bitsRead -= 8 - firstOffset;
outputValue |= (uint64_t)(buffer[firstWord] & (0xFF >> firstOffset)) << bitsRead;
continue;
}
// Last word
if (i == (wordCount - 1)) {
outputValue |= (uint64_t)buffer[lastWord] >> (7 - lastOffset);
break;
}
// Just a normal byte
bitsRead -= 8;
outputValue |= (uint64_t)buffer[firstWord + i] << bitsRead;
}
return outputValue;
}
}

121
core/src/dsp/utils/ccsds.h Normal file
View File

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

View File

@ -0,0 +1,6 @@
#pragma once
#include <dsp/types.h>
#define DSP_SIGN(n) ((n) >= 0)
#define DSP_STEP_CPLX(c) (complex_t{(c.re > 0.0f) ? 1.0f : -1.0f, (c.im > 0.0f) ? 1.0f : -1.0f})
#define DSP_STEP(n) (((n) > 0.0f) ? 1.0f : -1.0f)

12
core/src/dsp/utils/math.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <math.h>
#define FL_M_PI 3.1415926535f
namespace dsp {
namespace math {
inline double sinc(double omega, double x, double norm) {
return (x == 0.0f) ? 1.0f : (sin(omega*x)/(norm*x));
}
}
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <dsp/types.h>
namespace dsp {
namespace window_function {
inline double blackman(double n, double N, double alpha = 0.16f) {
double a0 = (1.0f-alpha) / 2.0f;
double a2 = alpha / 2.0f;
return a0 - (0.5f*cos(2.0f*FL_M_PI*(n/N))) + (a2*cos(4.0f*FL_M_PI*(n/N)));
}
inline double blackmanThirdOrder(double n, double N, double a0, double a1, double a2, double a3) {
return a0 - (a1*cos(2.0f*FL_M_PI*(n/N))) + (a2*cos(4.0f*FL_M_PI*(n/N))) - (a3*cos(6.0f*FL_M_PI*(n/N)));
}
inline double nuttall(double n, double N) {
return blackmanThirdOrder(n, N, 0.3635819f, 0.4891775f, 0.1365995f, 0.0106411f);
}
inline double blackmanNuttall(double n, double N) {
return blackmanThirdOrder(n, N, 0.3635819f, 0.4891775f, 0.1365995f, 0.0106411f);
}
inline double blackmanHarris(double n, double N) {
return blackmanThirdOrder(n, N, 0.35875f, 0.48829f, 0.14128f, 0.01168f);
}
}
}

126
core/src/dsp/vfo.h Normal file
View File

@ -0,0 +1,126 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <algorithm>
namespace dsp {
class VFO {
public:
VFO() {}
~VFO() {
if (!_init) { return; }
stop();
_init = false;
}
VFO(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
init(in, offset, inSampleRate, outSampleRate, bandWidth);
};
void init(stream<complex_t>* in, float offset, float inSampleRate, float outSampleRate, float bandWidth) {
_in = in;
_offset = offset;
_inSampleRate = inSampleRate;
_outSampleRate = outSampleRate;
_bandWidth = bandWidth;
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
xlator.init(_in, _inSampleRate, -_offset);
win.init(realCutoff, realCutoff, inSampleRate);
resamp.init(&xlator.out, &win, _inSampleRate, _outSampleRate);
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
resamp.updateWindow(&win);
out = &resamp.out;
_init = true;
}
void start() {
assert(_init);
if (running) { return; }
xlator.start();
resamp.start();
}
void stop() {
assert(_init);
if (!running) { return; }
xlator.stop();
resamp.stop();
}
void setInSampleRate(float inSampleRate) {
assert(_init);
_inSampleRate = inSampleRate;
if (running) { xlator.stop(); resamp.stop(); }
xlator.setSampleRate(_inSampleRate);
resamp.setInSampleRate(_inSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { xlator.start(); resamp.start(); }
}
void setOutSampleRate(float outSampleRate) {
assert(_init);
_outSampleRate = outSampleRate;
if (running) { resamp.stop(); }
resamp.setOutSampleRate(_outSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { resamp.start(); }
}
void setOutSampleRate(float outSampleRate, float bandWidth) {
assert(_init);
_outSampleRate = outSampleRate;
_bandWidth = bandWidth;
if (running) { resamp.stop(); }
resamp.setOutSampleRate(_outSampleRate);
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setSampleRate(_inSampleRate * resamp.getInterpolation());
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
if (running) { resamp.start(); }
}
void setOffset(float offset) {
assert(_init);
_offset = offset;
xlator.setFrequency(-_offset);
}
void setBandwidth(float bandWidth) {
assert(_init);
_bandWidth = bandWidth;
float realCutoff = std::min<float>(_bandWidth, std::min<float>(_inSampleRate, _outSampleRate)) / 2.0f;
win.setCutoff(realCutoff);
win.setTransWidth(realCutoff);
resamp.updateWindow(&win);
}
stream<complex_t>* out;
private:
bool _init = false;
bool running = false;
float _offset, _inSampleRate, _outSampleRate, _bandWidth;
filter_window::BlackmanWindow win;
stream<complex_t>* _in;
FrequencyXlator<complex_t> xlator;
PolyphaseResampler<complex_t> resamp;
};
}

272
core/src/dsp/window.h Normal file
View File

@ -0,0 +1,272 @@
#pragma once
#include <dsp/block.h>
#include <dsp/types.h>
#include <dsp/utils/window_functions.h>
namespace dsp {
namespace filter_window {
class generic_window {
public:
virtual int getTapCount() { return -1; }
virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {}
};
class generic_complex_window {
public:
virtual int getTapCount() { return -1; }
virtual void createTaps(dsp::complex_t* taps, int tapCount, float factor = 1.0f) {}
};
class BlackmanWindow : public filter_window::generic_window {
public:
BlackmanWindow() {}
BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); }
void init(float cutoff, float transWidth, float sampleRate) {
_cutoff = cutoff;
_transWidth = transWidth;
_sampleRate = sampleRate;
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
}
void setCutoff(float cutoff) {
_cutoff = cutoff;
}
void setTransWidth(float transWidth) {
_transWidth = transWidth;
}
int getTapCount() {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
int _M = 4.0f / (_transWidth / _sampleRate);
if (_M < 4) {
_M = 4;
}
if (_M % 2 == 0) { _M++; }
return _M;
}
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
// Calculate cuttoff frequency
float omega = 2.0f * FL_M_PI * (_cutoff / _sampleRate);
if (omega > FL_M_PI) { omega = FL_M_PI; }
// Generate taps
float val;
float sum = 0.0f;
float tc = tapCount;
for (int i = 0; i < tapCount; i++) {
val = math::sinc(omega, (float)i - (tc/2), FL_M_PI) * window_function::blackman(i, tc - 1);
taps[i] = val;
sum += val;
}
// Normalize taps and multiply by supplied factor
for (int i = 0; i < tapCount; i++) {
taps[i] *= factor;
taps[i] /= sum;
}
}
private:
float _cutoff, _transWidth, _sampleRate;
};
class BandPassBlackmanWindow : public filter_window::generic_complex_window {
public:
BandPassBlackmanWindow() {}
BandPassBlackmanWindow(float lowCutoff, float highCutoff, float transWidth, float sampleRate) { init(lowCutoff, highCutoff, transWidth, sampleRate); }
void init(float lowCutoff, float highCutoff, float transWidth, float sampleRate) {
assert(lowCutoff <= highCutoff);
_lowCutoff = lowCutoff;
_highCutoff = highCutoff;
_transWidth = transWidth;
_sampleRate = sampleRate;
// Calculate other values
_offset = (_lowCutoff + _highCutoff) / 2.0f;
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
}
void setCutoffs(float lowCutoff, float highCutoff) {
assert(lowCutoff <= highCutoff);
_lowCutoff = lowCutoff;
_highCutoff = highCutoff;
// Calculate other values
_offset = (_lowCutoff + _highCutoff) / 2.0f;
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
}
void setLowCutoff(float lowCutoff) {
assert(lowCutoff <= _highCutoff);
_lowCutoff = lowCutoff;
// Calculate other values
_offset = (_lowCutoff + _highCutoff) / 2.0f;
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
}
void setHighCutoff(float highCutoff) {
assert(_lowCutoff <= highCutoff);
_highCutoff = highCutoff;
// Calculate other values
_offset = (_lowCutoff + _highCutoff) / 2.0f;
_cutoff = fabs((_highCutoff - _lowCutoff) / 2.0f);
}
void setTransWidth(float transWidth) {
_transWidth = transWidth;
}
int getTapCount() {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
int _M = 4.0f / (_transWidth / _sampleRate);
if (_M < 4) {
_M = 4;
}
if (_M % 2 == 0) { _M++; }
return _M;
}
void createTaps(dsp::complex_t* taps, int tapCount, float factor = 1.0f) {
// Calculate cuttoff frequency
float omega = 2.0f * FL_M_PI * (_cutoff / _sampleRate);
if (omega > FL_M_PI) { omega = FL_M_PI; }
// Generate taps
float val;
float sum = 0.0f;
float tc = tapCount;
for (int i = 0; i < tapCount; i++) {
val = math::sinc(omega, (float)i - (tc/2), FL_M_PI) * window_function::blackman(i, tc - 1);
taps[i].re = val;
taps[i].im = 0;
sum += val;
}
// Normalize taps and multiply by supplied factor
for (int i = 0; i < tapCount; i++) {
taps[i] = taps[i] * factor;
taps[i] = taps[i] / sum;
}
// Add offset
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
lv_32fc_t phaseDelta = lv_cmake(std::cos((-_offset / _sampleRate) * 2.0f * FL_M_PI), std::sin((-_offset / _sampleRate) * 2.0f * FL_M_PI));
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)taps, (lv_32fc_t*)taps, phaseDelta, &phase, tapCount);
}
private:
float _lowCutoff, _highCutoff;
float _cutoff, _transWidth, _sampleRate, _offset;
};
}
class RRCTaps : public filter_window::generic_window {
public:
RRCTaps() {}
RRCTaps(int tapCount, float sampleRate, float baudRate, float alpha) { init(tapCount, sampleRate, baudRate, alpha); }
void init(int tapCount, float sampleRate, float baudRate, float alpha) {
_tapCount = tapCount;
_sampleRate = sampleRate;
_baudRate = baudRate;
_alpha = alpha;
}
int getTapCount() {
return _tapCount;
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
}
void setBaudRate(float baudRate) {
_baudRate = baudRate;
}
void setTapCount(int count) {
_tapCount = count;
}
void setAlpha(float alpha) {
_alpha = alpha;
}
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
// ======== CREDIT: GNU Radio =========
tapCount |= 1; // ensure that tapCount is odd
double spb = _sampleRate / _baudRate; // samples per bit/symbol
double scale = 0;
for (int i = 0; i < tapCount; i++)
{
double x1, x2, x3, num, den;
double xindx = i - tapCount / 2;
x1 = FL_M_PI * xindx / spb;
x2 = 4 * _alpha * xindx / spb;
x3 = x2 * x2 - 1;
// Avoid Rounding errors...
if (fabs(x3) >= 0.000001) {
if (i != tapCount / 2)
num = cos((1 + _alpha) * x1) +
sin((1 - _alpha) * x1) / (4 * _alpha * xindx / spb);
else
num = cos((1 + _alpha) * x1) + (1 - _alpha) * FL_M_PI / (4 * _alpha);
den = x3 * FL_M_PI;
}
else {
if (_alpha == 1)
{
taps[i] = -1;
scale += taps[i];
continue;
}
x3 = (1 - _alpha) * x1;
x2 = (1 + _alpha) * x1;
num = (sin(x2) * (1 + _alpha) * FL_M_PI -
cos(x3) * ((1 - _alpha) * FL_M_PI * spb) / (4 * _alpha * xindx) +
sin(x3) * spb * spb / (4 * _alpha * xindx * xindx));
den = -32 * FL_M_PI * _alpha * _alpha * xindx / spb;
}
taps[i] = 4 * _alpha * num / den;
scale += taps[i];
}
for (int i = 0; i < tapCount; i++) {
taps[i] = taps[i] / scale;
}
}
private:
int _tapCount;
float _sampleRate, _baudRate, _alpha;
};
}

6
core/src/glfw_window.h Normal file
View File

@ -0,0 +1,6 @@
#include <GLFW/glfw3.h>
#include <module.h>
namespace core {
SDRPP_EXPORT GLFWwindow* window;
};

View File

@ -0,0 +1,49 @@
#include <gui/colormaps.h>
#include <filesystem>
#include <spdlog/spdlog.h>
#include <fstream>
#include <json.hpp>
using nlohmann::json;
namespace colormaps {
std::map<std::string, Map> maps;
void loadMap(std::string path) {
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Could not load {0}, file doesn't exist", path);
return;
}
std::ifstream file(path.c_str());
json data;
file >> data;
file.close();
Map map;
std::vector<std::string> mapTxt;
try {
map.name = data["name"];
map.author = data["author"];
mapTxt = data["map"].get<std::vector<std::string>>();
}
catch (const std::exception&) {
spdlog::error("Could not load {0}", path);
return;
}
map.entryCount = mapTxt.size();
map.map = new float[mapTxt.size() * 3];
int i = 0;
for(auto const& col : mapTxt) {
uint8_t r, g, b, a;
map.map[i * 3] = std::stoi(col.substr(1, 2), NULL, 16);
map.map[(i * 3) + 1] = std::stoi(col.substr(3, 2), NULL, 16);
map.map[(i * 3) + 2] = std::stoi(col.substr(5, 2), NULL, 16);
i++;
}
maps[map.name] = map;
}
}

18
core/src/gui/colormaps.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <vector>
#include <module.h>
#include <map>
namespace colormaps {
struct Map {
std::string name;
std::string author;
float* map;
int entryCount;
};
void loadMap(std::string path);
SDRPP_EXPORT std::map<std::string, Map> maps;
}

View File

@ -0,0 +1,66 @@
#include <gui/dialogs/credits.h>
#include <imgui.h>
#include <gui/icons.h>
#include <gui/style.h>
#include <config.h>
#include <credits.h>
#include <version.h>
namespace credits {
ImFont* bigFont;
void init() {
}
void show() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0,0,0,0));
ImVec2 dispSize = ImGui::GetIO().DisplaySize;
ImVec2 center = ImVec2(dispSize.x/2.0f, dispSize.y/2.0f);
ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::OpenPopup("Credits");
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
ImGui::PushFont(style::hugeFont);
ImGui::Text("SDR++ ");
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("This software is brought to you by Alexandre Rouma with the help of\n\n");
ImGui::Columns(3, "CreditColumns", true);
ImGui::Text("Contributors");
for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
}
ImGui::NextColumn();
ImGui::Text("Libraries");
for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
}
ImGui::NextColumn();
ImGui::Text("Patrons");
for (int i = 0; i < sdrpp_credits::patronCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
}
ImGui::Columns(1, "CreditColumnsEnd", true);
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")");
ImGui::EndPopup();
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace credits {
void init();
void show();
}

View File

@ -0,0 +1,89 @@
#include <GL/glew.h>
#include <gui/dialogs/loading_screen.h>
#include <gui/main_window.h>
#include <imgui.h>
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <gui/icons.h>
#include <gui/style.h>
#include <credits.h>
#include <gui/gui.h>
namespace LoadingScreen {
GLFWwindow* _win;
void setWindow(GLFWwindow* win) {
_win = win;
}
void show(std::string msg) {
glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::OpenPopup("Credits");
ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground);
ImGui::PushFont(style::hugeFont);
ImGui::Text("SDR++ ");
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
// ImGui::Spacing();
// ImGui::Spacing();
// ImGui::Spacing();
// ImGui::Text("This software is brought to you by\n\n");
// ImGui::Columns(3, "CreditColumns", true);
// ImGui::Text("Contributors");
// for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
// ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
// }
// ImGui::NextColumn();
// ImGui::Text("Libraries");
// for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
// ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
// }
// ImGui::NextColumn();
// ImGui::Text("Patrons");
// for (int i = 0; i < sdrpp_credits::patronCount; i++) {
// ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
// }
// ImGui::Columns(1, "CreditColumnsEnd", true);
// ImGui::Spacing();
// ImGui::Spacing();
// ImGui::Spacing();
ImVec2 origPos = ImGui::GetCursorPos();
ImGui::SetCursorPosY(origPos.y + 50);
ImGui::Text("%s", msg.c_str());
ImGui::SetCursorPos(origPos);
ImGui::EndPopup();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor(1);
ImGui::End();
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(_win, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClearColor(gui::themeManager.clearColor.x, gui::themeManager.clearColor.y, gui::themeManager.clearColor.z, gui::themeManager.clearColor.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(_win);
}
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <thread>
#include <string>
#include <mutex>
#include <GLFW/glfw3.h>
namespace LoadingScreen {
void setWindow(GLFWwindow* win);
void show(std::string msg);
};

1709
core/src/gui/file_dialogs.h Normal file

File diff suppressed because it is too large Load Diff

9
core/src/gui/gui.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <gui/gui.h>
namespace gui {
MainWindow mainWindow;
ImGui::WaterFall waterfall;
FrequencySelect freqSelect;
ThemeManager themeManager;
Menu menu;
};

18
core/src/gui/gui.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <gui/widgets/waterfall.h>
#include <gui/widgets/frequency_select.h>
#include <gui/widgets/menu.h>
#include <gui/dialogs/loading_screen.h>
#include <module.h>
#include <gui/main_window.h>
#include <gui/theme_manager.h>
namespace gui {
SDRPP_EXPORT ImGui::WaterFall waterfall;
SDRPP_EXPORT FrequencySelect freqSelect;
SDRPP_EXPORT Menu menu;
SDRPP_EXPORT ThemeManager themeManager;
SDRPP_EXPORT MainWindow mainWindow;
void selectSource(std::string name);
};

53
core/src/gui/icons.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <gui/icons.h>
#include <stdint.h>
#include <GL/glew.h>
#include <config.h>
#include <options.h>
#define STB_IMAGE_IMPLEMENTATION
#include <imgui/stb_image.h>
#include <filesystem>
#include <spdlog/spdlog.h>
namespace icons {
ImTextureID LOGO;
ImTextureID PLAY;
ImTextureID STOP;
ImTextureID MENU;
ImTextureID MUTED;
ImTextureID UNMUTED;
ImTextureID NORMAL_TUNING;
ImTextureID CENTER_TUNING;
GLuint loadTexture(std::string path) {
int w,h,n;
stbi_uc* data = stbi_load(path.c_str(), &w, &h, &n, 0);
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)data);
stbi_image_free(data);
return texId;
}
bool load(std::string resDir) {
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Inavlid resource directory: {0}", resDir);
return false;
}
LOGO = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/sdrpp.png");
PLAY = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/play.png");
STOP = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/stop.png");
MENU = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/menu.png");
MUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/muted.png");
UNMUTED = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/unmuted.png");
NORMAL_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/normal_tuning.png");
CENTER_TUNING = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/center_tuning.png");
return true;
}
}

18
core/src/gui/icons.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <imgui/imgui.h>
#include <GL/glew.h>
#include <string>
namespace icons {
extern ImTextureID LOGO;
extern ImTextureID PLAY;
extern ImTextureID STOP;
extern ImTextureID MENU;
extern ImTextureID MUTED;
extern ImTextureID UNMUTED;
extern ImTextureID NORMAL_TUNING;
extern ImTextureID CENTER_TUNING;
GLuint loadTexture(std::string path);
bool load(std::string resDir);
}

View File

@ -0,0 +1,676 @@
#include <gui/main_window.h>
#include <gui/gui.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <thread>
#include <complex>
#include <gui/widgets/waterfall.h>
#include <gui/widgets/frequency_select.h>
#include <signal_path/dsp.h>
#include <gui/icons.h>
#include <gui/widgets/bandplan.h>
#include <gui/style.h>
#include <config.h>
#include <signal_path/signal_path.h>
#include <core.h>
#include <gui/menus/source.h>
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/sink.h>
#include <gui/menus/vfo_color.h>
#include <gui/menus/module_manager.h>
#include <gui/menus/theme.h>
#include <gui/dialogs/credits.h>
#include <filesystem>
#include <signal_path/source.h>
#include <gui/dialogs/loading_screen.h>
#include <options.h>
#include <gui/colormaps.h>
#include <gui/widgets/snr_meter.h>
#include <gui/tuner.h>
void MainWindow::init() {
LoadingScreen::show("Initializing UI");
gui::waterfall.init();
gui::waterfall.setRawFFTSize(fftSize);
credits::init();
core::configManager.acquire();
json menuElements = core::configManager.conf["menuElements"];
std::string modulesDir = core::configManager.conf["modulesDirectory"];
std::string resourcesDir = core::configManager.conf["resourcesDirectory"];
core::configManager.release();
// Load menu elements
gui::menu.order.clear();
for (auto& elem : menuElements) {
if (!elem.contains("name")) { spdlog::error("Menu element is missing name key"); continue; }
if (!elem["name"].is_string()) { spdlog::error("Menu element name isn't a string"); continue; }
if (!elem.contains("open")) { spdlog::error("Menu element is missing open key"); continue; }
if (!elem["open"].is_boolean()) { spdlog::error("Menu element name isn't a string"); continue; }
Menu::MenuOption_t opt;
opt.name = elem["name"];
opt.open = elem["open"];
gui::menu.order.push_back(opt);
}
gui::menu.registerEntry("Source", sourecmenu::draw, NULL);
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
gui::menu.registerEntry("VFO Color", vfo_color_menu::draw, NULL);
gui::menu.registerEntry("Module Manager", module_manager_menu::draw, NULL);
gui::freqSelect.init();
// Set default values for waterfall in case no source init's it
gui::waterfall.setBandwidth(8000000);
gui::waterfall.setViewBandwidth(8000000);
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fftwPlan = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
sigpath::signalPath.init(8000000, 20, fftSize, &dummyStream, (dsp::complex_t*)fft_in, fftHandler, this);
sigpath::signalPath.start();
vfoCreatedHandler.handler = vfoAddedHandler;
vfoCreatedHandler.ctx = this;
sigpath::vfoManager.onVfoCreated.bindHandler(&vfoCreatedHandler);
spdlog::info("Loading modules");
// Load modules from /module directory
if (std::filesystem::is_directory(modulesDir)) {
for (const auto & file : std::filesystem::directory_iterator(modulesDir)) {
std::string path = file.path().generic_string();
if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) {
continue;
}
if (!file.is_regular_file()) { continue; }
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
}
else {
spdlog::warn("Module directory {0} does not exist, not loading modules from directory", modulesDir);
}
// Read module config
core::configManager.acquire();
std::vector<std::string> modules = core::configManager.conf["modules"];
auto modList = core::configManager.conf["moduleInstances"].items();
core::configManager.release();
// Load additional modules specified through config
for (auto const& path : modules) {
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
// Create module instances
for (auto const& [name, _module] : modList) {
std::string mod = _module["module"];
bool enabled = _module["enabled"];
spdlog::info("Initializing {0} ({1})", name, mod);
LoadingScreen::show("Initializing " + name + " (" + mod + ")");
core::moduleManager.createInstance(name, mod);
if (!enabled) { core::moduleManager.disableInstance(name); }
}
// Load color maps
LoadingScreen::show("Loading color maps");
spdlog::info("Loading color maps");
if (std::filesystem::is_directory(resourcesDir + "/colormaps")) {
for (const auto & file : std::filesystem::directory_iterator(resourcesDir + "/colormaps")) {
std::string path = file.path().generic_string();
LoadingScreen::show("Loading " + path);
spdlog::info("Loading {0}", path);
if (file.path().extension().generic_string() != ".json") {
continue;
}
if (!file.is_regular_file()) { continue; }
colormaps::loadMap(path);
}
}
else {
spdlog::warn("Color map directory {0} does not exist, not loading modules from directory", modulesDir);
}
gui::waterfall.updatePalletteFromArray(colormaps::maps["Turbo"].map, colormaps::maps["Turbo"].entryCount);
sourecmenu::init();
sinkmenu::init();
bandplanmenu::init();
displaymenu::init();
vfo_color_menu::init();
module_manager_menu::init();
// TODO for 0.2.5
// Fix gain not updated on startup, soapysdr
// Update UI settings
LoadingScreen::show("Loading configuration");
core::configManager.acquire();
fftMin = core::configManager.conf["min"];
fftMax = core::configManager.conf["max"];
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMax(fftMax);
double frequency = core::configManager.conf["frequency"];
showMenu = core::configManager.conf["showMenu"];
startedWithMenuClosed = !showMenu;
gui::freqSelect.setFrequency(frequency);
gui::freqSelect.frequencyChanged = false;
sigpath::sourceManager.tune(frequency);
gui::waterfall.setCenterFrequency(frequency);
bw = gui::waterfall.getBandwidth();
gui::waterfall.vfoFreqChanged = false;
gui::waterfall.centerFreqMoved = false;
gui::waterfall.selectFirstVFO();
menuWidth = core::configManager.conf["menuWidth"];
newWidth = menuWidth;
fftHeight = core::configManager.conf["fftHeight"];
gui::waterfall.setFFTHeight(fftHeight);
tuningMode = core::configManager.conf["centerTuning"] ? tuner::TUNER_MODE_CENTER : tuner::TUNER_MODE_NORMAL;
core::configManager.release();
// Correct the offset of all VFOs so that they fit on the screen
float finalBwHalf = gui::waterfall.getBandwidth() / 2.0;
for (auto& [_name, _vfo] : gui::waterfall.vfos) {
if (_vfo->lowerOffset < -finalBwHalf) {
sigpath::vfoManager.setCenterOffset(_name, (_vfo->bandwidth/2)-finalBwHalf);
continue;
}
if (_vfo->upperOffset > finalBwHalf) {
sigpath::vfoManager.setCenterOffset(_name, finalBwHalf-(_vfo->bandwidth/2));
continue;
}
}
initComplete = true;
core::moduleManager.doPostInitAll();
}
void MainWindow::fftHandler(dsp::complex_t* samples, int count, void* ctx) {
MainWindow* _this = (MainWindow*)ctx;
std::lock_guard<std::mutex> lck(_this->fft_mtx);
// Apply window
volk_32fc_32f_multiply_32fc((lv_32fc_t*)_this->fft_in, (lv_32fc_t*)samples, sigpath::signalPath.fftTaps, count);
// Zero out the rest of the samples
if (count < _this->fftSize) {
memset(&_this->fft_in[count], 0, (_this->fftSize-count) * sizeof(dsp::complex_t));
}
// Execute FFT
fftwf_execute(_this->fftwPlan);
// Get the FFT buffer
float* fftBuf = gui::waterfall.getFFTBuffer();
if (fftBuf == NULL) {
gui::waterfall.pushFFT();
return;
}
// Take power of spectrum
volk_32fc_s32f_power_spectrum_32f(fftBuf, (lv_32fc_t*)_this->fft_out, _this->fftSize, _this->fftSize);
// Push back data
gui::waterfall.pushFFT();
}
void MainWindow::vfoAddedHandler(VFOManager::VFO* vfo, void* ctx) {
MainWindow* _this = (MainWindow*)ctx;
std::string name = vfo->getName();
core::configManager.acquire();
if (!core::configManager.conf["vfoOffsets"].contains(name)) {
core::configManager.release();
return;
}
double offset = core::configManager.conf["vfoOffsets"][name];
core::configManager.release();
double viewBW = gui::waterfall.getViewBandwidth();
double viewOffset = gui::waterfall.getViewOffset();
double viewLower = viewOffset - (viewBW/2.0);
double viewUpper = viewOffset + (viewBW/2.0);
double newOffset = std::clamp<double>(offset, viewLower, viewUpper);
sigpath::vfoManager.setCenterOffset(name, _this->initComplete ? newOffset : offset);
}
void MainWindow::draw() {
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::WaterfallVFO* vfo = NULL;
if (gui::waterfall.selectedVFO != "") {
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
}
// Handle VFO movement
if (vfo != NULL) {
if (vfo->centerOffsetChanged) {
if (tuningMode == tuner::TUNER_MODE_CENTER) {
tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, gui::waterfall.getCenterFrequency() + vfo->generalOffset);
}
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
gui::freqSelect.frequencyChanged = false;
core::configManager.acquire();
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
core::configManager.release(true);
}
}
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
// Handle selection of another VFO
if (gui::waterfall.selectedVFOChanged) {
gui::freqSelect.setFrequency((vfo != NULL) ? (vfo->generalOffset + gui::waterfall.getCenterFrequency()) : gui::waterfall.getCenterFrequency());
gui::waterfall.selectedVFOChanged = false;
gui::freqSelect.frequencyChanged = false;
}
// Handle change in selected frequency
if (gui::freqSelect.frequencyChanged) {
gui::freqSelect.frequencyChanged = false;
tuner::tune(tuningMode, gui::waterfall.selectedVFO, gui::freqSelect.frequency);
if (vfo != NULL) {
vfo->centerOffsetChanged = false;
vfo->lowerOffsetChanged = false;
vfo->upperOffsetChanged = false;
}
core::configManager.acquire();
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
if (vfo != NULL) {
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
}
core::configManager.release(true);
}
// Handle dragging the frequency scale
if (gui::waterfall.centerFreqMoved) {
gui::waterfall.centerFreqMoved = false;
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
if (vfo != NULL) {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
}
else {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
}
core::configManager.acquire();
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
core::configManager.release(true);
}
int _fftHeight = gui::waterfall.getFFTHeight();
if (fftHeight != _fftHeight) {
fftHeight = _fftHeight;
core::configManager.acquire();
core::configManager.conf["fftHeight"] = fftHeight;
core::configManager.release(true);
}
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
int width = vMax.x - vMin.x;
int height = vMax.y - vMin.y;
// To Bar
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_MENU, false)) {
showMenu = !showMenu;
core::configManager.acquire();
core::configManager.conf["showMenu"] = showMenu;
core::configManager.release(true);
}
ImGui::PopID();
ImGui::SameLine();
bool tmpPlaySate = playing;
if (playButtonLocked && !tmpPlaySate) { style::beginDisabled(); }
if (playing) {
ImGui::PushID(ImGui::GetID("sdrpp_stop_btn"));
if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) {
playing = false;
onPlayStateChange.emit(false);
sigpath::sourceManager.stop();
sigpath::signalPath.inputBuffer.flush();
}
ImGui::PopID();
}
else { // TODO: Might need to check if there even is a device
ImGui::PushID(ImGui::GetID("sdrpp_play_btn"));
if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(GLFW_KEY_END, false)) {
sigpath::signalPath.inputBuffer.flush();
sigpath::sourceManager.start();
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
playing = true;
onPlayStateChange.emit(true);
}
ImGui::PopID();
}
if (playButtonLocked && !tmpPlaySate) { style::endDisabled(); }
ImGui::SameLine();
//ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true);
ImGui::SameLine();
gui::freqSelect.draw();
ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
if (tuningMode == tuner::TUNER_MODE_CENTER) {
ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn"));
if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
tuningMode = tuner::TUNER_MODE_NORMAL;
core::configManager.acquire();
core::configManager.conf["centerTuning"] = false;
core::configManager.release(true);
}
ImGui::PopID();
}
else { // TODO: Might need to check if there even is a device
ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn"));
if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
tuningMode = tuner::TUNER_MODE_CENTER;
tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, gui::freqSelect.frequency);
core::configManager.acquire();
core::configManager.conf["centerTuning"] = true;
core::configManager.release(true);
}
ImGui::PopID();
}
ImGui::SameLine();
int snrWidth = std::min<int>(300, ImGui::GetWindowSize().x - ImGui::GetCursorPosX() - 87);
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (snrWidth+87));
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5);
ImGui::SetNextItemWidth(snrWidth);
ImGui::SNRMeter((vfo != NULL) ? gui::waterfall.selectedVFOSNR : 0);
ImGui::SameLine();
// Logo button
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
ImGui::SetCursorPosY(10);
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
showCredits = true;
}
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
showCredits = false;
}
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
showCredits = false;
}
// Handle menu resize
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
if (!lockWaterfallControls) {
float curY = ImGui::GetCursorPosY();
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (grabbingMenu) {
newWidth = mousePos.x;
newWidth = std::clamp<float>(newWidth, 250, winSize.x - 250);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
if (click) {
grabbingMenu = true;
}
}
else {
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
}
if(!down && grabbingMenu) {
grabbingMenu = false;
menuWidth = newWidth;
core::configManager.acquire();
core::configManager.conf["menuWidth"] = menuWidth;
core::configManager.release(true);
}
}
// Left Column
lockWaterfallControls = false;
if (showMenu) {
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
ImGui::SetColumnWidth(2, 60);
ImGui::BeginChild("Left Column");
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
if (gui::menu.draw(firstMenuRender)) {
core::configManager.acquire();
json arr = json::array();
for (int i = 0; i < gui::menu.order.size(); i++) {
arr[i]["name"] = gui::menu.order[i].name;
arr[i]["open"] = gui::menu.order[i].open;
}
core::configManager.conf["menuElements"] = arr;
// Update enabled and disabled modules
for (auto [_name, inst] : core::moduleManager.instances) {
if (!core::configManager.conf["moduleInstances"].contains(_name)) { continue; }
core::configManager.conf["moduleInstances"][_name]["enabled"] = inst.instance->isEnabled();
}
core::configManager.release(true);
}
if (startedWithMenuClosed) {
startedWithMenuClosed = false;
}
else {
firstMenuRender = false;
}
if(ImGui::CollapsingHeader("Debug")) {
ImGui::Text("Frame time: %.3f ms/frame", 1000.0 / ImGui::GetIO().Framerate);
ImGui::Text("Framerate: %.1f FPS", ImGui::GetIO().Framerate);
ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency());
ImGui::Text("Source name: %s", sourceName.c_str());
ImGui::Checkbox("Show demo window", &demoWindow);
ImGui::Text("ImGui version: %s", ImGui::GetVersion());
ImGui::Checkbox("Bypass buffering", &sigpath::signalPath.inputBuffer.bypass);
ImGui::Text("Buffering: %d", (sigpath::signalPath.inputBuffer.writeCur - sigpath::signalPath.inputBuffer.readCur + 32) % 32);
if (ImGui::Button("Test Bug")) {
spdlog::error("Will this make the software crash?");
}
if (ImGui::Button("Testing something")) {
gui::menu.order[0].open = true;
firstMenuRender = true;
}
ImGui::Spacing();
}
ImGui::EndChild();
}
else {
// When hiding the menu bar
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, 8);
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
ImGui::SetColumnWidth(2, 60);
}
// Right Column
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGui::NextColumn();
ImGui::PopStyleVar();
ImGui::BeginChild("Waterfall");
gui::waterfall.draw();
ImGui::EndChild();
if (!lockWaterfallControls) {
// Handle arrow keys
if (vfo != NULL && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
if (ImGui::IsKeyPressed(GLFW_KEY_LEFT) && !gui::freqSelect.digitHovered) {
double nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset - vfo->snapInterval;
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
}
if (ImGui::IsKeyPressed(GLFW_KEY_RIGHT) && !gui::freqSelect.digitHovered) {
double nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + vfo->snapInterval;
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
}
core::configManager.acquire();
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
if (vfo != NULL) {
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
}
core::configManager.release(true);
}
// Handle scrollwheel
int wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
double nfreq;
if (vfo != NULL) {
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
}
else {
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);
}
tuner::tune(tuningMode, gui::waterfall.selectedVFO, nfreq);
gui::freqSelect.setFrequency(nfreq);
core::configManager.acquire();
core::configManager.conf["frequency"] = gui::waterfall.getCenterFrequency();
if (vfo != NULL) {
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
}
core::configManager.release(true);
}
}
ImGui::NextColumn();
ImGui::BeginChild("WaterfallControls");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0));
ImGui::Text("Zoom");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "")) {
gui::waterfall.setViewBandwidth(bw);
if (vfo != NULL) {
gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen
}
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0));
ImGui::Text("Max");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -160.0f, "")) {
fftMax = std::max<float>(fftMax, fftMin + 10);
core::configManager.acquire();
core::configManager.conf["max"] = fftMax;
core::configManager.release(true);
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0));
ImGui::Text("Min");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -160.0f, "")) {
fftMin = std::min<float>(fftMax - 10, fftMin);
core::configManager.acquire();
core::configManager.conf["min"] = fftMin;
core::configManager.release(true);
}
ImGui::EndChild();
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setWaterfallMax(fftMax);
ImGui::End();
if (showCredits) {
credits::show();
}
if (demoWindow) {
ImGui::ShowDemoWindow();
}
}
void MainWindow::setViewBandwidthSlider(float bandwidth) {
bw = bandwidth;
}
bool MainWindow::sdrIsRunning() {
return playing;
}
void MainWindow::setFFTSize(int size) {
std::lock_guard<std::mutex> lck(fft_mtx);
fftSize = size;
gui::waterfall.setRawFFTSize(fftSize);
sigpath::signalPath.setFFTSize(fftSize);
fftwf_free(fft_in);
fftwf_free(fft_out);
fftwf_destroy_plan(fftwPlan);
fft_in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fftwPlan = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
}
void MainWindow::setFFTWindow(int win) {
std::lock_guard<std::mutex> lck(fft_mtx);
sigpath::signalPath.setFFTWindow(win);
}
bool MainWindow::isPlaying() {
return playing;
}

View File

@ -0,0 +1,67 @@
#pragma once
#include <imgui/imgui.h>
#include <fftw3.h>
#include <dsp/types.h>
#include <dsp/stream.h>
#include <signal_path/vfo_manager.h>
#include <string>
#include <utils/event.h>
#include <mutex>
#include <gui/tuner.h>
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
class MainWindow {
public:
void init();
void draw();
void setViewBandwidthSlider(float bandwidth);
bool sdrIsRunning();
void setFFTSize(int size);
void setFFTWindow(int win);
// TODO: Replace with it's own class
void setVFO(double freq);
bool isPlaying();
bool lockWaterfallControls = false;
bool playButtonLocked = false;
Event<bool> onPlayStateChange;
private:
static void fftHandler(dsp::complex_t* samples, int count, void* ctx);
static void vfoAddedHandler(VFOManager::VFO* vfo, void* ctx);
// FFT Variables
int fftSize = 8192 * 8;
std::mutex fft_mtx;
fftwf_complex *fft_in, *fft_out;
fftwf_plan fftwPlan;
// GUI Variables
bool firstMenuRender = true;
bool startedWithMenuClosed = false;
float fftMin = -70.0;
float fftMax = 0.0;
float bw = 8000000;
bool playing = false;
bool showCredits = false;
std::string audioStreamName = "";
std::string sourceName = "";
int menuWidth = 300;
bool grabbingMenu = false;
int newWidth = 300;
int fftHeight = 300;
bool showMenu = true;
int tuningMode = tuner::TUNER_MODE_NORMAL;
dsp::stream<dsp::complex_t> dummyStream;
bool demoWindow = false;
int selectedWindow = 0;
bool initComplete = false;
EventHandler<VFOManager::VFO*> vfoCreatedHandler;
};

View File

@ -0,0 +1,67 @@
#include <gui/menus/bandplan.h>
#include <gui/widgets/bandplan.h>
#include <gui/gui.h>
#include <core.h>
namespace bandplanmenu {
int bandplanId;
bool bandPlanEnabled;
int bandPlanPos = 0;
const char* bandPlanPosTxt = "Bottom\0Top\0";
void init() {
// todo: check if the bandplan wasn't removed
if (bandplan::bandplanNames.size() == 0) {
gui::waterfall.hideBandplan();
return;
}
if (bandplan::bandplans.find(core::configManager.conf["bandPlan"]) != bandplan::bandplans.end()) {
std::string name = core::configManager.conf["bandPlan"];
bandplanId = std::distance(bandplan::bandplanNames.begin(), std::find(bandplan::bandplanNames.begin(),
bandplan::bandplanNames.end(), name));
gui::waterfall.bandplan = &bandplan::bandplans[name];
}
else {
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[0]];
}
bandPlanEnabled = core::configManager.conf["bandPlanEnabled"];
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
bandPlanPos = core::configManager.conf["bandPlanPos"];
gui::waterfall.setBandPlanPos(bandPlanPos);
}
void draw(void* ctx) {
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
ImGui::PushItemWidth(menuColumnWidth);
if (ImGui::Combo("##_bandplan_name_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
core::configManager.acquire();
core::configManager.conf["bandPlan"] = bandplan::bandplanNames[bandplanId];
core::configManager.release(true);
}
ImGui::PopItemWidth();
ImGui::Text("Position");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuColumnWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##_bandplan_pos_", &bandPlanPos, bandPlanPosTxt)) {
gui::waterfall.setBandPlanPos(bandPlanPos);
core::configManager.acquire();
core::configManager.conf["bandPlanPos"] = bandPlanPos;
core::configManager.release(true);
}
if (ImGui::Checkbox("Enabled", &bandPlanEnabled)) {
bandPlanEnabled ? gui::waterfall.showBandplan() : gui::waterfall.hideBandplan();
core::configManager.acquire();
core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled;
core::configManager.release(true);
}
bandplan::BandPlan_t plan = bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
ImGui::Text("Country: %s (%s)", plan.countryName.c_str(), plan.countryCode.c_str());
ImGui::Text("Author: %s", plan.authorName.c_str());
}
};

View File

@ -0,0 +1,6 @@
#pragma once
namespace bandplanmenu {
void init();
void draw(void* ctx);
};

View File

@ -0,0 +1,156 @@
#include <gui/menus/display.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
#include <gui/colormaps.h>
#include <gui/gui.h>
#include <gui/main_window.h>
#include <signal_path/signal_path.h>
namespace displaymenu {
bool showWaterfall;
bool fastFFT = true;
bool fullWaterfallUpdate = true;
int colorMapId = 0;
std::vector<std::string> colorMapNames;
std::string colorMapNamesTxt = "";
std::string colorMapAuthor = "";
int selectedWindow = 0;
int fftRate = 20;
const int FFTSizes[] = {
65536,
32768,
16384,
8192,
4096,
2048,
1024
};
const char* FFTSizesStr = "65536\0"
"32768\0"
"16384\0"
"8192\0"
"4096\0"
"2048\0"
"1024\0";
int fftSizeId = 0;
void init() {
showWaterfall = core::configManager.conf["showWaterfall"];
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
std::string colormapName = core::configManager.conf["colorMap"];
if (colormaps::maps.find(colormapName) != colormaps::maps.end()) {
colormaps::Map map = colormaps::maps[colormapName];
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
}
for (auto const& [name, map] : colormaps::maps) {
colorMapNames.push_back(name);
colorMapNamesTxt += name;
colorMapNamesTxt += '\0';
if (name == colormapName) {
colorMapId = (colorMapNames.size() - 1);
colorMapAuthor = map.author;
}
}
fastFFT = core::configManager.conf["fastFFT"];
gui::waterfall.setFastFFT(fastFFT);
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
fftSizeId = 0;
int fftSize = core::configManager.conf["fftSize"];
for (int i = 0; i < 7; i++) {
if (fftSize == FFTSizes[i]) {
fftSizeId = i;
break;
}
}
gui::mainWindow.setFFTSize(FFTSizes[fftSizeId]);
fftRate = core::configManager.conf["fftRate"];
sigpath::signalPath.setFFTRate(fftRate);
selectedWindow = std::clamp<int>((int)core::configManager.conf["fftWindow"], 0, _FFT_WINDOW_COUNT-1);
gui::mainWindow.setFFTWindow(selectedWindow);
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
bool homePressed = ImGui::IsKeyPressed(GLFW_KEY_HOME, false);
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
if (homePressed) { showWaterfall = !showWaterfall; }
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
}
if (ImGui::Checkbox("Fast FFT##_sdrpp", &fastFFT)) {
gui::waterfall.setFastFFT(fastFFT);
core::configManager.acquire();
core::configManager.conf["fastFFT"] = fastFFT;
core::configManager.release(true);
}
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
core::configManager.acquire();
core::configManager.conf["fullWaterfallUpdate"] = fullWaterfallUpdate;
core::configManager.release(true);
}
ImGui::Text("FFT Framerate");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
fftRate = std::max<int>(1, fftRate);
sigpath::signalPath.setFFTRate(fftRate);
core::configManager.acquire();
core::configManager.conf["fftRate"] = fftRate;
core::configManager.release(true);
}
ImGui::Text("FFT Size");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) {
gui::mainWindow.setFFTSize(FFTSizes[fftSizeId]);
core::configManager.acquire();
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId];
core::configManager.release(true);
}
ImGui::Text("FFT Window");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##sdrpp_fft_window", &selectedWindow, "Rectangular\0Blackman\0")) {
gui::mainWindow.setFFTWindow(selectedWindow);
core::configManager.acquire();
core::configManager.conf["fftWindow"] = selectedWindow;
core::configManager.release(true);
}
if (colorMapNames.size() > 0) {
ImGui::Text("Color Map");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##_sdrpp_color_map_sel", &colorMapId, colorMapNamesTxt.c_str())) {
colormaps::Map map = colormaps::maps[colorMapNames[colorMapId]];
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
core::configManager.acquire();
core::configManager.conf["colorMap"] = colorMapNames[colorMapId];
core::configManager.release(true);
colorMapAuthor = map.author;
}
ImGui::Text("Color map Author: %s", colorMapAuthor.c_str());
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace displaymenu {
void init();
void draw(void* ctx);
}

View File

@ -0,0 +1,104 @@
#include <gui/menus/module_manager.h>
#include <imgui.h>
#include <core.h>
#include <string.h>
#include <gui/style.h>
namespace module_manager_menu {
char modName[1024];
std::vector<std::string> modTypes;
std::vector<std::string> toBeRemoved;
std::string modTypesTxt;
int modTypeId;
void init() {
modName[0] = 0;
modTypes.clear();
modTypesTxt = "";
for (auto& [name, mod] : core::moduleManager.modules) {
modTypes.push_back(name);
modTypesTxt += name;
modTypesTxt += '\0';
}
modTypeId = 0;
}
void draw(void* ctx) {
bool modified = false;
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 10);
ImGui::TableSetupScrollFreeze(3, 1);
ImGui::TableHeadersRow();
float height = ImGui::CalcTextSize("-").y;
toBeRemoved.clear();
for (auto& [name, inst] : core::moduleManager.instances) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text(name.c_str());
ImGui::TableSetColumnIndex(1);
ImGui::Text(inst.module.info->name);
ImGui::TableSetColumnIndex(2);
ImVec2 origPos = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(origPos.x - 3, origPos.y));
if (ImGui::Button(("##module_mgr_"+name).c_str(), ImVec2(height,height))) {
toBeRemoved.push_back(name);
modified = true;
}
ImGui::SetCursorPos(ImVec2(origPos.x + 2, origPos.y - 5));
ImGui::Text("_");
}
ImGui::EndTable();
for (auto& rem : toBeRemoved) {
core::moduleManager.deleteInstance(rem);
}
}
// Add module row with slightly different settings
ImGui::BeginTable("Module Manager Add Table", 3);
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 16);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
ImGui::InputText("##module_mod_name", modName, 1000);
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth());
ImGui::Combo("##module_mgr_type", &modTypeId, modTypesTxt.c_str());
ImGui::TableSetColumnIndex(2);
if (strlen(modName) == 0) { style::beginDisabled(); }
if (ImGui::Button("+##module_mgr_add_btn", ImVec2(16,0))) {
core::moduleManager.createInstance(modName, modTypes[modTypeId]);
core::moduleManager.postInit(modName);
modified = true;
}
if (strlen(modName) == 0) { style::endDisabled(); }
ImGui::EndTable();
if (modified) {
// Update enabled and disabled modules
core::configManager.acquire();
json instances;
for (auto [_name, inst] : core::moduleManager.instances) {
instances[_name]["module"] = inst.module.info->name;
instances[_name]["enabled"] = inst.instance->isEnabled();
}
core::configManager.conf["moduleInstances"] = instances;
core::configManager.release(true);
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace module_manager_menu {
void init();
void draw(void* ctx);
}

View File

@ -0,0 +1,15 @@
#include <gui/menus/sink.h>
#include <signal_path/signal_path.h>
#include <core.h>
namespace sinkmenu {
void init() {
core::configManager.acquire();
sigpath::sinkManager.loadSinksFromConfig();
core::configManager.release();
}
void draw(void* ctx) {
sigpath::sinkManager.showMenu();
}
};

View File

@ -0,0 +1,6 @@
#pragma once
namespace sinkmenu {
void init();
void draw(void* ctx);
};

View File

@ -0,0 +1,210 @@
#include <gui/menus/source.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
namespace sourecmenu {
int offsetMode = 0;
int sourceId = 0;
double customOffset = 0.0;
double effectiveOffset = 0.0;
int decimationPower = 0;
bool iqCorrection = false;
EventHandler<std::string> sourceRegisteredHandler;
EventHandler<std::string> sourceUnregisterHandler;
EventHandler<std::string> sourceUnregisteredHandler;
std::vector<std::string> sourceNames;
std::string sourceNamesTxt;
std::string selectedSource;
enum {
OFFSET_MODE_NONE,
OFFSET_MODE_CUSTOM,
OFFSET_MODE_SPYVERTER,
OFFSET_MODE_HAM_IT_UP,
OFFSET_MODE_DK5AV_XB,
OFFSET_MODE_KU_LNB_9750,
OFFSET_MODE_KU_LNB_10700,
_OFFSET_MODE_COUNT
};
const char* offsetModesTxt = "None\0"
"Custom\0"
"SpyVerter\0"
"Ham-It-Up\0"
"DK5AV X-Band\0"
"Ku LNB (9750MHz)\0"
"Ku LNB (10700MHz)\0";
const char* decimationStages = "None\0"
"2\0"
"4\0"
"8\0"
"16\0"
"32\0"
"64\0";
void updateOffset() {
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; }
else if (offsetMode == OFFSET_MODE_SPYVERTER) { effectiveOffset = 120000000; } // 120MHz Up-conversion
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) { effectiveOffset = 125000000; } // 125MHz Up-conversion
else if (offsetMode == OFFSET_MODE_DK5AV_XB) { effectiveOffset = -6800000000; } // 6.8GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) { effectiveOffset = -9750000000; } // 9.750GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) { effectiveOffset = -10700000000; } // 10.7GHz Down-conversion
else { effectiveOffset = 0; }
sigpath::sourceManager.setTuningOffset(effectiveOffset);
}
void refreshSources() {
sourceNames = sigpath::sourceManager.getSourceNames();
sourceNamesTxt.clear();
for (auto name : sourceNames) {
sourceNamesTxt += name;
sourceNamesTxt += '\0';
}
}
void selectSource(std::string name) {
if (sourceNames.empty()) {
selectedSource.clear();
return;
}
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
if (it == sourceNames.end()) {
selectSource(sourceNames[0]);
return;
}
sourceId = std::distance(sourceNames.begin(), it);
selectedSource = sourceNames[sourceId];
sigpath::sourceManager.selectSource(sourceNames[sourceId]);
}
void onSourceRegistered(std::string name, void* ctx) {
refreshSources();
if (selectedSource.empty()) {
sourceId = 0;
selectSource(sourceNames[0]);
return;
}
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
}
void onSourceUnregister(std::string name, void* ctx) {
if (name != selectedSource) { return; }
// TODO: Stop everything
}
void onSourceUnregistered(std::string name, void* ctx) {
refreshSources();
if (sourceNames.empty()) {
selectedSource = "";
return;
}
if (name == selectedSource) {
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1);
selectSource(sourceNames[sourceId]);
return;
}
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
}
void init() {
core::configManager.acquire();
std::string selected = core::configManager.conf["source"];
customOffset = core::configManager.conf["offset"];
offsetMode = core::configManager.conf["offsetMode"];
decimationPower = core::configManager.conf["decimationPower"];
iqCorrection = core::configManager.conf["iqCorrection"];
sigpath::signalPath.setIQCorrection(iqCorrection);
updateOffset();
refreshSources();
selectSource(selected);
sigpath::signalPath.setDecimation(decimationPower);
sourceRegisteredHandler.handler = onSourceRegistered;
sourceUnregisterHandler.handler = onSourceUnregister;
sourceUnregisteredHandler.handler = onSourceUnregistered;
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler);
core::configManager.release();
}
void draw(void* ctx) {
float itemWidth = ImGui::GetContentRegionAvailWidth();
bool running = gui::mainWindow.sdrIsRunning();
if (running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(itemWidth);
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) {
selectSource(sourceNames[sourceId]);
core::configManager.acquire();
core::configManager.conf["source"] = sourceNames[sourceId];
core::configManager.release(true);
}
if (running) { style::endDisabled(); }
sigpath::sourceManager.showSelectedMenu();
if (ImGui::Checkbox("IQ Correction##_sdrpp_iq_corr", &iqCorrection)) {
sigpath::signalPath.setIQCorrection(iqCorrection);
core::configManager.acquire();
core::configManager.conf["iqCorrection"] = iqCorrection;
core::configManager.release(true);
}
ImGui::Text("Offset mode");
ImGui::SameLine();
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) {
updateOffset();
core::configManager.acquire();
core::configManager.conf["offsetMode"] = offsetMode;
core::configManager.release(true);
}
ImGui::Text("Offset");
ImGui::SameLine();
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (offsetMode == OFFSET_MODE_CUSTOM) {
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) {
updateOffset();
core::configManager.acquire();
core::configManager.conf["offset"] = customOffset;
core::configManager.release(true);
}
}
else {
style::beginDisabled();
ImGui::InputDouble("##freq_offset", &effectiveOffset, 1.0, 100.0);
style::endDisabled();
}
if (running) { style::beginDisabled(); }
ImGui::Text("Decimation");
ImGui::SameLine();
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) {
sigpath::signalPath.setDecimation(decimationPower);
core::configManager.acquire();
core::configManager.conf["decimationPower"] = decimationPower;
core::configManager.release(true);
}
if (running) { style::endDisabled(); }
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace sourecmenu {
void init();
void draw(void* ctx);
}

View File

@ -0,0 +1,47 @@
#include <gui/menus/theme.h>
#include <gui/gui.h>
#include <options.h>
#include <core.h>
namespace thememenu {
int themeId;
std::vector<std::string> themeNames;
std::string themeNamesTxt;
void init(std::string resDir) {
// TODO: Not hardcode theme directory
gui::themeManager.loadThemesFromDir(resDir + "/themes/");
core::configManager.acquire();
std::string selectedThemeName = core::configManager.conf["theme"];
core::configManager.release();
// Select theme by name, if not available, apply Dark theme
themeNames = gui::themeManager.getThemeNames();
auto it = std::find(themeNames.begin(), themeNames.end(), selectedThemeName);
if (it == themeNames.end()) {
it = std::find(themeNames.begin(), themeNames.end(), "Dark");
selectedThemeName = "Dark";
}
gui::themeManager.applyTheme(selectedThemeName);
themeId = std::distance(themeNames.begin(), it);
themeNamesTxt = "";
for (auto name : themeNames) {
themeNamesTxt += name;
themeNamesTxt += '\0';
}
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGui::Text("Theme");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##theme_select_combo", &themeId, themeNamesTxt.c_str())) {
gui::themeManager.applyTheme(themeNames[themeId]);
core::configManager.acquire();
core::configManager.conf["theme"] = themeNames[themeId];
core::configManager.release(true);
}
}
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace thememenu {
void init(std::string resDir);
void draw(void* ctx);
}

View File

@ -0,0 +1,137 @@
#include <gui/menus/vfo_color.h>
#include <gui/gui.h>
#include <gui/widgets/waterfall.h>
#include <signal_path/signal_path.h>
#include <string>
#include <core.h>
#include <map>
namespace vfo_color_menu {
std::map<std::string, ImVec4> vfoColors;
std::string openName = "";
EventHandler<VFOManager::VFO*> vfoAddHndl;
void vfoAddHandler(VFOManager::VFO* vfo, void* ctx) {
std::string name = vfo->getName();
if (vfoColors.find(name) != vfoColors.end()) {
ImVec4 col = vfoColors[name];
vfo->setColor(IM_COL32((int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255), 50));
return;
}
vfo->setColor(IM_COL32(255, 255, 255, 50));
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
}
void init() {
// Load colors from config
bool modified = false;
core::configManager.acquire();
json conf = core::configManager.conf["vfoColors"];
for (auto& [name, val] : conf.items()) {
// If not a string, repair with default
if (!val.is_string()) {
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
modified = true;
if (sigpath::vfoManager.vfoExists(name)) {
sigpath::vfoManager.setColor(name, IM_COL32(255, 255, 255, 50));
}
continue;
}
// If not a valid hex color, repair with default
std::string col = val;
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
modified = true;
if (sigpath::vfoManager.vfoExists(name)) {
sigpath::vfoManager.setColor(name, IM_COL32(255, 255, 255, 50));
}
continue;
}
// Since the color is valid, decode it and set the vfo's color
float r, g, b;
r = std::stoi(col.substr(1, 2), NULL, 16);
g = std::stoi(col.substr(3, 2), NULL, 16);
b = std::stoi(col.substr(5, 2), NULL, 16);
vfoColors[name] = ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
if (sigpath::vfoManager.vfoExists(name)) {
sigpath::vfoManager.setColor(name, IM_COL32((int)roundf(r), (int)roundf(g), (int)roundf(b), 50));
}
}
// Iterate existing VFOs and set their color if in the config, if not set to default
for (auto& [name, vfo] : gui::waterfall.vfos) {
if (vfoColors.find(name) == vfoColors.end()) {
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
vfo->color = IM_COL32(255, 255, 255, 50);
modified = true;
}
}
vfoAddHndl.handler = vfoAddHandler;
sigpath::vfoManager.onVfoCreated.bindHandler(&vfoAddHndl);
core::configManager.release(modified);
}
void draw(void* ctx) {
ImGui::BeginTable("VFO Color Buttons Table", 2);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
if (ImGui::Button("Auto Color##vfo_color", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) {
float delta = 1.0f / (float)gui::waterfall.vfos.size();
float hue = 0;
for (auto& [name, vfo] : gui::waterfall.vfos) {
float r, g, b;
ImGui::ColorConvertHSVtoRGB(hue, 0.5f, 1.0f, r, g, b);
vfoColors[name] = ImVec4(r, g, b, 1.0f);
vfo->color = IM_COL32((int)roundf(r * 255), (int)roundf(g * 255), (int)roundf(b * 255), 50);
hue += delta;
core::configManager.acquire();
char buf[16];
sprintf(buf, "#%02X%02X%02X", (int)roundf(r * 255), (int)roundf(g * 255), (int)roundf(b * 255));
core::configManager.conf["vfoColors"][name] = buf;
core::configManager.release(true);
}
}
ImGui::TableSetColumnIndex(1);
if (ImGui::Button("Clear All##vfo_color", ImVec2(ImGui::GetContentRegionAvailWidth(), 0))) {
for (auto& [name, vfo] : gui::waterfall.vfos) {
vfoColors[name] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
vfo->color = IM_COL32(255, 255, 255, 50);
core::configManager.acquire();
char buf[16];
core::configManager.conf["vfoColors"][name] = "#FFFFFF";
core::configManager.release(true);
}
}
ImGui::EndTable();
ImGui::BeginTable("VFO Color table", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
for (auto& [name, vfo] : gui::waterfall.vfos) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImVec4 col(1.0f, 1.0f, 1.0f, 1.0f);
if (vfoColors.find(name) != vfoColors.end()) {
col = vfoColors[name];
}
if (ImGui::ColorEdit3(("##vfo_color_"+name).c_str(), (float*)&col, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
vfoColors[name] = col;
vfo->color = IM_COL32((int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255), 50);
core::configManager.acquire();
char buf[16];
sprintf(buf, "#%02X%02X%02X", (int)roundf(col.x * 255), (int)roundf(col.y * 255), (int)roundf(col.z * 255));
core::configManager.conf["vfoColors"][name] = buf;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::Text(name.c_str());
}
ImGui::EndTable();
}
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace vfo_color_menu {
void init();
void draw(void* ctx);
}

46
core/src/gui/style.cpp Normal file
View File

@ -0,0 +1,46 @@
#include <gui/style.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <config.h>
#include <options.h>
#include <spdlog/spdlog.h>
#include <filesystem>
namespace style {
ImFont* baseFont;
ImFont* bigFont;
ImFont* hugeFont;
bool loadFonts(std::string resDir) {
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Inavlid resource directory: {0}", resDir);
return false;
}
baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f);
bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f);
hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f);
return true;
}
void beginDisabled() {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
auto& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
ImVec4 btnCol = colors[ImGuiCol_Button];
ImVec4 frameCol = colors[ImGuiCol_FrameBg];
ImVec4 textCol = colors[ImGuiCol_Text];
btnCol.w = 0.15f;
frameCol.w = 0.30f;
textCol.w = 0.65f;
ImGui::PushStyleColor(ImGuiCol_Button, btnCol);
ImGui::PushStyleColor(ImGuiCol_FrameBg, frameCol);
ImGui::PushStyleColor(ImGuiCol_Text, textCol);
}
void endDisabled() {
ImGui::PopItemFlag();
ImGui::PopStyleColor(3);
}
}

15
core/src/gui/style.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <imgui.h>
#include <string>
namespace style {
extern ImFont* baseFont;
extern ImFont* bigFont;
extern ImFont* hugeFont;
bool setDefaultStyle(std::string resDir);
bool loadFonts(std::string resDir);
void beginDisabled();
void endDisabled();
void testtt();
}

View File

@ -0,0 +1,237 @@
#include <json.hpp>
#include <gui/theme_manager.h>
#include <imgui_internal.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <fstream>
bool ThemeManager::loadThemesFromDir(std::string path) {
// // TEST JUST TO DUMP THE ORIGINAL THEME
// auto& style = ImGui::GetStyle();
// ImVec4* colors = style.Colors;
// printf("\n\n");
// for (auto [name, id] : IMGUI_COL_IDS) {
// ImVec4 col = colors[id];
// uint8_t r = 255 - (col.x * 255.0f);
// uint8_t g = 255 - (col.y * 255.0f);
// uint8_t b = 255 - (col.z * 255.0f);
// uint8_t a = col.w * 255.0f;
// printf("\"%s\": \"#%02X%02X%02X%02X\",\n", name.c_str(), r, g, b, a);
// }
// printf("\n\n");
if (!std::filesystem::is_directory(path)) {
spdlog::error("Theme directory doesn't exist: {0}", path);
return false;
}
themes.clear();
for (const auto & file : std::filesystem::directory_iterator(path)) {
std::string _path = file.path().generic_string();
if (file.path().extension().generic_string() != ".json") {
continue;
}
loadTheme(_path);
}
return true;
}
bool ThemeManager::loadTheme(std::string path) {
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Theme file doesn't exist: {0}", path);
return false;
}
// Load defaults in theme
Theme thm;
thm.author = "--";
// Load JSON
std::ifstream file(path.c_str());
json data;
file >> data;
file.close();
// Load theme name
if (!data.contains("name")) {
spdlog::error("Theme {0} is missing the name parameter", path);
return false;
}
if (!data["name"].is_string()) {
spdlog::error("Theme {0} contains invalid name field. Expected string", path);
return false;
}
std::string name = data["name"];
if (themes.find(name) != themes.end()) {
spdlog::error("A theme named '{0}' already exists", name);
return false;
}
// Load theme author if available
if (data.contains("author")) {
if (!data["author"].is_string()) {
spdlog::error("Theme {0} contains invalid author field. Expected string", path);
return false;
}
thm.author = data["author"];
}
// Iterate through all parameters and check their contents
std::map<std::string, std::string> params = data;
for (auto const& [param, val] : params) {
if (param == "name" || param == "author") { continue; }
// Exception for non-imgu colors
if (param == "WaterfallBackground" || param == "ClearColor") {
if (val[0] != '#' || !std::all_of(val.begin() + 1, val.end(), ::isxdigit) || val.length() != 9) {
spdlog::error("Theme {0} contains invalid {1} field. Expected hex RGBA color", path, param);
return false;
}
continue;
}
bool isValid = false;
// If param is a color, check that it's a valid RGBA hex value
if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) {
if (val[0] != '#' || !std::all_of(val.begin() + 1, val.end(), ::isxdigit) || val.length() != 9) {
spdlog::error("Theme {0} contains invalid {1} field. Expected hex RGBA color", path, param);
return false;
}
isValid = true;
}
if (!isValid) {
spdlog::error("Theme {0} contains unknown {1} field.", path, param);
return false;
}
}
thm.data = data;
themes[name] = thm;
return true;
}
bool ThemeManager::applyTheme(std::string name) {
if (themes.find(name) == themes.end()) {
spdlog::error("Unknown theme: {0}", name);
return false;
}
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
style.WindowRounding = 0.0f;
style.ChildRounding = 0.0f;
style.FrameRounding = 0.0f;
style.GrabRounding = 0.0f;
style.PopupRounding = 0.0f;
style.ScrollbarRounding = 0.0f;
ImVec4* colors = style.Colors;
Theme thm = themes[name];
uint8_t ret[4];
std::map<std::string, std::string> params = thm.data;
for (auto const& [param, val] : params) {
if (param == "name" || param == "author") { continue; }
if (param == "WaterfallBackground") {
decodeRGBA(val, ret);
waterfallBg = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
continue;
}
if (param == "ClearColor") {
decodeRGBA(val, ret);
clearColor = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
continue;
}
// If param is a color, check that it's a valid RGBA hex value
if (IMGUI_COL_IDS.find(param) != IMGUI_COL_IDS.end()) {
decodeRGBA(val, ret);
colors[IMGUI_COL_IDS[param]] = ImVec4((float)ret[0]/255.0f, (float)ret[1]/255.0f, (float)ret[2]/255.0f, (float)ret[3]/255.0f);
continue;
}
}
return true;
}
bool ThemeManager::decodeRGBA(std::string str, uint8_t out[4]) {
if (str[0] != '#' || !std::all_of(str.begin() + 1, str.end(), ::isxdigit) || str.length() != 9) {
return false;
}
out[0] = std::stoi(str.substr(1, 2), NULL, 16);
out[1] = std::stoi(str.substr(3, 2), NULL, 16);
out[2] = std::stoi(str.substr(5, 2), NULL, 16);
out[3] = std::stoi(str.substr(7, 2), NULL, 16);
return true;
}
std::vector<std::string> ThemeManager::getThemeNames() {
std::vector<std::string> names;
for (auto [name, theme] : themes) { names.push_back(name); }
return names;
}
std::map<std::string, int> ThemeManager::IMGUI_COL_IDS = {
{"Text", ImGuiCol_Text},
{"TextDisabled", ImGuiCol_TextDisabled},
{"WindowBg", ImGuiCol_WindowBg},
{"ChildBg", ImGuiCol_ChildBg},
{"PopupBg", ImGuiCol_PopupBg},
{"Border", ImGuiCol_Border},
{"BorderShadow", ImGuiCol_BorderShadow},
{"FrameBg", ImGuiCol_FrameBg},
{"FrameBgHovered", ImGuiCol_FrameBgHovered},
{"FrameBgActive", ImGuiCol_FrameBgActive},
{"TitleBg", ImGuiCol_TitleBg},
{"TitleBgActive", ImGuiCol_TitleBgActive},
{"TitleBgCollapsed", ImGuiCol_TitleBgCollapsed},
{"MenuBarBg", ImGuiCol_MenuBarBg},
{"ScrollbarBg", ImGuiCol_ScrollbarBg},
{"ScrollbarGrab", ImGuiCol_ScrollbarGrab},
{"ScrollbarGrabHovered", ImGuiCol_ScrollbarGrabHovered},
{"ScrollbarGrabActive", ImGuiCol_ScrollbarGrabActive},
{"CheckMark", ImGuiCol_CheckMark},
{"SliderGrab", ImGuiCol_SliderGrab},
{"SliderGrabActive", ImGuiCol_SliderGrabActive},
{"Button", ImGuiCol_Button},
{"ButtonHovered", ImGuiCol_ButtonHovered},
{"ButtonActive", ImGuiCol_ButtonActive},
{"Header", ImGuiCol_Header},
{"HeaderHovered", ImGuiCol_HeaderHovered},
{"HeaderActive", ImGuiCol_HeaderActive},
{"Separator", ImGuiCol_Separator},
{"SeparatorHovered", ImGuiCol_SeparatorHovered},
{"SeparatorActive", ImGuiCol_SeparatorActive},
{"ResizeGrip", ImGuiCol_ResizeGrip},
{"ResizeGripHovered", ImGuiCol_ResizeGripHovered},
{"ResizeGripActive", ImGuiCol_ResizeGripActive},
{"Tab", ImGuiCol_Tab},
{"TabHovered", ImGuiCol_TabHovered},
{"TabActive", ImGuiCol_TabActive},
{"TabUnfocused", ImGuiCol_TabUnfocused},
{"TabUnfocusedActive", ImGuiCol_TabUnfocusedActive},
{"PlotLines", ImGuiCol_PlotLines},
{"PlotLinesHovered", ImGuiCol_PlotLinesHovered},
{"PlotHistogram", ImGuiCol_PlotHistogram},
{"PlotHistogramHovered", ImGuiCol_PlotHistogramHovered},
{"TableHeaderBg", ImGuiCol_TableHeaderBg},
{"TableBorderStrong", ImGuiCol_TableBorderStrong},
{"TableBorderLight", ImGuiCol_TableBorderLight},
{"TableRowBg", ImGuiCol_TableRowBg},
{"TableRowBgAlt", ImGuiCol_TableRowBgAlt},
{"TextSelectedBg", ImGuiCol_TextSelectedBg},
{"DragDropTarget", ImGuiCol_DragDropTarget},
{"NavHighlight", ImGuiCol_NavHighlight},
{"NavWindowingHighlight", ImGuiCol_NavWindowingHighlight},
{"NavWindowingDimBg", ImGuiCol_NavWindowingDimBg},
{"ModalWindowDimBg", ImGuiCol_ModalWindowDimBg}
};

View File

@ -0,0 +1,33 @@
#pragma once
#include <map>
#include <mutex>
#include <imgui.h>
#include <vector>
#include <json.hpp>
using nlohmann::json;
struct Theme {
std::string author;
json data;
};
class ThemeManager {
public:
bool loadThemesFromDir(std::string path);
bool loadTheme(std::string path);
bool applyTheme(std::string name);
std::vector<std::string> getThemeNames();
ImVec4 waterfallBg = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);;
ImVec4 clearColor = ImVec4(0.0666f, 0.0666f, 0.0666f, 1.0f);
private:
static bool decodeRGBA(std::string str, uint8_t out[4]);
static std::map<std::string, int> IMGUI_COL_IDS;
std::map<std::string, Theme> themes;
};

139
core/src/gui/tuner.cpp Normal file
View File

@ -0,0 +1,139 @@
#include <signal_path/signal_path.h>
#include <gui/gui.h>
#include <gui/tuner.h>
#include <string>
namespace tuner {
void centerTuning(std::string vfoName, double freq) {
if (vfoName != "") {
if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; }
sigpath::vfoManager.setOffset(vfoName, 0);
}
double BW = gui::waterfall.getBandwidth();
double viewBW = gui::waterfall.getViewBandwidth();
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
gui::waterfall.setCenterFrequency(freq);
gui::waterfall.setViewOffset(0);
gui::freqSelect.setFrequency(freq);
sigpath::sourceManager.tune(freq);
}
void normalTuning(std::string vfoName, double freq) {
if (vfoName == "") {
centerTuning(vfoName, freq);
return;
}
if (gui::waterfall.vfos.find(vfoName) == gui::waterfall.vfos.end()) { return; }
double viewBW = gui::waterfall.getViewBandwidth();
double BW = gui::waterfall.getBandwidth();
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[vfoName];
double currentOff = vfo->centerOffset;
double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
double delta = freq - currentTune;
double newVFO = currentOff + delta;
double vfoBW = vfo->bandwidth;
double vfoBottom = newVFO - (vfoBW / 2.0);
double vfoTop = newVFO + (vfoBW / 2.0);
double view = gui::waterfall.getViewOffset();
double viewBottom = view - (viewBW / 2.0);
double viewTop = view + (viewBW / 2.0);
double wholeFreq = gui::waterfall.getCenterFrequency();
double bottom = -(BW / 2.0);
double top = (BW / 2.0);
// VFO still fints in the view
if (vfoBottom > viewBottom && vfoTop < viewTop) {
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
return;
}
// VFO too low for current SDR tuning
if (vfoBottom < bottom) {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
sigpath::vfoManager.setOffset(vfoName, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
return;
}
// VFO too high for current SDR tuning
if (vfoTop > top) {
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
sigpath::vfoManager.setOffset(vfoName, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
return;
}
// VFO is still without the SDR's bandwidth
if (delta < 0) {
double newViewOff = vfoTop - (viewBW / 2.0) + (viewBW / 10.0);
double newViewBottom = newViewOff - (viewBW / 2.0);
double newViewTop = newViewOff + (viewBW / 2.0);
if (newViewBottom > bottom) {
gui::waterfall.setViewOffset(newViewOff);
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
return;
}
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
double newVFOOffset = (BW / 2.0) - (vfoBW / 2.0) - (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(vfoName, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
}
else {
double newViewOff = vfoBottom + (viewBW / 2.0) - (viewBW / 10.0);
double newViewBottom = newViewOff - (viewBW / 2.0);
double newViewTop = newViewOff + (viewBW / 2.0);
if (newViewTop < top) {
gui::waterfall.setViewOffset(newViewOff);
sigpath::vfoManager.setCenterOffset(vfoName, newVFO);
return;
}
gui::waterfall.setViewOffset((viewBW / 2.0) - (BW / 2.0));
double newVFOOffset = (vfoBW / 2.0) - (BW / 2.0) + (viewBW / 10.0);
sigpath::vfoManager.setCenterOffset(vfoName, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
}
}
void iqTuning(double freq) {
gui::waterfall.setCenterFrequency(freq);
gui::waterfall.centerFreqMoved = true;
sigpath::sourceManager.tune(freq);
}
void tune(int mode, std::string vfoName, double freq) {
switch (mode) {
case TUNER_MODE_CENTER:
centerTuning(vfoName, freq);
break;
case TUNER_MODE_NORMAL:
normalTuning(vfoName, freq);
break;
case TUNER_MODE_LOWER_HALF:
normalTuning(vfoName, freq);
break;
case TUNER_MODE_UPPER_HALF:
normalTuning(vfoName, freq);
break;
case TUNER_MODE_IQ_ONLY:
iqTuning(freq);
break;
}
}
}

20
core/src/gui/tuner.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
#include <module.h>
namespace tuner {
void centerTuning(std::string vfoName, double freq);
void normalTuning(std::string vfoName, double freq);
void iqTuning(double freq);
enum {
TUNER_MODE_CENTER,
TUNER_MODE_NORMAL,
TUNER_MODE_LOWER_HALF,
TUNER_MODE_UPPER_HALF,
TUNER_MODE_IQ_ONLY,
_TUNER_MODE_COUNT
};
void tune(int mode, std::string vfoName, double freq);
}

View File

@ -0,0 +1,114 @@
#include <gui/widgets/bandplan.h>
#include <fstream>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <sstream>
#include <iomanip>
namespace bandplan {
std::map<std::string, BandPlan_t> bandplans;
std::vector<std::string> bandplanNames;
std::string bandplanNameTxt;
std::map<std::string, BandPlanColor_t> colorTable;
void generateTxt() {
bandplanNameTxt = "";
for (int i = 0; i < bandplanNames.size(); i++) {
bandplanNameTxt += bandplanNames[i];
bandplanNameTxt += '\0';
}
}
void to_json(json& j, const Band_t& b) {
j = json{
{"name", b.name},
{"type", b.type},
{"start", b.start},
{"end", b.end},
};
}
void from_json(const json& j, Band_t& b) {
j.at("name").get_to(b.name);
j.at("type").get_to(b.type);
j.at("start").get_to(b.start);
j.at("end").get_to(b.end);
}
void to_json(json& j, const BandPlan_t& b) {
j = json{
{"name", b.name},
{"country_name", b.countryName},
{"country_code", b.countryCode},
{"author_name", b.authorName},
{"author_url", b.authorURL},
{"bands", b.bands}
};
}
void from_json(const json& j, BandPlan_t& b) {
j.at("name").get_to(b.name);
j.at("country_name").get_to(b.countryName);
j.at("country_code").get_to(b.countryCode);
j.at("author_name").get_to(b.authorName);
j.at("author_url").get_to(b.authorURL);
j.at("bands").get_to(b.bands);
}
void to_json(json& j, const BandPlanColor_t& ct) {
spdlog::error("ImGui color to JSON not implemented!!!");
}
void from_json(const json& j, BandPlanColor_t& ct) {
std::string col = j.get<std::string>();
if (col[0] != '#' || !std::all_of(col.begin() + 1, col.end(), ::isxdigit)) {
return;
}
uint8_t r, g, b, a;
r = std::stoi(col.substr(1, 2), NULL, 16);
g = std::stoi(col.substr(3, 2), NULL, 16);
b = std::stoi(col.substr(5, 2), NULL, 16);
a = std::stoi(col.substr(7, 2), NULL, 16);
ct.colorValue = IM_COL32(r, g, b, a);
ct.transColorValue = IM_COL32(r, g, b, 100);
}
void loadBandPlan(std::string path) {
std::ifstream file(path.c_str());
json data;
file >> data;
file.close();
BandPlan_t plan = data.get<BandPlan_t>();
if (bandplans.find(plan.name) != bandplans.end()) {
spdlog::error("Duplicate band plan name ({0}), not loading.", plan.name);
return;
}
bandplans[plan.name] = plan;
bandplanNames.push_back(plan.name);
generateTxt();
}
void loadFromDir(std::string path) {
if (!std::filesystem::exists(path)) {
spdlog::error("Band Plan directory does not exist");
return;
}
if (!std::filesystem::is_directory(path)) {
spdlog::error("Band Plan directory isn't a directory...");
return;
}
bandplans.clear();
for (const auto & file : std::filesystem::directory_iterator(path)) {
std::string path = file.path().generic_string();
if (file.path().extension().generic_string() != ".json") {
continue;
}
loadBandPlan(path);
}
}
void loadColorTable(json table) {
colorTable = table.get<std::map<std::string, BandPlanColor_t>>();
}
};

View File

@ -0,0 +1,47 @@
#pragma once
#include <json.hpp>
#include <imgui/imgui.h>
#include <stdint.h>
using nlohmann::json;
namespace bandplan {
struct Band_t {
std::string name;
std::string type;
double start;
double end;
};
void to_json(json& j, const Band_t& b);
void from_json(const json& j, Band_t& b);
struct BandPlan_t {
std::string name;
std::string countryName;
std::string countryCode;
std::string authorName;
std::string authorURL;
std::vector<Band_t> bands;
};
void to_json(json& j, const BandPlan_t& b);
void from_json(const json& j, BandPlan_t& b);
struct BandPlanColor_t {
uint32_t colorValue;
uint32_t transColorValue;
};
void to_json(json& j, const BandPlanColor_t& ct);
void from_json(const json& j, BandPlanColor_t& ct);
void loadBandPlan(std::string path);
void loadFromDir(std::string path);
void loadColorTable(json table);
extern std::map<std::string, BandPlan_t> bandplans;
extern std::vector<std::string> bandplanNames;
extern std::string bandplanNameTxt;
extern std::map<std::string, BandPlanColor_t> colorTable;
};

View File

@ -0,0 +1,42 @@
#include <gui/widgets/constellation_diagram.h>
namespace ImGui {
ConstellationDiagram::ConstellationDiagram() {
memset(buffer, 0, 1024 * sizeof(dsp::complex_t));
}
void ConstellationDiagram::draw(const ImVec2& size_arg) {
std::lock_guard<std::mutex> lck(bufferMtx);
ImGuiWindow* window = GetCurrentWindow();
ImGuiStyle& style = GetStyle();
float pad = style.FramePadding.y;
ImVec2 min = window->DC.CursorPos;
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), CalcItemWidth());
ImRect bb(min, ImVec2(min.x+size.x, min.y+size.y));
float lineHeight = size.y;
ItemSize(size, style.FramePadding.y);
if (!ItemAdd(bb, 0)) {
return;
}
window->DrawList->AddRectFilled(min, ImVec2(min.x+size.x, min.y+size.y), IM_COL32(0,0,0,255));
ImU32 col = ImGui::GetColorU32(ImGuiCol_CheckMark, 0.7f);
float increment = size.x / 1024.0f;
for (int i = 0; i < 1024; i++) {
if (buffer[i].re > 1.5f || buffer[i].re < -1.5f) { continue; }
if (buffer[i].im > 1.5f || buffer[i].im < -1.5f) { continue; }
window->DrawList->AddCircleFilled(ImVec2((((buffer[i].re / 1.5f) + 1) * (size.x*0.5f)) + min.x, (((buffer[i].im / 1.5f) + 1) * (size.y*0.5f)) + min.y), 2, col);
}
}
dsp::complex_t* ConstellationDiagram::acquireBuffer() {
bufferMtx.lock();
return buffer;
}
void ConstellationDiagram::releaseBuffer() {
bufferMtx.unlock();
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <dsp/stream.h>
#include <mutex>
#include <dsp/types.h>
namespace ImGui {
class ConstellationDiagram {
public:
ConstellationDiagram();
void draw(const ImVec2& size_arg = ImVec2(0, 0));
dsp::complex_t* acquireBuffer();
void releaseBuffer();
private:
std::mutex bufferMtx;
dsp::complex_t buffer[1024];
};
}

View File

@ -0,0 +1,77 @@
#include <gui/widgets/file_select.h>
#include <regex>
#include <options.h>
#include <filesystem>
#include <gui/file_dialogs.h>
FileSelect::FileSelect(std::string defaultPath, std::vector<std::string> filter) {
_filter = filter;
setPath(defaultPath);
}
bool FileSelect::render(std::string id) {
bool _pathChanged = false;
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
float buttonWidth = ImGui::CalcTextSize("...").x + 20.0f;
bool lastPathValid = pathValid;
if (!lastPathValid) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
}
ImGui::SetNextItemWidth(menuColumnWidth - buttonWidth);
if (ImGui::InputText(id.c_str(), strPath, 2047)) {
path = std::string(strPath);
std::string expandedPath = expandString(strPath);
if (!std::filesystem::is_regular_file(expandedPath)) {
pathValid = false;
}
else {
pathValid = true;
_pathChanged = true;
}
}
if (!lastPathValid) {
ImGui::PopStyleColor();
}
ImGui::SameLine();
if (ImGui::Button(("..." + id + "_winselect").c_str(), ImVec2(buttonWidth - 8.0f, 0)) && !dialogOpen) {
dialogOpen = true;
if (workerThread.joinable()) { workerThread.join(); }
workerThread = std::thread(&FileSelect::worker, this);
}
_pathChanged |= pathChanged;
pathChanged = false;
return _pathChanged;
}
void FileSelect::setPath(std::string path, bool markChanged) {
this->path = path;
std::string expandedPath = expandString(path);
pathValid = std::filesystem::is_regular_file(expandedPath);
if (markChanged) { pathChanged = true; }
strcpy(strPath, path.c_str());
}
std::string FileSelect::expandString(std::string input) {
input = std::regex_replace(input, std::regex("%ROOT%"), options::opts.root);
return std::regex_replace(input, std::regex("//"), "/");
}
bool FileSelect::pathIsValid() {
return pathValid;
}
void FileSelect::worker() {
auto file = pfd::open_file("Open File", pathValid ? std::filesystem::path(expandString(path)).parent_path().string() : "", _filter);
std::vector<std::string> res = file.result();
if (res.size() > 0) {
path = res[0];
strcpy(strPath, path.c_str());
pathChanged = true;
}
pathValid = std::filesystem::is_regular_file(expandString(path));
dialogOpen = false;
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <stdint.h>
#include <string>
#include <thread>
#include <vector>
class FileSelect {
public:
FileSelect(std::string defaultPath, std::vector<std::string> filter = {"All Files", "*"});
bool render(std::string id);
void setPath(std::string path, bool markChanged = false);
bool pathIsValid();
std::string expandString(std::string input);
std::string path = "";
private:
void worker();
std::thread workerThread;
std::vector<std::string> _filter;
bool pathValid = false;
bool dialogOpen = false;
char strPath[2048];
bool pathChanged = false;
};

View File

@ -0,0 +1,76 @@
#include <gui/widgets/folder_select.h>
#include <regex>
#include <options.h>
#include <filesystem>
#include <gui/file_dialogs.h>
FolderSelect::FolderSelect(std::string defaultPath) {
setPath(defaultPath);
}
bool FolderSelect::render(std::string id) {
bool _pathChanged = false;
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
float buttonWidth = ImGui::CalcTextSize("...").x + 20.0f;
bool lastPathValid = pathValid;
if (!lastPathValid) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
}
ImGui::SetNextItemWidth(menuColumnWidth - buttonWidth);
if (ImGui::InputText(id.c_str(), strPath, 2047)) {
path = std::string(strPath);
std::string expandedPath = expandString(strPath);
if (!std::filesystem::is_directory(expandedPath)) {
pathValid = false;
}
else {
pathValid = true;
_pathChanged = true;
}
}
if (!lastPathValid) {
ImGui::PopStyleColor();
}
ImGui::SameLine();
if (ImGui::Button(("..." + id + "_winselect").c_str(), ImVec2(buttonWidth - 8.0f, 0)) && !dialogOpen) {
dialogOpen = true;
if (workerThread.joinable()) { workerThread.join(); }
workerThread = std::thread(&FolderSelect::worker, this);
}
_pathChanged |= pathChanged;
pathChanged = false;
return _pathChanged;
}
void FolderSelect::setPath(std::string path, bool markChanged) {
this->path = path;
std::string expandedPath = expandString(path);
pathValid = std::filesystem::is_directory(expandedPath);
if (markChanged) { pathChanged = true; }
strcpy(strPath, path.c_str());
}
std::string FolderSelect::expandString(std::string input) {
input = std::regex_replace(input, std::regex("%ROOT%"), options::opts.root);
return std::regex_replace(input, std::regex("//"), "/");
}
bool FolderSelect::pathIsValid() {
return pathValid;
}
void FolderSelect::worker() {
auto fold = pfd::select_folder("Select Folder", pathValid ? std::filesystem::path(expandString(path)).parent_path().string() : "");
std::string res = fold.result();
if (res != "") {
path = res;
strcpy(strPath, path.c_str());
pathChanged = true;
}
pathValid = std::filesystem::is_directory(expandString(path));
dialogOpen = false;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <stdint.h>
#include <string>
#include <thread>
class FolderSelect {
public:
FolderSelect(std::string defaultPath);
bool render(std::string id);
void setPath(std::string path, bool markChanged = false);
bool pathIsValid();
std::string expandString(std::string input);
std::string path = "";
private:
void worker();
std::thread workerThread;
bool pathValid = false;
bool dialogOpen = false;
char strPath[2048];
bool pathChanged = false;
};

View File

@ -0,0 +1,244 @@
#include <gui/widgets/frequency_select.h>
#include <config.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <glfw_window.h>
#include <GLFW/glfw3.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
}
FrequencySelect::FrequencySelect() {
}
void FrequencySelect::init() {
for (int i = 0; i < 12; i++) {
digits[i] = 0;
}
}
void FrequencySelect::onPosChange() {
ImVec2 digitSz = ImGui::CalcTextSize("0");
ImVec2 commaSz = ImGui::CalcTextSize(".");
int digitHeight = digitSz.y;
int digitWidth = digitSz.x;
int commaOffset = 0;
for (int i = 0; i < 12; i++) {
digitTopMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y);
digitBottomMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y + (digitHeight / 2));
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + (digitHeight / 2));
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + digitHeight);
if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += commaSz.x;
}
}
}
void FrequencySelect::onResize() {
}
void FrequencySelect::incrementDigit(int i) {
if (i < 0) {
return;
}
if (digits[i] < 9) {
digits[i]++;
}
else {
digits[i] = 0;
incrementDigit(i - 1);
}
frequencyChanged = true;
}
void FrequencySelect::decrementDigit(int i) {
if (i < 0) {
return;
}
if (digits[i] > 0) {
digits[i]--;
}
else {
if (i == 0) { return; }
// Check if there are non zero digits afterwards
bool otherNoneZero = false;
for (int j = i - 1; j >= 0; j--) {
if (digits[j] > 0) {
otherNoneZero = true;
break;
}
}
if (!otherNoneZero) { return; }
digits[i] = 9;
decrementDigit(i - 1);
}
frequencyChanged = true;
}
void FrequencySelect::moveCursorToDigit(int i) {
double xpos, ypos;
glfwGetCursorPos(core::window, &xpos, &ypos);
float nxpos = (digitTopMaxs[i].x + digitTopMins[i].x) / 2.0f;
glfwSetCursorPos(core::window, nxpos, ypos);
}
void FrequencySelect::draw() {
window = ImGui::GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax();
ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x;
widgetPos.y += window->Pos.y - 3;
widgetEndPos.x += window->Pos.x + cursorPos.x;
widgetEndPos.y += window->Pos.y - 3;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
ImGui::PushFont(style::bigFont);
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
onPosChange();
}
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
lastWidgetSize = widgetSize;
onResize();
}
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
ImVec2 digitSz = ImGui::CalcTextSize("0");
ImVec2 commaSz = ImGui::CalcTextSize(".");
int digitHeight = digitSz.y;
int digitWidth = digitSz.x;
int commaOffset = 0;
bool zeros = true;
ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y)));
for (int i = 0; i < 12; i++) {
if (digits[i] != 0) {
zeros = false;
}
sprintf(buf, "%d", digits[i]);
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y),
zeros ? disabledColor : textColor, buf);
if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += commaSz.x;
window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y),
zeros ? disabledColor : textColor, ".");
}
}
if (!gui::mainWindow.lockWaterfallControls) {
ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = ImGui::GetIO().MouseWheel;
bool onDigit = false;
bool hovered = false;
for (int i = 0; i < 12; i++) {
onDigit = false;
if (isInArea(mousePos, digitTopMins[i], digitTopMaxs[i])) {
window->DrawList->AddRectFilled(digitTopMins[i], digitTopMaxs[i], IM_COL32(255, 0, 0, 75));
if (leftClick) {
incrementDigit(i);
}
onDigit = true;
}
if (isInArea(mousePos, digitBottomMins[i], digitBottomMaxs[i])) {
window->DrawList->AddRectFilled(digitBottomMins[i], digitBottomMaxs[i], IM_COL32(0, 0, 255, 75));
if (leftClick) {
decrementDigit(i);
}
onDigit = true;
}
if (onDigit) {
hovered = true;
if (rightClick || (ImGui::IsKeyPressed(GLFW_KEY_DELETE) || ImGui::IsKeyPressed(GLFW_KEY_ENTER) || ImGui::IsKeyPressed(GLFW_KEY_KP_ENTER))) {
for (int j = i; j < 12; j++) {
digits[j] = 0;
}
frequencyChanged = true;
}
if (ImGui::IsKeyPressed(GLFW_KEY_UP)) {
incrementDigit(i);
}
if (ImGui::IsKeyPressed(GLFW_KEY_DOWN)) {
decrementDigit(i);
}
if ((ImGui::IsKeyPressed(GLFW_KEY_LEFT) || ImGui::IsKeyPressed(GLFW_KEY_BACKSPACE)) && i > 0) {
moveCursorToDigit(i - 1);
}
if (ImGui::IsKeyPressed(GLFW_KEY_RIGHT) && i < 11) {
moveCursorToDigit(i + 1);
}
auto chars = ImGui::GetIO().InputQueueCharacters;
// For each keyboard characters, type it
for (int j = 0; j < chars.Size; j++) {
if (chars[j] >= '0' && chars[j] <= '9') {
digits[i + j] = chars[j] - '0';
if ((i + j) < 11) { moveCursorToDigit(i + j + 1); }
frequencyChanged = true;
}
}
if (mw != 0) {
int count = abs(mw);
for (int j = 0; j < count; j++) {
mw > 0 ? incrementDigit(i) : decrementDigit(i);
}
}
}
}
digitHovered = hovered;
}
uint64_t freq = 0;
for (int i = 0; i < 12; i++) {
freq += digits[i] * pow(10, 11 - i);
}
uint64_t orig = freq;
freq = std::clamp<uint64_t>(freq, minFreq, maxFreq);
if (freq != orig && limitFreq) {
setFrequency(freq);
}
else {
frequency = orig;
}
ImGui::PopFont();
ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17);
//ImGui::NewLine();
}
void FrequencySelect::setFrequency(int64_t freq) {
freq = std::max<int64_t>(0, freq);
int i = 11;
for (uint64_t f = freq; i >= 0; i--) {
digits[i] = f % 10;
f -= digits[i];
f /= 10;
}
frequency = freq;
}

View File

@ -1,22 +1,29 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <stdint.h>
class FrequencySelect {
public:
FrequencySelect();
void init();
void draw();
void setFrequency(long freq);
void setFrequency(int64_t freq);
long frequency;
uint64_t frequency;
bool frequencyChanged = false;
bool digitHovered = false;
bool limitFreq;
uint64_t minFreq;
uint64_t maxFreq;
private:
void onPosChange();
void onResize();
void incrementDigit(int i);
void decrementDigit(int i);
void moveCursorToDigit(int i);
ImVec2 widgetPos;
ImVec2 widgetEndPos;
@ -26,7 +33,6 @@ private:
ImVec2 lastWidgetSize;
ImGuiWindow* window;
ImFont* font;
int digits[12];
ImVec2 digitBottomMins[12];

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