500 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
414 changed files with 70966 additions and 114058 deletions

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/

View File

@ -1,45 +0,0 @@
name: Linux Build
on: [push]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Update repositories
run: sudo apt update
- name: Install dependencies
run: sudo apt install libfftw3-dev libglfw3-dev libglew-dev libvolk2-dev libsoapysdr-dev libairspyhf-dev libairspy-dev libiio-dev libad9361-dev portaudio19-dev libhackrf-dev
- name: Create Build Environment
# Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure CMake
# Use a bash shell so we can use the same syntax for environment variable
# access regardless of the host operating system
shell: bash
working-directory: ${{runner.workspace}}/build
# Note the current convention is to use the -S and -B options here to specify source
# and build directories, but this is only available with CMake 3.13 and higher.
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Build
working-directory: ${{runner.workspace}}/build
shell: bash
# Execute the build. You can specify a specific target with "--target <NAME>"
run: cmake --build . --config $BUILD_TYPE

6
.gitignore vendored
View File

@ -1,5 +1,7 @@
build/
.vscode/
.vs/
.idea/
*.old
*.dll
*.exe
@ -7,5 +9,5 @@ build/
*.wav
.DS_Store
root_dev/
sdrpp_v0.2.5_beta_x64
sdrpp_v0.2.5_beta_x64_new_wf
Folder.DotSettings.user
CMakeSettings.json

View File

@ -1,60 +1,165 @@
cmake_minimum_required(VERSION 3.13)
project(sdrpp)
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Depedencies: soapysdr)" ON)
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Depedencies: libairspyhf)" ON)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_INSTALL_PREFIX "/usr/local")
else()
set(CMAKE_INSTALL_PREFIX "/usr")
endif()
# Sources
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Depedencies: libairspy)" ON)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" 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_AUDIO_SINK "Build Audio Sink Module (Depedencies: portaudio)" 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)
# 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)
# 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)
# 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)
# 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 ()
# Core of SDR++
add_subdirectory("core")
# Base modules
add_subdirectory("radio")
add_subdirectory("recorder")
# Source modules
if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SPYSERVER_SOURCE)
add_subdirectory("spyserver_source")
endif (OPT_BUILD_SPYSERVER_SOURCE)
if (OPT_BUILD_SOAPY_SOURCE)
add_subdirectory("soapy_source")
endif (OPT_BUILD_SOAPY_SOURCE)
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_AIRSPY_SOURCE)
add_subdirectory("airspy_source")
endif (OPT_BUILD_AIRSPY_SOURCE)
if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE)
if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_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 (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
endif (MSVC)
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)
@ -63,13 +168,18 @@ target_link_libraries(sdrpp PRIVATE sdrpp_core)
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 (MSVC)
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 ()
@ -78,4 +188,21 @@ 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" -G "Visual Studio 15 2017 Win64"
# 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

@ -3,9 +3,11 @@ project(airspy_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
endif (MSVC)
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/")
@ -17,15 +19,24 @@ set_target_properties(airspy_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
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(LIBAIRSPYHF REQUIRED libairspy)
pkg_check_modules(LIBAIRSPY REQUIRED libairspy)
target_include_directories(airspy_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
target_link_directories(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
target_link_libraries(airspy_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
endif (MSVC)
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)

View File

@ -9,6 +9,7 @@
#include <options.h>
#include <libairspy/airspy.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
@ -26,6 +27,8 @@ public:
AirspySourceModule(std::string name) {
this->name = name;
airspy_init();
sampleRate = 10000000.0;
handler.ctx = this;
@ -43,19 +46,22 @@ public:
}
// Select device from config
config.aquire();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
core::setInputSampleRate(sampleRate);
sigpath::sourceManager.registerSource("Airspy", &handler);
}
~AirspySourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("Airspy");
airspy_exit();
}
void postInit() {}
void enable() {
enabled = true;
}
@ -104,15 +110,23 @@ public:
}
void selectBySerial(uint64_t serial) {
selectedSerial = serial;
airspy_device* dev;
int err = airspy_open_sn(&dev, selectedSerial);
if (err != 0) {
char buf[1024];
sprintf(buf, "%016" PRIX64, selectedSerial);
spdlog::error("Could not open Airspy HF+ {0}", buf);
return;
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);
@ -131,7 +145,7 @@ public:
selectedSerStr = std::string(buf);
// Load config here
config.aquire();
config.acquire();
bool created = false;
if (!config.conf["devices"].contains(selectedSerStr)) {
created = true;
@ -225,11 +239,9 @@ private:
static void start(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (_this->running) {
return;
}
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
spdlog::error("Tried to start AirspyHF+ source with null serial");
spdlog::error("Tried to start Airspy source with null serial");
return;
}
@ -282,9 +294,7 @@ private:
static void stop(void* ctx) {
AirspySourceModule* _this = (AirspySourceModule*)ctx;
if (!_this->running) {
return;
}
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
airspy_close(_this->openDev);
@ -312,7 +322,7 @@ private:
_this->selectBySerial(_this->devList[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
@ -322,7 +332,7 @@ private:
_this->sampleRate = _this->sampleRateList[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
config.release(true);
}
@ -332,7 +342,7 @@ private:
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
if (ImGui::Button(CONCAT("Refresh##_airspy_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
_this->refresh();
config.aquire();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
@ -351,7 +361,7 @@ private:
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 0;
config.release(true);
}
@ -365,7 +375,7 @@ private:
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 1;
config.release(true);
}
@ -391,7 +401,7 @@ private:
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["gainMode"] = 2;
config.release(true);
}
@ -410,7 +420,7 @@ private:
airspy_set_sensitivity_gain(_this->openDev, _this->sensitiveGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sensitiveGain"] = _this->sensitiveGain;
config.release(true);
}
@ -425,7 +435,7 @@ private:
airspy_set_linearity_gain(_this->openDev, _this->linearGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["linearGain"] = _this->linearGain;
config.release(true);
}
@ -445,7 +455,7 @@ private:
airspy_set_lna_gain(_this->openDev, _this->lnaGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaGain"] = _this->lnaGain;
config.release(true);
}
@ -462,7 +472,7 @@ private:
airspy_set_mixer_gain(_this->openDev, _this->mixerGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerGain"] = _this->mixerGain;
config.release(true);
}
@ -478,7 +488,7 @@ private:
airspy_set_vga_gain(_this->openDev, _this->vgaGain);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["vgaGain"] = _this->vgaGain;
config.release(true);
}
@ -496,7 +506,7 @@ private:
}
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lnaAgc"] = _this->lnaAgc;
config.release(true);
}
@ -512,7 +522,7 @@ private:
}
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["mixerAgc"] = _this->mixerAgc;
config.release(true);
}
@ -526,7 +536,7 @@ private:
airspy_set_rf_bias(_this->openDev, _this->biasT);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["biasT"] = _this->biasT;
config.release(true);
}

View File

@ -3,9 +3,11 @@ project(airspyhf_source)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
endif (MSVC)
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/")
@ -17,7 +19,7 @@ set_target_properties(airspyhf_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_directories(airspyhf_source PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(airspyhf_source PUBLIC airspyhf)
else (MSVC)
@ -28,4 +30,13 @@ else (MSVC)
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})
endif (MSVC)
# 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

@ -8,6 +8,7 @@
#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())
@ -41,19 +42,21 @@ public:
refresh();
config.aquire();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
selectByString(devSerial);
core::setInputSampleRate(sampleRate);
sigpath::sourceManager.registerSource("Airspy HF+", &handler);
}
~AirspyHFSourceModule() {
stop(this);
sigpath::sourceManager.unregisterSource("Airspy HF+");
}
void postInit() {}
enum AGCMode {
AGC_MODE_OFF,
AGC_MODE_LOW,
@ -108,15 +111,24 @@ public:
}
void selectBySerial(uint64_t serial) {
selectedSerial = serial;
airspyhf_device_t* dev;
int err = airspyhf_open_sn(&dev, selectedSerial);
if (err != 0) {
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);
return;
}
selectedSerial = serial;
uint32_t sampleRates[256];
airspyhf_get_samplerates(dev, sampleRates, 0);
@ -135,7 +147,7 @@ public:
selectedSerStr = std::string(buf);
// Load config here
config.aquire();
config.acquire();
bool created = false;
if (!config.conf["devices"].contains(selectedSerStr)) {
created = true;
@ -203,9 +215,7 @@ private:
static void start(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (_this->running) {
return;
}
if (_this->running) { return; }
if (_this->selectedSerial == 0) {
spdlog::error("Tried to start AirspyHF+ source with null serial");
return;
@ -225,7 +235,7 @@ private:
if (_this->agcMode > 0) {
airspyhf_set_hf_agc_threshold(_this->openDev, _this->agcMode - 1);
}
airspyhf_set_hf_att(_this->openDev, _this->atten / 6);
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
airspyhf_start(_this->openDev, callback, _this);
@ -236,9 +246,7 @@ private:
static void stop(void* ctx) {
AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx;
if (!_this->running) {
return;
}
if (!_this->running) { return; }
_this->running = false;
_this->stream.stopWriter();
airspyhf_close(_this->openDev);
@ -266,7 +274,7 @@ private:
_this->selectBySerial(_this->devList[_this->devId]);
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["device"] = _this->selectedSerStr;
config.release(true);
}
@ -276,7 +284,7 @@ private:
_this->sampleRate = _this->sampleRateList[_this->srId];
core::setInputSampleRate(_this->sampleRate);
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["sampleRate"] = _this->sampleRate;
config.release(true);
}
@ -286,7 +294,7 @@ private:
float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX();
if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) {
_this->refresh();
config.aquire();
config.acquire();
std::string devSerial = config.conf["device"];
config.release();
_this->selectByString(devSerial);
@ -306,7 +314,7 @@ private:
}
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["agcMode"] = _this->agcMode;
config.release(true);
}
@ -319,7 +327,7 @@ private:
airspyhf_set_hf_lna(_this->openDev, _this->hfLNA);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["lna"] = _this->hfLNA;
config.release(true);
}
@ -328,13 +336,12 @@ private:
ImGui::Text("Attenuation");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderInt(CONCAT("##_airspyhf_attn_", _this->name), &_this->atten, 0, 48, "%d dB")) {
_this->atten = (_this->atten / 6) * 6;
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);
airspyhf_set_hf_att(_this->openDev, _this->atten / 6.0f);
}
if (_this->selectedSerStr != "") {
config.aquire();
config.acquire();
config.conf["devices"][_this->selectedSerStr]["attenuation"] = _this->atten;
config.release(true);
}
@ -361,7 +368,7 @@ private:
int srId = 0;
int agcMode = AGC_MODE_OFF;
bool hfLNA = false;
int atten = 0;
float atten = 0.0f;
std::string selectedSerStr = "";
std::vector<uint64_t> devList;

View File

@ -3,9 +3,11 @@ project(audio_sink)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fpermissive")
endif (MSVC)
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")
@ -16,17 +18,23 @@ target_link_libraries(audio_sink PRIVATE sdrpp_core)
set_target_properties(audio_sink PROPERTIES PREFIX "")
if (MSVC)
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC portaudio)
# 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(PORTAUDIO REQUIRED portaudio-2.0)
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
target_include_directories(sdrpp_core PUBLIC ${PORTAUDIO_INCLUDE_DIRS})
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})
target_link_directories(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARY_DIRS})
endif ()
target_link_libraries(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARIES})
endif (MSVC)
# Install directives
install(TARGETS audio_sink DESTINATION lib/sdrpp/plugins)

View File

@ -3,9 +3,12 @@
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <portaudio.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())
@ -17,81 +20,46 @@ SDRPP_MOD_INFO {
/* Max instances */ 1
};
ConfigManager config;
class AudioSink : SinkManager::Sink {
public:
struct AudioDevice_t {
std::string name;
int index;
int channels;
int srId;
std::vector<double> sampleRates;
std::string txtSampleRates;
};
AudioSink(SinkManager::Stream* stream, std::string streamName) {
_stream = stream;
_streamName = streamName;
s2m.init(_stream->sinkOut);
monoRB.init(&s2m.out);
stereoRB.init(_stream->sinkOut);
monoPacker.init(&s2m.out, 512);
stereoPacker.init(_stream->sinkOut, 512);
// Initialize PortAudio
devCount = Pa_GetDeviceCount();
devId = Pa_GetDefaultOutputDevice();
const PaDeviceInfo *deviceInfo;
PaStreamParameters outputParams;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
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);
// Gather hardware info
for(int i = 0; i < devCount; i++) {
deviceInfo = Pa_GetDeviceInfo(i);
if (deviceInfo->maxOutputChannels < 1) {
continue;
}
AudioDevice_t dev;
dev.name = deviceInfo->name;
dev.index = i;
dev.channels = std::min<int>(deviceInfo->maxOutputChannels, 2);
dev.sampleRates.clear();
dev.txtSampleRates = "";
for (int j = 0; j < 6; j++) {
outputParams.channelCount = dev.channels;
outputParams.device = dev.index;
outputParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
PaError err = Pa_IsFormatSupported(NULL, &outputParams, POSSIBLE_SAMP_RATE[j]);
if (err != paFormatIsSupported) {
continue;
}
dev.sampleRates.push_back(POSSIBLE_SAMP_RATE[j]);
dev.txtSampleRates += std::to_string((int)POSSIBLE_SAMP_RATE[j]);
dev.txtSampleRates += '\0';
}
if (dev.sampleRates.size() == 0) {
continue;
}
if (i == devId) {
devListId = devices.size();
defaultDev = devListId;
}
dev.srId = 0;
AudioDevice_t* _dev = new AudioDevice_t;
*_dev = dev;
devices.push_back(_dev);
deviceNames.push_back(deviceInfo->name);
txtDevList += deviceInfo->name;
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';
}
// Load config from file
selectByName(device);
}
~AudioSink() {
for (auto const& dev : devices) {
delete dev;
}
}
void start() {
@ -109,127 +77,164 @@ public:
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(), &devListId, txtDevList.c_str())) {
// TODO: Load SR from config
if (running) {
doStop();
doStart();
}
// TODO: Save to config
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);
}
AudioDevice_t* dev = devices[devListId];
ImGui::SetNextItemWidth(menuWidth);
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &dev->srId, dev->txtSampleRates.c_str())) {
_stream->setSampleRate(dev->sampleRates[dev->srId]);
if (ImGui::Combo(("##_audio_sink_sr_"+_streamName).c_str(), &srId, sampleRatesTxt.c_str())) {
sampleRate = sampleRates[srId];
_stream->setSampleRate(sampleRate);
if (running) {
doStop();
doStart();
}
// TODO: Save to config
config.acquire();
config.conf[_streamName]["devices"][devList[devId].name] = sampleRate;
config.release(true);
}
}
private:
void doStart() {
const PaDeviceInfo *deviceInfo;
AudioDevice_t* dev = devices[devListId];
PaStreamParameters outputParams;
deviceInfo = Pa_GetDeviceInfo(dev->index);
outputParams.channelCount = 2;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
outputParams.device = dev->index;
outputParams.suggestedLatency = Pa_GetDeviceInfo(outputParams.device)->defaultLowOutputLatency;
PaError err;
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;
float sampleRate = dev->sampleRates[dev->srId];
int bufferSize = sampleRate / 60.0f;
if (dev->channels == 2) {
stereoRB.data.setMaxLatency(bufferSize * 2);
stereoRB.start();
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _stereo_cb, this);
try {
audio.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &callback, this, &opts);
stereoPacker.setSampleCount(bufferFrames);
audio.startStream();
stereoPacker.start();
}
else {
monoRB.data.setMaxLatency(bufferSize * 2);
monoRB.start();
err = Pa_OpenStream(&stream, NULL, &outputParams, sampleRate, paFramesPerBufferUnspecified, 0, _mono_cb, this);
}
if (err != 0) {
spdlog::error("Error while opening audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
catch ( RtAudioError& e ) {
spdlog::error("Could not open audio device");
return;
}
err = Pa_StartStream(stream);
if (err != 0) {
spdlog::error("Error while starting audio stream: ({0}) => {1}", err, Pa_GetErrorText(err));
return;
}
spdlog::info("Audio device open.");
running = true;
spdlog::info("RtAudio stream open");
}
void doStop() {
s2m.stop();
monoRB.stop();
stereoRB.stop();
monoRB.data.stopReader();
stereoRB.data.stopReader();
Pa_StopStream(stream);
Pa_CloseStream(stream);
monoRB.data.clearReadStop();
stereoRB.data.clearReadStop();
monoPacker.stop();
stereoPacker.stop();
monoPacker.out.stopReader();
stereoPacker.out.stopReader();
audio.stopStream();
audio.closeStream();
monoPacker.out.clearReadStop();
stereoPacker.out.clearReadStop();
}
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
static int callback( void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->monoRB.data.read((float*)output, frameCount);
return 0;
}
int count = _this->stereoPacker.out.read();
if (count < 0) { return 0; }
static int _stereo_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->stereoRB.data.read((dsp::stereo_t*)output, frameCount);
// 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::RingBufferSink<float> monoRB;
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
dsp::Packer<float> monoPacker;
dsp::Packer<dsp::stereo_t> stereoPacker;
std::string _streamName;
PaStream *stream;
int srId = 0;
int devCount;
int devId = 0;
int devListId = 0;
int defaultDev = 0;
bool running = false;
const double POSSIBLE_SAMP_RATE[6] = {
48000.0f,
44100.0f,
24000.0f,
22050.0f,
12000.0f,
11025.0f
};
unsigned int defaultDevId = 0;
std::vector<AudioDevice_t*> devices;
std::vector<std::string> deviceNames;
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 {
@ -239,15 +244,16 @@ public:
provider.create = create_sink;
provider.ctx = this;
Pa_Initialize();
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
Pa_Terminate();
// Unregister sink, this will automatically stop and delete all instances of the audio sink
sigpath::sinkManager.unregisterSinkProvider("Audio");
}
void postInit() {}
void enable() {
enabled = true;
}
@ -272,8 +278,10 @@ private:
};
MOD_EXPORT void _INIT_() {
// Nothing here
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
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) {
@ -281,10 +289,11 @@ MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
return instance;
}
MOD_EXPORT void _DELETE_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

View File

@ -5,9 +5,11 @@ project(sdrpp_core)
if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
else()
set(CMAKE_CXX_FLAGS "-g -O3 -std=c++17 -fpermissive")
endif (MSVC)
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
@ -16,6 +18,9 @@ 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")
@ -43,6 +48,9 @@ if (MSVC)
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)
@ -88,3 +96,6 @@ 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)

View File

@ -1,6 +1,7 @@
#include <config.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <filesystem>
ConfigManager::ConfigManager() {
@ -46,73 +47,49 @@ void ConfigManager::save(bool lock) {
}
void ConfigManager::enableAutoSave() {
if (!autoSaveEnabled) {
autoSaveEnabled = true;
autoSaveThread = std::thread(autoSaveWorker, this);
}
if (autoSaveEnabled) { return; }
autoSaveEnabled = true;
termFlag = false;
autoSaveThread = std::thread(&ConfigManager::autoSaveWorker, this);
}
void ConfigManager::disableAutoSave() {
if (autoSaveEnabled) {
if (!autoSaveEnabled) { return; }
{
std::lock_guard<std::mutex> lock(termMtx);
autoSaveEnabled = false;
autoSaveThread.join();
termFlag = true;
}
termCond.notify_one();
if (autoSaveThread.joinable()) { autoSaveThread.join(); }
}
void ConfigManager::aquire() {
void ConfigManager::acquire() {
mtx.lock();
}
void ConfigManager::release(bool changed) {
this->changed |= changed;
void ConfigManager::release(bool modified) {
changed |= modified;
mtx.unlock();
}
void ConfigManager::autoSaveWorker(ConfigManager* _this) {
while (_this->autoSaveEnabled) {
if (!_this->mtx.try_lock()) {
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 (_this->changed) {
_this->changed = false;
_this->save(false);
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; } );
}
_this->mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
// void ConfigManager::setResourceDir(std::string path) {
// if (!std::filesystem::exists(path)) {
// spdlog::error("Resource directory '{0}' does not exist", path);
// return;
// }
// if (!std::filesystem::is_regular_file(path)) {
// spdlog::error("Resource directory '{0}' is not a directory", path);
// return;
// }
// resDir = path;
// }
// std::string ConfigManager::getResourceDir() {
// return resDir;
// }
// void ConfigManager::setConfigDir(std::string path) {
// if (!std::filesystem::exists(path)) {
// spdlog::error("Resource directory '{0}' does not exist", path);
// return;
// }
// if (!std::filesystem::is_regular_file(path)) {
// spdlog::error("Resource directory '{0}' is not a directory", path);
// return;
// }
// resDir = path;
// }
// std::string ConfigManager::getConfigDir() {
// return configDir;
// }
}

View File

@ -3,6 +3,7 @@
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
using nlohmann::json;
@ -15,18 +16,22 @@ public:
void save(bool lock = true);
void enableAutoSave();
void disableAutoSave();
void aquire();
void release(bool changed = false);
void acquire();
void release(bool modified = false);
json conf;
private:
static void autoSaveWorker(ConfigManager* _this);
void autoSaveWorker();
std::string path = "";
bool changed = false;
bool autoSaveEnabled = false;
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;
};

View File

@ -14,10 +14,11 @@
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <glfw_window.h>
#include <options.h>
#include <duktape/duktape.h>
#include <duktape/duk_console.h>
#include <filesystem>
#include <gui/menus/theme.h>
#include <server.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
@ -28,18 +29,30 @@
#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;
ScriptManager scriptManager;
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
gui::waterfall.setBandwidth(samplerate);
spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate);
gui::waterfall.setBandwidth(effectiveSr);
gui::waterfall.setViewOffset(0);
gui::waterfall.setViewBandwidth(samplerate);
sigpath::signalPath.setSampleRate(samplerate);
setViewBandwidthSlider(samplerate);
gui::waterfall.setViewBandwidth(effectiveSr);
sigpath::signalPath.setSampleRate(effectiveSr);
gui::mainWindow.setViewBandwidthSlider(effectiveSr);
}
};
@ -59,25 +72,18 @@ static void maximized_callback(GLFWwindow* window, int n) {
}
}
duk_ret_t test_func(duk_context *ctx) {
printf("Hello from C++\n");
return 1;
}
// main
int sdrpp_main(int argc, char *argv[]) {
#ifdef _WIN32
//FreeConsole();
// ConfigManager::setResourceDir("./res");
// ConfigManager::setConfigDir(".");
#endif
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);
@ -101,55 +107,116 @@ int sdrpp_main(int argc, char *argv[]) {
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;
defConfig["menuOrder"] = {
"Source",
"Radio",
"Recorder",
"Sinks",
"Audio",
"Scripting",
"Band Plan",
"Display"
};
defConfig["menuWidth"] = 300;
defConfig["min"] = -70.0;
defConfig["moduleInstances"]["Radio"] = "radio";
defConfig["moduleInstances"]["Recorder"] = "recorder";
defConfig["moduleInstances"]["SoapySDR Source"] = "soapy_source";
defConfig["moduleInstances"]["PlutoSDR Source"] = "plutosdr_source";
defConfig["moduleInstances"]["RTL-TCP Source"] = "rtl_tcp_source";
defConfig["moduleInstances"]["AirspyHF+ Source"] = "airspyhf_source";
defConfig["moduleInstances"]["Airspy Source"] = "airspy_source";
defConfig["moduleInstances"]["HackRF Source"] = "hackrf_source";
// 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["streams"] = json::object();
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["bandColors"]["broadcast"] = "#0000FFFF";
defConfig["bandColors"]["amateur"] = "#FF0000FF";
defConfig["bandColors"]["aviation"] = "#00FF00FF";
defConfig["bandColors"]["marine"] = "#00FFFFFF";
defConfig["bandColors"]["military"] = "#FFFF00FF";
defConfig["vfoOffsets"] = json::object();
defConfig["vfoColors"]["Radio"] = "#FFFFFF";
#ifdef _WIN32
defConfig["modulesDirectory"] = "./modules";
defConfig["resourcesDirectory"] = "./res";
#else
defConfig["modulesDirectory"] = "/usr/lib/sdrpp/plugins";
defConfig["resourcesDirectory"] = "/usr/share/sdrpp";
defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins";
defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp";
#endif
// Load config
@ -158,16 +225,39 @@ int sdrpp_main(int argc, char *argv[]) {
core::configManager.load(defConfig);
core::configManager.enableAutoSave();
// Fix config
core::configManager.aquire();
core::configManager.acquire();
// Fix missing elements in config
for (auto const& item : defConfig.items()) {
if (!core::configManager.conf.contains(item.key())) {
spdlog::warn("Missing key in config {0}, repairing", 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()) {
@ -187,8 +277,8 @@ int sdrpp_main(int argc, char *argv[]) {
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
core::configManager.aquire();
core::configManager.acquire();
int winWidth = core::configManager.conf["windowSize"]["w"];
int winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
@ -196,22 +286,32 @@ int sdrpp_main(int argc, char *argv[]) {
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();
GLFWwindow* window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (window == NULL)
core::window = glfwCreateWindow(winWidth, winHeight, "SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")", NULL, NULL);
if (core::window == NULL)
return 1;
glfwMakeContextCurrent(window);
glfwMakeContextCurrent(core::window);
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(window);
glfwMaximizeWindow(core::window);
}
glfwSetWindowMaximizeCallback(window, maximized_callback);
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;
@ -232,7 +332,7 @@ int sdrpp_main(int argc, char *argv[]) {
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(window, 10, icons);
glfwSetWindowIcon(core::window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
@ -251,12 +351,21 @@ int sdrpp_main(int argc, char *argv[]) {
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
ImGui_ImplGlfw_InitForOpenGL(core::window, true);
if (!style::setDarkStyle(resDir)) { return -1; }
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;
}
}
LoadingScreen::setWindow(window);
if (!style::loadFonts(resDir)) { return -1; }
thememenu::init(resDir);
LoadingScreen::setWindow(core::window);
LoadingScreen::show("Loading icons");
spdlog::info("Loading icons");
@ -270,7 +379,7 @@ int sdrpp_main(int argc, char *argv[]) {
spdlog::info("Loading band plans color table");
bandplan::loadColorTable(bandColors);
windowInit();
gui::mainWindow.init();
spdlog::info("Ready.");
@ -278,7 +387,7 @@ int sdrpp_main(int argc, char *argv[]) {
int fsWidth, fsHeight, fsPosX, fsPosY;
// Main loop
while (!glfwWindowShouldClose(window)) {
while (!glfwWindowShouldClose(core::window)) {
glfwPollEvents();
// Start the Dear ImGui frame
@ -290,16 +399,16 @@ int sdrpp_main(int argc, char *argv[]) {
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.aquire();
core::configManager.acquire();
core::configManager.conf["maximized"]= _maximized;
if (!maximized) {
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
glfwSetWindowSize(core::window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
int _winWidth, _winHeight;
glfwGetWindowSize(window, &_winWidth, &_winHeight);
glfwGetWindowSize(core::window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
@ -307,20 +416,20 @@ int sdrpp_main(int argc, char *argv[]) {
spdlog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(window, &fsPosX, &fsPosY);
glfwGetWindowPos(core::window, &fsPosX, &fsPosY);
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
glfwSetWindowMonitor(core::window, monitor, 0, 0, mode->width, mode->height, 0);
}
else {
spdlog::info("Fullscreen: OFF");
glfwSetWindowMonitor(window, nullptr, fsPosX, fsPosY, fsWidth, fsHeight, 0);
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.aquire();
core::configManager.acquire();
core::configManager.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
@ -329,21 +438,26 @@ int sdrpp_main(int argc, char *argv[]) {
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
drawWindow();
gui::mainWindow.draw();
}
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(window, &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(0.9f, 0.9f, 0.9f, 1.0f);
//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(window);
glfwSwapBuffers(core::window);
}
// Shut down all modules
for (auto& [name, mod] : core::moduleManager.modules) {
mod.end();
}
// Cleanup
@ -351,8 +465,13 @@ int sdrpp_main(int argc, char *argv[]) {
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwDestroyWindow(core::window);
glfwTerminate();
sigpath::signalPath.stop();
core::configManager.disableAutoSave();
core::configManager.save();
return 0;
}

View File

@ -1,13 +1,13 @@
#pragma once
#include <config.h>
#include <module.h>
#include <scripting.h>
#include <module.h>
#include <module_com.h>
namespace core {
SDRPP_EXPORT ConfigManager configManager;
SDRPP_EXPORT ScriptManager scriptManager;
SDRPP_EXPORT ModuleManager moduleManager;
SDRPP_EXPORT ModuleComManager modComManager;
void setInputSampleRate(double samplerate);
};

View File

@ -2,32 +2,50 @@
namespace sdrpp_credits {
const char* contributors[] = {
"Ryzerth (Author)",
"aosync",
"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"
"Tobias Mädel",
"Zimm"
};
const char* libraries[] = {
"Dear ImGui (ocornut)",
"fftw3 (fftw.org)",
"glew (Nigel Stewart)",
"glfw (Camilla Löwy)",
"json (nlohmann)",
"portaudio (P.A. comm.)",
"SoapySDR (PothosWare)",
"spdlog (gabime)",
"Portable File Dialogs"
};
const char* patrons[] = {
"Croccydile",
"Daniele D'Agnelli",
"Eric Johnson",
"W4IPA",
"Lee Donaghy",
"ON4MU",
"Passion-Radio.com",
"Scanner School",
"SignalsEverywhere",
"Lee Donaghy"
"Syne Ardwin (WI9SYN)"
};
const int contributorCount = sizeof(contributors) / sizeof(char*);
const int libraryCount = sizeof(libraries) / sizeof(char*);
const int patronCount = sizeof(patrons) / sizeof(char*);
}
}

View File

@ -8,15 +8,15 @@ namespace dsp {
MonoToStereo(stream<float>* in) { init(in); }
~MonoToStereo() { generic_block<MonoToStereo>::stop(); }
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);
@ -26,13 +26,10 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < count; i++) {
out.writeBuf[i].l = _in->readBuf[i];
out.writeBuf[i].r = _in->readBuf[i];
}
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, _in->readBuf, count);
_in->flush();
if (!out.swap(count)) { return -1; }
@ -42,26 +39,93 @@ namespace dsp {
stream<stereo_t> out;
private:
int count;
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() { generic_block<StereoToMono>::stop(); }
~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);
@ -71,14 +135,15 @@ namespace dsp {
}
int run() {
count = _in->read();
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) / 2.0f;
out.writeBuf[i] = (_in->readBuf[i].l + _in->readBuf[i].r) * 0.5f;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
@ -86,7 +151,51 @@ namespace dsp {
stream<float> out;
private:
int count;
float* l_buf, *r_buf;
stream<stereo_t>* _in;
};
class StereoToChannels : public generic_block<StereoToChannels> {
public:
StereoToChannels() {}
StereoToChannels(stream<stereo_t>* in) { init(in); }
void init(stream<stereo_t>* in) {
_in = in;
generic_block<StereoToChannels>::registerInput(_in);
generic_block<StereoToChannels>::registerOutput(&out_left);
generic_block<StereoToChannels>::registerOutput(&out_right);
generic_block<StereoToChannels>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
assert(generic_block<StereoToChannels>::_block_init);
std::lock_guard<std::mutex> lck(generic_block<StereoToChannels>::ctrlMtx);
generic_block<StereoToChannels>::tempStop();
generic_block<StereoToChannels>::unregisterInput(_in);
_in = in;
generic_block<StereoToChannels>::registerInput(_in);
generic_block<StereoToChannels>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_32f_x2(out_left.writeBuf, out_right.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if (!out_left.swap(count)) { return -1; }
if (!out_right.swap(count)) { return -1; }
return count;
}
stream<float> out_left;
stream<float> out_right;
private:
stream<stereo_t>* _in;
};

View File

@ -8,20 +8,29 @@
#include <spdlog/spdlog.h>
#define FL_M_PI 3.1415926535f
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 {
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;
@ -31,6 +40,7 @@ namespace dsp {
}
virtual void stop() {
assert(_block_init);
std::lock_guard<std::mutex> lck(ctrlMtx);
if (!running) {
return;
@ -39,7 +49,26 @@ namespace dsp {
running = false;
}
virtual int calcOutSize(int inSize) { return inSize; }
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;
@ -50,7 +79,7 @@ namespace dsp {
while (run() >= 0);
}
void aquire() {
void acquire() {
ctrlMtx.lock();
}
@ -58,19 +87,19 @@ namespace dsp {
ctrlMtx.unlock();
}
void registerInput(untyped_steam* inStream) {
void registerInput(untyped_stream* inStream) {
inputs.push_back(inStream);
}
void unregisterInput(untyped_steam* inStream) {
void unregisterInput(untyped_stream* inStream) {
inputs.erase(std::remove(inputs.begin(), inputs.end(), inStream), inputs.end());
}
void registerOutput(untyped_steam* outStream) {
void registerOutput(untyped_stream* outStream) {
outputs.push_back(outStream);
}
void unregisterOutput(untyped_steam* outStream) {
void unregisterOutput(untyped_stream* outStream) {
outputs.erase(std::remove(outputs.begin(), outputs.end(), outStream), outputs.end());
}
@ -99,7 +128,53 @@ namespace dsp {
}
}
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;
@ -107,21 +182,47 @@ namespace dsp {
}
void tempStop() {
assert(_block_init);
if (running && !tempStopped) {
doStop();
tempStopped = true;
}
}
std::vector<untyped_steam*> inputs;
std::vector<untyped_steam*> outputs;
virtual int calcOutSize(int inSize) {
assert(_block_init);
return inSize;
}
bool running = false;
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;
std::thread workerThread;
bool running = false;
protected:
bool _block_init = false;
std::mutex ctrlMtx;
};

View File

@ -8,13 +8,15 @@ namespace dsp {
template <class T>
class RingBuffer {
public:
RingBuffer() {
}
RingBuffer() {}
RingBuffer(int maxLatency) { init(maxLatency); }
~RingBuffer() { delete _buffer; }
~RingBuffer() {
if (!_init) { return; }
delete _buffer;
_init = false;
}
void init(int maxLatency) {
size = RING_BUF_SZ;
@ -27,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) {
@ -59,6 +63,7 @@ namespace dsp {
}
int readAndSkip(T* data, int len, int skip) {
assert(_init);
int dataRead = 0;
int toRead = 0;
while (dataRead < len) {
@ -104,6 +109,7 @@ namespace dsp {
}
int waitUntilReadable() {
assert(_init);
if (_stopReader) { return -1; }
int _r = getReadable();
if (_r != 0) { return _r; }
@ -114,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(); };
@ -121,6 +128,7 @@ namespace dsp {
}
int write(T* data, int len) {
assert(_init);
int dataWritten = 0;
int toWrite = 0;
while (dataWritten < len) {
@ -151,6 +159,7 @@ namespace dsp {
}
int waitUntilwritable() {
assert(_init);
if (_stopWriter) { return -1; }
int _w = getWritable();
if (_w != 0) { return _w; }
@ -161,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(); };
@ -170,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;
@ -214,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;
};
}

View File

@ -8,17 +8,17 @@ namespace dsp {
ComplexToStereo(stream<complex_t>* in) { init(in); }
~ComplexToStereo() { generic_block<ComplexToStereo>::stop(); }
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);
@ -28,7 +28,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
@ -41,8 +41,6 @@ namespace dsp {
stream<stereo_t> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
@ -53,15 +51,15 @@ namespace dsp {
ComplexToReal(stream<complex_t>* in) { init(in); }
~ComplexToReal() { generic_block<ComplexToReal>::stop(); }
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);
@ -71,7 +69,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
@ -84,8 +82,6 @@ namespace dsp {
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
@ -96,15 +92,15 @@ namespace dsp {
ComplexToImag(stream<complex_t>* in) { init(in); }
~ComplexToImag() { generic_block<ComplexToImag>::stop(); }
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);
@ -114,7 +110,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
@ -127,8 +123,6 @@ namespace dsp {
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
@ -141,8 +135,10 @@ namespace dsp {
RealToComplex(stream<float>* in) { init(in); }
~RealToComplex() {
delete[] nullBuffer;
if (!generic_block<RealToComplex>::_block_init) { return; }
generic_block<RealToComplex>::stop();
delete[] nullBuffer;
generic_block<RealToComplex>::_block_init = false;
}
void init(stream<float>* in) {
@ -151,9 +147,11 @@ namespace dsp {
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);
@ -163,7 +161,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count);
@ -176,10 +174,172 @@ namespace dsp {
stream<complex_t> out;
private:
float avg;
int count;
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;
};
}

View File

@ -1,8 +1,16 @@
#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
@ -22,69 +30,72 @@ inline float fast_arctan2(float y, float x) {
if (y < 0.0f) {
return -angle;
}
return angle;
return angle;
}
namespace dsp {
class FMDemod : public generic_block<FMDemod> {
class FloatFMDemod : public generic_block<FloatFMDemod> {
public:
FMDemod() {}
FloatFMDemod() {}
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
~FMDemod() { generic_block<FMDemod>::stop(); }
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<FMDemod>::registerInput(_in);
generic_block<FMDemod>::registerOutput(&out);
generic_block<FloatFMDemod>::registerInput(_in);
generic_block<FloatFMDemod>::registerOutput(&out);
generic_block<FloatFMDemod>::_block_init = true;
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
generic_block<FMDemod>::unregisterInput(_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<FMDemod>::registerInput(_in);
generic_block<FMDemod>::tempStart();
generic_block<FloatFMDemod>::registerInput(_in);
generic_block<FloatFMDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
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<FMDemod>::tempStart();
generic_block<FloatFMDemod>::tempStart();
}
float getSampleRate() {
assert(generic_block<FloatFMDemod>::_block_init);
return _sampleRate;
}
void setDeviation(float deviation) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
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<FMDemod>::tempStart();
generic_block<FloatFMDemod>::tempStart();
}
float getDeviation() {
assert(generic_block<FloatFMDemod>::_block_init);
return _deviation;
}
int run() {
count = _in->read();
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].i, _in->readBuf[i].q);
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; }
@ -100,8 +111,90 @@ namespace dsp {
stream<float> out;
private:
int count;
float phase, phasorSpeed, _sampleRate, _deviation;
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;
};
@ -112,15 +205,15 @@ namespace dsp {
AMDemod(stream<complex_t>* in) { init(in); }
~AMDemod() { generic_block<AMDemod>::stop(); }
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);
@ -130,18 +223,16 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_magnitude_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
volk_32f_accumulator_s32f(&avg, out.writeBuf, count);
avg /= (float)count;
for (int i = 0; i < count; i++) {
out.writeBuf[i] -= avg;
avg += out.writeBuf[i] * 10e-4;
}
if (!out.swap(count)) { return -1; }
@ -151,9 +242,8 @@ namespace dsp {
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
float avg = 0;
};
@ -164,8 +254,10 @@ namespace dsp {
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 {
@ -194,9 +286,11 @@ namespace dsp {
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);
@ -206,7 +300,7 @@ namespace dsp {
}
void setSampleRate(float sampleRate) {
// No need to restart
assert(generic_block<SSBDemod>::_block_init);
_sampleRate = sampleRate;
switch (_mode) {
case MODE_USB:
@ -222,7 +316,7 @@ namespace dsp {
}
void setBandWidth(float bandWidth) {
// No need to restart
assert(generic_block<SSBDemod>::_block_init);
_bandWidth = bandWidth;
switch (_mode) {
case MODE_USB:
@ -238,6 +332,7 @@ namespace dsp {
}
void setMode(int mode) {
assert(generic_block<SSBDemod>::_block_init);
_mode = mode;
switch (_mode) {
case MODE_USB:
@ -253,7 +348,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
@ -267,7 +362,6 @@ namespace dsp {
stream<float> out;
private:
int count;
int _mode;
float _sampleRate, _bandWidth;
stream<complex_t>* _in;
@ -276,4 +370,360 @@ namespace dsp {
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;
};
}

View File

@ -13,9 +13,11 @@ namespace dsp {
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) {
@ -29,9 +31,11 @@ namespace dsp {
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);
@ -41,17 +45,22 @@ namespace dsp {
}
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() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
generic_block<FIR<T>>::ctrlMtx.lock();
memcpy(bufStart, _in->readBuf, count * sizeof(T));
_in->flush();
@ -70,13 +79,14 @@ namespace dsp {
memmove(buffer, &buffer[count], tapCount * sizeof(T));
generic_block<FIR<T>>::ctrlMtx.unlock();
return count;
}
stream<T> out;
private:
int count;
stream<T>* _in;
dsp::filter_window::generic_window* _window;
@ -88,15 +98,98 @@ namespace dsp {
};
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<float>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
BFMDeemp(stream<stereo_t>* in, float sampleRate, float tau) { init(in, sampleRate, tau); }
~BFMDeemp() { generic_block<BFMDeemp>::stop(); }
void init(stream<float>* in, float sampleRate, float tau) {
void init(stream<stereo_t>* in, float sampleRate, float tau) {
_in = in;
_sampleRate = sampleRate;
_tau = tau;
@ -104,9 +197,11 @@ namespace dsp {
alpha = dt / (_tau + dt);
generic_block<BFMDeemp>::registerInput(_in);
generic_block<BFMDeemp>::registerOutput(&out);
generic_block<BFMDeemp>::_block_init = true;
}
void setInput(stream<float>* in) {
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);
@ -116,12 +211,14 @@ namespace dsp {
}
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);
@ -132,20 +229,26 @@ namespace dsp {
if (count < 0) { return -1; }
if (bypass) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(float));
memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
if (isnan(lastOut)) {
lastOut = 0.0f;
if (isnan(lastOutL)) {
lastOutL = 0.0f;
}
out.writeBuf[0] = (alpha * _in->readBuf[0]) + ((1-alpha) * lastOut);
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] = (alpha * _in->readBuf[i]) + ((1 - alpha) * out.writeBuf[i - 1]);
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);
}
lastOut = out.writeBuf[count - 1];
lastOutL = out.writeBuf[count - 1].l;
lastOutR = out.writeBuf[count - 1].r;
_in->flush();
if (!out.swap(count)) { return -1; }
@ -154,15 +257,16 @@ namespace dsp {
bool bypass = false;
stream<float> out;
stream<stereo_t> out;
private:
int count;
float lastOut = 0.0f;
float lastOutL = 0.0f;
float lastOutR = 0.0f;
float alpha;
float _tau;
float _sampleRate;
stream<float>* _in;
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 },
};

View File

@ -10,20 +10,52 @@ namespace dsp {
Add(stream<T>* a, stream<T>* b) { init(a, b); }
~Add() { generic_block<Add>::stop(); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Add>::registerInput(a);
generic_block<Add>::registerInput(b);
generic_block<Add>::registerOutput(&out);
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() {
a_count = _a->read();
int a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
int b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
@ -32,7 +64,7 @@ namespace dsp {
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
volk_32fc_x2_add_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
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);
@ -47,7 +79,87 @@ namespace dsp {
stream<T> out;
private:
int a_count, b_count;
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;
@ -60,20 +172,52 @@ namespace dsp {
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
~Multiply() { generic_block<Multiply>::stop(); }
void init(stream<T>* a, stream<T>* b) {
_a = a;
_b = b;
generic_block<Multiply>::registerInput(a);
generic_block<Multiply>::registerInput(b);
generic_block<Multiply>::registerOutput(&out);
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() {
a_count = _a->read();
int a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
int b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
@ -82,7 +226,7 @@ namespace dsp {
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_x2_multiply_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
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);
@ -97,7 +241,6 @@ namespace dsp {
stream<T> out;
private:
int a_count, b_count;
stream<T>* _a;
stream<T>* _b;

View File

@ -7,93 +7,76 @@
#include <string.h>
namespace dsp {
class VolumeMeasure : public generic_block<VolumeMeasure> {
class LevelMeter : public generic_block<LevelMeter> {
public:
VolumeMeasure() {}
LevelMeter() {}
VolumeMeasure(stream<stereo_t>* in) { init(in); }
~VolumeMeasure() {
generic_block<VolumeMeasure>::stop();
delete[] leftBuf;
delete[] rightBuf;
}
LevelMeter(stream<stereo_t>* in) { init(in); }
void init(stream<stereo_t>* in) {
_in = in;
leftBuf = new float[STREAM_BUFFER_SIZE];
rightBuf = new float[STREAM_BUFFER_SIZE];
generic_block<VolumeMeasure>::registerInput(_in);
generic_block<VolumeMeasure>::registerOutput(&out);
generic_block<LevelMeter>::registerInput(_in);
generic_block<LevelMeter>::_block_init = true;
}
void setInput(stream<stereo_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<VolumeMeasure>::ctrlMtx);
generic_block<VolumeMeasure>::tempStop();
generic_block<VolumeMeasure>::unregisterInput(_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<VolumeMeasure>::registerInput(_in);
generic_block<VolumeMeasure>::tempStart();
generic_block<LevelMeter>::registerInput(_in);
generic_block<LevelMeter>::tempStart();
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
memcpy(out.writeBuf, _in->readBuf, count * sizeof(stereo_t));
volk_32fc_deinterleave_32f_x2(leftBuf, rightBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if (!out.swap(count)) { return -1; }
// Get peak from last value
float time = (float)count / sampleRate;
peak.l -= peakFall * time;
peak.r -= peakFall * time;
stereo_t _peak;
_peak.l = powf(10, peak.l / 10.0f);
_peak.r = powf(10, peak.r / 10.0f);
stereo_t _average;
// Calculate average
volk_32f_s32f_power_32f(leftBuf, leftBuf, 2, count);
volk_32f_s32f_power_32f(rightBuf, rightBuf, 2, count);
volk_32f_sqrt_32f(leftBuf, leftBuf, count);
volk_32f_sqrt_32f(rightBuf, rightBuf, count);
volk_32f_accumulator_s32f(&_average.l, leftBuf, count);
volk_32f_accumulator_s32f(&_average.r, rightBuf, count);
_average.l /= (float)count;
_average.r /= (float)count;
// Calculate peak
float maxL = 0, maxR = 0;
float absL, absR;
for (int i = 0; i < count; i++) {
if (leftBuf[i] > _peak.l) { _peak.l = leftBuf[i]; }
if (rightBuf[i] > _peak.r) { _peak.r = rightBuf[i]; }
absL = fabs(_in->readBuf[i].l);
absR = fabs(_in->readBuf[i].r);
if (absL > maxL) { maxL = absL; }
if (absR > maxR) { maxR = absR; }
}
// Assign
peak.l = 10.0f * log10f(_peak.l);
peak.r = 10.0f * log10f(_peak.r);
average.l = (average.l * (1.0f - avgFilt)) + (10.0f * log10f(_average.l) * avgFilt);
average.r = (average.r * (1.0f - avgFilt)) + (10.0f * log10f(_average.r) * avgFilt);
_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;
}
stream<stereo_t> out;
float getLeftLevel() {
std::lock_guard<std::mutex> lck(lvlMtx);
float ret = lvlL;
lvlL = -90.0f;
return ret;
}
stereo_t peak = {0, 0};
stereo_t average = {0, 0};
float getRightLevel() {
std::lock_guard<std::mutex> lck(lvlMtx);
float ret = lvlR;
lvlR = -90.0f;
return ret;
}
private:
int count;
float peakFall = 10.0f; // dB/S
float avgFilt = 0.2f; // IIR filter coef
float sampleRate = 48000;
float lvlL = -90.0f;
float lvlR = -90.0f;
stream<stereo_t>* _in;
std::mutex lvlMtx;
float* leftBuf;
float* rightBuf;
};
}

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;
};
}

View File

@ -3,6 +3,8 @@
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <string.h>
#include <stdint.h>
#include <dsp/math.h>
namespace dsp {
template <class T>
@ -12,10 +14,6 @@ namespace dsp {
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
~FrequencyXlator() {
generic_block<FrequencyXlator<T>>::stop();
}
void init(stream<complex_t>* in, float sampleRate, float freq) {
_in = in;
_sampleRate = sampleRate;
@ -24,9 +22,11 @@ namespace dsp {
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 setInputSize(stream<complex_t>* in) {
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);
@ -36,27 +36,29 @@ namespace dsp {
}
void setSampleRate(float sampleRate) {
// No need to restart
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) {
// No need to restart
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() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
// TODO: Do float xlation
@ -75,7 +77,6 @@ namespace dsp {
stream<complex_t> out;
private:
int count;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
@ -90,8 +91,6 @@ namespace dsp {
AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); }
~AGC() { generic_block<AGC>::stop(); }
void init(stream<float>* in, float fallRate, float sampleRate) {
_in = in;
_sampleRate = sampleRate;
@ -99,9 +98,11 @@ namespace dsp {
_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);
@ -111,27 +112,34 @@ namespace dsp {
}
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() {
count = _in->read();
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++) {
if (_in->readBuf[i] > level) { level = _in->readBuf[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();
@ -142,7 +150,6 @@ namespace dsp {
stream<float> out;
private:
int count;
float level = 0.0f;
float _fallRate;
float _CorrectedFallRate;
@ -151,6 +158,126 @@ namespace dsp {
};
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:
@ -158,16 +285,16 @@ namespace dsp {
Volume(stream<T>* in, float volume) { init(in, volume); }
~Volume() { generic_block<Volume<T>>::stop(); }
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 setInputSize(stream<T>* in) {
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);
@ -177,24 +304,28 @@ namespace dsp {
}
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() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
if (_muted) {
@ -222,7 +353,6 @@ namespace dsp {
stream<T> out;
private:
int count;
float level = 1.0f;
float _volume = 1.0f;
bool _muted = false;
@ -237,8 +367,10 @@ namespace dsp {
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) {
@ -247,9 +379,11 @@ namespace dsp {
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);
@ -259,18 +393,20 @@ namespace dsp {
}
void setLevel(float level) {
assert(generic_block<Squelch>::_block_init);
_level = level;
}
float getLevel() {
assert(generic_block<Squelch>::_block_init);
return _level;
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
float sum = 0.0f;
float sum;
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count);
volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count;
@ -291,10 +427,192 @@ namespace dsp {
private:
int count;
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;
};
}

View File

@ -3,6 +3,7 @@
#include <dsp/window.h>
#include <numeric>
#include <string.h>
#include <dsp/math.h>
namespace dsp {
template <class T>
@ -13,10 +14,12 @@ namespace dsp {
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) {
@ -37,20 +40,27 @@ namespace dsp {
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;
@ -58,10 +68,13 @@ namespace dsp {
_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;
@ -69,18 +82,23 @@ namespace dsp {
_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;
@ -89,42 +107,50 @@ namespace dsp {
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 {
count = _in->read();
int count = _in->read();
if (count < 0) {
return -1;
}
int outCount = calcOutSize(count);
memcpy(&buffer[tapsPerPhase], _in->readBuf, count * sizeof(T));
_in->flush();
// Write to output
int outIndex = 0;
int _interp_m_1 = _interp - 1;
int inOffset = offset;
int _counter = counter;
if constexpr (std::is_same_v<T, float>) {
for (int i = 0; outIndex < outCount; i += _decim) {
int phase = i % _interp;
volk_32f_x2_dot_prod_32f(&out.writeBuf[outIndex], &buffer[i / _interp], tapPhases[phase], tapsPerPhase);
outIndex++;
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>) {
for (int i = 0; outIndex < outCount; i += _decim) {
int phase = i % _interp;
volk_32fc_32f_dot_prod_32fc((lv_32fc_t*)&out.writeBuf[outIndex], (lv_32fc_t*)&buffer[(i / _interp)], tapPhases[phase], tapsPerPhase);
outIndex++;
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(outCount)) { return -1; }
if (!out.swap(outIndex)) { return -1; }
offset = inOffset - count;
counter = _counter;
memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T));
@ -172,7 +198,6 @@ namespace dsp {
tapPhases.clear();
}
int count;
stream<T>* _in;
dsp::filter_window::generic_window* _window;
@ -184,84 +209,11 @@ namespace dsp {
float _inSampleRate, _outSampleRate;
float* taps;
int counter = 0;
int offset = 0;
int tapsPerPhase;
std::vector<float*> tapPhases;
};
class PowerDecimator : public generic_block<PowerDecimator> {
public:
PowerDecimator() {}
PowerDecimator(stream<complex_t>* in, unsigned int power) { init(in, power); }
~PowerDecimator() {
generic_block<PowerDecimator>::stop();
}
void init(stream<complex_t>* in, unsigned int power) {
_in = in;
_power = power;
generic_block<PowerDecimator>::registerInput(_in);
generic_block<PowerDecimator>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx);
generic_block<PowerDecimator>::tempStop();
generic_block<PowerDecimator>::unregisterInput(_in);
_in = in;
generic_block<PowerDecimator>::registerInput(_in);
generic_block<PowerDecimator>::tempStart();
}
void setPower(unsigned int power) {
std::lock_guard<std::mutex> lck(generic_block<PowerDecimator>::ctrlMtx);
generic_block<PowerDecimator>::tempStop();
generic_block<PowerDecimator>::unregisterInput(_in);
_power = power;
generic_block<PowerDecimator>::registerInput(_in);
generic_block<PowerDecimator>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (_power == 0) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
}
else if (_power == 1) {
for (int j = 0; j < count; j += 2) {
out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f;
out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f;
}
count /= 2;
}
_in->flush();
if (_power > 1) {
for (int i = 1; i < _power; i++) {
for (int j = 0; j < count; j += 2) {
out.writeBuf[j / 2].i = (_in->readBuf[j].i + _in->readBuf[j + 1].i) * 0.5f;
out.writeBuf[j / 2].q = (_in->readBuf[j].q + _in->readBuf[j + 1].q) * 0.5f;
}
count /= 2;
}
}
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
int count;
unsigned int _power = 0;
stream<complex_t>* _in;
};
}

View File

@ -13,14 +13,14 @@ namespace dsp {
Splitter(stream<T>* in) { init(in); }
~Splitter() { generic_block<Splitter>::stop(); }
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);
@ -30,6 +30,7 @@ namespace dsp {
}
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);
@ -38,6 +39,7 @@ namespace dsp {
}
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);
@ -72,7 +74,12 @@ namespace dsp {
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
~Reshaper() { generic_block<Reshaper<T>>::stop(); }
// 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;
@ -81,9 +88,11 @@ namespace dsp {
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);
@ -93,21 +102,19 @@ namespace dsp {
}
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();
generic_block<Reshaper<T>>::unregisterInput(_in);
_keep = keep;
ringBuf.setMaxLatency(keep * 2);
generic_block<Reshaper<T>>::registerInput(_in);
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();
generic_block<Reshaper<T>>::unregisterInput(_in);
_skip = skip;
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
@ -122,7 +129,7 @@ namespace dsp {
stream<T> out;
private:
void doStart() {
void doStart() override {
workThread = std::thread(&Reshaper<T>::loop, this);
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
}
@ -131,7 +138,7 @@ namespace dsp {
while (run() >= 0);
}
void doStop() {
void doStop() override {
_in->stopReader();
ringBuf.stopReader();
out.stopWriter();
@ -151,27 +158,29 @@ namespace dsp {
}
void bufferWorker() {
complex_t* buf = new complex_t[_keep];
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(complex_t);
int delaySize = (-_skip) * sizeof(T);
int delayCount = (-_skip);
complex_t* start = &buf[std::max<int>(-_skip, 0)];
complex_t* delayStart = &buf[_keep + _skip];
T* start = &buf[std::max<int>(-_skip, 0)];
T* delayStart = &buf[_keep + _skip];
while (true) {
if (delay) {
memmove(buf, delayStart, delaySize);
for (int i = 0; i < delayCount; i++) {
buf[i].i /= 10.0f;
buf[i].q /= 10.0f;
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(complex_t));
memcpy(out.writeBuf, buf, _keep * sizeof(T));
if (!out.swap(_keep)) { break; }
}
delete[] buf;
@ -183,7 +192,6 @@ namespace dsp {
std::thread bufferWorkerThread;
std::thread workThread;
int _keep, _skip;
};
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
#include <fstream>
namespace dsp {
template <class T>
@ -10,16 +11,16 @@ namespace dsp {
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
~HandlerSink() { generic_block<HandlerSink<T>>::stop(); }
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);
@ -29,6 +30,7 @@ namespace dsp {
}
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;
@ -37,7 +39,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
_handler(_in->readBuf, count, _ctx);
_in->flush();
@ -45,7 +47,6 @@ namespace dsp {
}
private:
int count;
stream<T>* _in;
void (*_handler)(T* data, int count, void* ctx);
void* _ctx;
@ -59,15 +60,15 @@ namespace dsp {
RingBufferSink(stream<T>* in) { init(in); }
~RingBufferSink() { generic_block<RingBufferSink<T>>::stop(); }
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);
@ -77,7 +78,7 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
if (data.write(_in->readBuf, count) < 0) { return -1; }
_in->flush();
@ -97,7 +98,6 @@ namespace dsp {
data.clearWriteStop();
}
int count;
stream<T>* _in;
};
@ -109,14 +109,14 @@ namespace dsp {
NullSink(stream<T>* in) { init(in); }
~NullSink() { generic_block<NullSink<T>>::stop(); }
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);
@ -126,15 +126,68 @@ namespace dsp {
}
int run() {
count = _in->read();
int count = _in->read();
if (count < 0) { return -1; }
_in->flush();
return count;
}
private:
int count;
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;
};
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <dsp/block.h>
#include <dsp/math.h>
namespace dsp {
class SineSource : public generic_block<SineSource> {
@ -8,8 +9,6 @@ namespace dsp {
SineSource(int blockSize, float sampleRate, float freq) { init(blockSize, sampleRate, freq); }
~SineSource() { generic_block<SineSource>::stop(); }
void init(int blockSize, float sampleRate, float freq) {
_blockSize = blockSize;
_sampleRate = sampleRate;
@ -21,9 +20,11 @@ namespace dsp {
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;
@ -31,26 +32,29 @@ namespace dsp {
}
int getBlockSize() {
assert(generic_block<SineSource>::_block_init);
return _blockSize;
}
void setSampleRate(float sampleRate) {
// No need to restart
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) {
// No need to restart
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;
}
@ -71,4 +75,42 @@ namespace dsp {
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;
};
}

View File

@ -7,7 +7,7 @@
#define STREAM_BUFFER_SIZE 1000000
namespace dsp {
class untyped_steam {
class untyped_stream {
public:
virtual bool swap(int size) { return false; }
virtual int read() { return -1; }
@ -19,7 +19,7 @@ namespace dsp {
};
template <class T>
class stream : public untyped_steam {
class stream : public untyped_stream {
public:
stream() {
writeBuf = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T), volk_get_alignment());

View File

@ -1,12 +1,83 @@
#pragma once
#include <math.h>
#include <dsp/utils/math.h>
namespace dsp {
struct complex_t {
float q;
float i;
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);
}
}
}

View File

@ -10,7 +10,11 @@ namespace dsp {
public:
VFO() {}
~VFO() { stop(); }
~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);
@ -33,21 +37,26 @@ namespace dsp {
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);
@ -61,6 +70,7 @@ namespace dsp {
}
void setOutSampleRate(float outSampleRate) {
assert(_init);
_outSampleRate = outSampleRate;
if (running) { resamp.stop(); }
resamp.setOutSampleRate(_outSampleRate);
@ -73,6 +83,7 @@ namespace dsp {
}
void setOutSampleRate(float outSampleRate, float bandWidth) {
assert(_init);
_outSampleRate = outSampleRate;
_bandWidth = bandWidth;
if (running) { resamp.stop(); }
@ -86,11 +97,13 @@ namespace dsp {
}
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);
@ -101,6 +114,7 @@ namespace dsp {
stream<complex_t>* out;
private:
bool _init = false;
bool running = false;
float _offset, _inSampleRate, _outSampleRate, _bandWidth;
filter_window::BlackmanWindow win;

View File

@ -1,5 +1,7 @@
#pragma once
#include <dsp/block.h>
#include <dsp/types.h>
#include <dsp/utils/window_functions.h>
namespace dsp {
namespace filter_window {
@ -9,6 +11,12 @@ namespace dsp {
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() {}
@ -49,19 +57,21 @@ namespace dsp {
}
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
float tc = tapCount;
float sum = 0.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 = (sin(2.0f * FL_M_PI * fc * ((float)i - (tc / 2))) / ((float)i - (tc / 2))) *
(0.42f - (0.5f * cos(2.0f * FL_M_PI / tc)) + (0.8f * cos(4.0f * FL_M_PI / tc)));
taps[i] = val; // tapCount - i - 1
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;
@ -72,5 +82,191 @@ namespace dsp {
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;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,185 +0,0 @@
/*
* Minimal 'console' binding.
*
* https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
* https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
* https://developer.mozilla.org/en/docs/Web/API/console
*/
#include <stdio.h>
#include <stdarg.h>
#include "duktape.h"
#include "duk_console.h"
/* XXX: Add some form of log level filtering. */
/* XXX: Should all output be written via e.g. console.write(formattedMsg)?
* This would make it easier for user code to redirect all console output
* to a custom backend.
*/
/* XXX: Init console object using duk_def_prop() when that call is available. */
static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) {
duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx);
FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr;
duk_idx_t n = duk_get_top(ctx);
duk_idx_t i;
duk_get_global_string(ctx, "console");
duk_get_prop_string(ctx, -1, "format");
for (i = 0; i < n; i++) {
if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
/* Slow path formatting. */
duk_dup(ctx, -1); /* console.format */
duk_dup(ctx, i);
duk_call(ctx, 1);
duk_replace(ctx, i); /* arg[i] = console.format(arg[i]); */
}
}
duk_pop_2(ctx);
duk_push_string(ctx, " ");
duk_insert(ctx, 0);
duk_join(ctx, n);
if (error_name) {
duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
duk_push_string(ctx, "name");
duk_push_string(ctx, error_name);
duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE); /* to get e.g. 'Trace: 1 2 3' */
duk_get_prop_string(ctx, -1, "stack");
}
fprintf(output, "%s\n", duk_to_string(ctx, -1));
if (flags & DUK_CONSOLE_FLUSH) {
fflush(output);
}
return 0;
}
static duk_ret_t duk__console_assert(duk_context *ctx) {
if (duk_to_boolean(ctx, 0)) {
return 0;
}
duk_remove(ctx, 0);
return duk__console_log_helper(ctx, "AssertionError");
}
static duk_ret_t duk__console_log(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_trace(duk_context *ctx) {
return duk__console_log_helper(ctx, "Trace");
}
static duk_ret_t duk__console_info(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_warn(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_error(duk_context *ctx) {
return duk__console_log_helper(ctx, "Error");
}
static duk_ret_t duk__console_dir(duk_context *ctx) {
/* For now, just share the formatting of .log() */
return duk__console_log_helper(ctx, 0);
}
static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
duk_push_c_function(ctx, func, DUK_VARARGS);
duk_push_string(ctx, "name");
duk_push_string(ctx, name);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
duk_set_magic(ctx, -1, (duk_int_t) flags);
duk_put_prop_string(ctx, -2, name);
}
void duk_console_init(duk_context *ctx, duk_uint_t flags) {
duk_uint_t flags_orig;
/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
* just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
*/
if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
flags &= ~DUK_CONSOLE_STDOUT_ONLY;
}
/* Remember the (possibly corrected) flags we received. */
flags_orig = flags;
duk_push_object(ctx);
/* Custom function to format objects; user can replace.
* For now, try JX-formatting and if that fails, fall back
* to ToString(v).
*/
duk_eval_string(ctx,
"(function (E) {"
"return function format(v){"
"try{"
"return E('jx',v);"
"}catch(e){"
"return String(v);" /* String() allows symbols, ToString() internal algorithm doesn't. */
"}"
"};"
"})(Duktape.enc)");
duk_put_prop_string(ctx, -2, "format");
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stdout. */
flags |= DUK_CONSOLE_STDOUT_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags); /* alias to console.log */
duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stderr. */
flags |= DUK_CONSOLE_STDERR_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags); /* alias to console.error */
duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
duk_put_global_string(ctx, "console");
/* Proxy wrapping: ensures any undefined console method calls are
* ignored silently. This was required specifically by the
* DeveloperToolsWG proposal (and was implemented also by Firefox:
* https://bugzilla.mozilla.org/show_bug.cgi?id=629607). This is
* apparently no longer the preferred way of implementing console.
* When Proxy is enabled, whitelist at least .toJSON() to avoid
* confusing JX serialization of the console object.
*/
if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
/* Tolerate failure to initialize Proxy wrapper in case
* Proxy support is disabled.
*/
(void) duk_peval_string_noresult(ctx,
"(function(){"
"var D=function(){};"
"var W={toJSON:true};" /* whitelisted */
"console=new Proxy(console,{"
"get:function(t,k){"
"var v=t[k];"
"return typeof v==='function'||W[k]?v:D;"
"}"
"});"
"})();"
);
}
}

View File

@ -1,29 +0,0 @@
#if !defined(DUK_CONSOLE_H_INCLUDED)
#define DUK_CONSOLE_H_INCLUDED
#include "duktape.h"
#if defined(__cplusplus)
extern "C" {
#endif
/* Use a proxy wrapper to make undefined methods (console.foo()) no-ops. */
#define DUK_CONSOLE_PROXY_WRAPPER (1U << 0)
/* Flush output after every call. */
#define DUK_CONSOLE_FLUSH (1U << 1)
/* Send output to stdout only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDOUT_ONLY (1U << 2)
/* Send output to stderr only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDERR_ONLY (1U << 3)
/* Initialize the console system */
extern void duk_console_init(duk_context *ctx, duk_uint_t flags);
#if defined(__cplusplus)
}
#endif /* end 'extern "C"' wrapper */
#endif /* DUK_CONSOLE_H_INCLUDED */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +0,0 @@
#pragma once
#include <vector>
#include <spdlog/spdlog.h>
template <class T>
class Event {
public:
Event() {}
~Event() {}
struct EventHandler {
EventHandler() {}
EventHandler(void (*handler)(T, void*), void* ctx) {
this->handler = handler;
this->ctx = ctx;
}
void (*handler)(T, void*);
void* ctx;
};
void emit(T value) {
for (auto const& handler : handlers) {
handler.handler(value, handler.ctx);
}
}
void bindHandler(const EventHandler& handler) {
handlers.push_back(handler);
}
void unbindHandler(const EventHandler& handler) {
if (handlers.find(handler) == handlers.end()) {
spdlog::error("Tried to remove a non-existant event handler");
return;
}
handlers.erase(std::remove(handlers.begin(), handlers.end(), handler), handlers.end());
}
private:
std::vector<EventHandler> handlers;
};

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

@ -15,6 +15,10 @@ namespace credits {
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);
@ -27,7 +31,7 @@ namespace credits {
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("This software is brought to you by\n\n");
ImGui::Text("This software is brought to you by Alexandre Rouma with the help of\n\n");
ImGui::Columns(3, "CreditColumns", true);
@ -53,9 +57,10 @@ namespace credits {
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("SDR++ v" VERSION_STR);
ImGui::Text("SDR++ v" VERSION_STR " (Built at " __TIME__ ", " __DATE__ ")");
ImGui::EndPopup();
ImGui::PopStyleVar(1);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
}
}

View File

@ -7,6 +7,7 @@
#include <gui/icons.h>
#include <gui/style.h>
#include <credits.h>
#include <gui/gui.h>
namespace LoadingScreen {
GLFWwindow* _win;
@ -26,7 +27,7 @@ namespace LoadingScreen {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::OpenPopup("Credits");
ImGui::PushStyleColor(ImGuiCol_ModalWindowDarkening, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
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);
@ -34,37 +35,40 @@ namespace LoadingScreen {
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
// ImGui::Spacing();
// ImGui::Spacing();
// ImGui::Spacing();
ImGui::Text("This software is brought to you by\n\n");
// ImGui::Text("This software is brought to you by\n\n");
ImGui::Columns(3, "CreditColumns", true);
// 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::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("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::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::Columns(1, "CreditColumnsEnd", true);
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
// 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);
@ -76,7 +80,7 @@ namespace LoadingScreen {
int display_w, display_h;
glfwGetFramebufferSize(_win, &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());

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -4,11 +4,15 @@
#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);
};

View File

@ -6,17 +6,13 @@
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <imgui_plot.h>
#include <thread>
#include <complex>
#include <gui/widgets/waterfall.h>
#include <gui/widgets/frequency_select.h>
#include <fftw3.h>
#include <signal_path/dsp.h>
#include <gui/icons.h>
#include <gui/widgets/bandplan.h>
#include <watcher.h>
#include <signal_path/vfo_manager.h>
#include <gui/style.h>
#include <config.h>
#include <signal_path/signal_path.h>
@ -25,112 +21,51 @@
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/sink.h>
#include <gui/menus/scripting.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>
// const int FFTSizes[] = {
// 65536,
// 32768,
// 16384,
// 8192,
// 4096,
// 2048
// };
// const char* FFTSizesStr[] = {
// "65536",
// "32768",
// "16384",
// "8192",
// "4096",
// "2048"
// };
// int fftSizeId = 0;
int fftSize = 8192 * 8;
std::thread worker;
std::mutex fft_mtx;
fftwf_complex *fft_in, *fft_out;
fftwf_plan p;
float* tempFFT;
float* FFTdata;
char buf[1024];
bool experimentalZoom = false;
void fftHandler(dsp::complex_t* samples, int count, void* ctx) {
memcpy(fft_in, samples, count * sizeof(dsp::complex_t));
fftwf_execute(p);
int half = count / 2;
volk_32fc_s32f_power_spectrum_32f(tempFFT, (lv_32fc_t*)fft_out, count, count);
volk_32f_s32f_multiply_32f(FFTdata, tempFFT, 0.5f, count);
memcpy(tempFFT, &FFTdata[half], half * sizeof(float));
memmove(&FFTdata[half], FFTdata, half * sizeof(float));
memcpy(FFTdata, tempFFT, half * sizeof(float));
float* fftBuf = gui::waterfall.getFFTBuffer();
if (fftBuf == NULL) {
gui::waterfall.pushFFT();
return;
}
float last = FFTdata[0];
for (int i = 0; i < count; i++) {
last = (FFTdata[i] * 0.1f) + (last * 0.9f);
fftBuf[i] = last;
}
gui::waterfall.pushFFT();
}
watcher<uint64_t> freq((uint64_t)90500000);
watcher<double> vfoFreq(92000000.0);
float fftMin = -70.0;
float fftMax = 0.0;
watcher<double> offset(0.0, true);
float bw = 8000000;
bool playing = false;
watcher<bool> dcbias(false, 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;
bool centerTuning = false;
dsp::stream<dsp::complex_t> dummyStream;
bool demoWindow = false;
void windowInit() {
void MainWindow::init() {
LoadingScreen::show("Initializing UI");
gui::waterfall.init();
gui::waterfall.setRawFFTSize(fftSize);
tempFFT = new float[fftSize];
FFTdata = new float[fftSize];
credits::init();
core::configManager.aquire();
gui::menu.order = core::configManager.conf["menuOrder"].get<std::vector<std::string>>();
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("Scripting", scriptingmenu::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();
@ -140,11 +75,15 @@ void windowInit() {
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
p = fftwf_plan_dft_1d(fftSize, fft_in, fft_out, FFTW_FORWARD, FFTW_ESTIMATE);
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);
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
@ -165,9 +104,9 @@ void windowInit() {
}
// Read module config
core::configManager.aquire();
core::configManager.acquire();
std::vector<std::string> modules = core::configManager.conf["modules"];
std::map<std::string, std::string> modList = core::configManager.conf["moduleInstances"];
auto modList = core::configManager.conf["moduleInstances"].items();
core::configManager.release();
// Load additional modules specified through config
@ -178,10 +117,13 @@ void windowInit() {
}
// Create module instances
for (auto const& [name, module] : modList) {
spdlog::info("Initializing {0} ({1})", name, module);
LoadingScreen::show("Initializing " + name + " (" + module + ")");
core::moduleManager.createInstance(name, module);
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
@ -207,22 +149,17 @@ void windowInit() {
sourecmenu::init();
sinkmenu::init();
scriptingmenu::init();
bandplanmenu::init();
displaymenu::init();
vfo_color_menu::init();
module_manager_menu::init();
// TODO for 0.2.5
// Add "select file" option for the file source
// Have a good directory system on both linux and windows
// Switch to double buffering (should fix occassional underruns)
// Fix gain not updated on startup, soapysdr
// TODO for 0.2.6
// Add a module add/remove/change order menu
// Update UI settings
LoadingScreen::show("Loading configuration");
core::configManager.aquire();
core::configManager.acquire();
fftMin = core::configManager.conf["min"];
fftMax = core::configManager.conf["max"];
gui::waterfall.setFFTMin(fftMin);
@ -232,6 +169,9 @@ void windowInit() {
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);
@ -247,113 +187,82 @@ void windowInit() {
fftHeight = core::configManager.conf["fftHeight"];
gui::waterfall.setFFTHeight(fftHeight);
centerTuning = core::configManager.conf["centerTuning"];
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 setVFO(double freq) {
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 BW = gui::waterfall.getBandwidth();
if (gui::waterfall.selectedVFO == "") {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
gui::waterfall.setCenterFrequency(freq);
sigpath::sourceManager.tune(freq);
return;
}
double viewOffset = gui::waterfall.getViewOffset();
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
double viewLower = viewOffset - (viewBW/2.0);
double viewUpper = viewOffset + (viewBW/2.0);
double currentOff = vfo->centerOffset;
double currentTune = gui::waterfall.getCenterFrequency() + vfo->generalOffset;
double delta = freq - currentTune;
double newOffset = std::clamp<double>(offset, viewLower, viewUpper);
double newVFO = currentOff + delta;
double vfoBW = vfo->bandwidth;
double vfoBottom = newVFO - (vfoBW / 2.0);
double vfoTop = newVFO + (vfoBW / 2.0);
sigpath::vfoManager.setCenterOffset(name, _this->initComplete ? newOffset : offset);
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);
if (centerTuning) {
gui::waterfall.setViewOffset((BW / 2.0) - (viewBW / 2.0));
gui::waterfall.setCenterFrequency(freq);
gui::waterfall.setViewOffset(0);
sigpath::vfoManager.setOffset(gui::waterfall.selectedVFO, 0);
sigpath::sourceManager.tune(freq);
return;
}
// VFO still fints in the view
if (vfoBottom > viewBottom && vfoTop < viewTop) {
sigpath::vfoManager.setCenterOffset(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, 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(gui::waterfall.selectedVFO, newVFOOffset);
gui::waterfall.setCenterFrequency(freq - newVFOOffset);
sigpath::sourceManager.tune(freq - newVFOOffset);
}
}
void drawWindow() {
void MainWindow::draw() {
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::WaterfallVFO* vfo = NULL;
@ -361,43 +270,47 @@ void drawWindow() {
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
}
// Handle VFO movement
if (vfo != NULL) {
if (vfo->centerOffsetChanged) {
if (centerTuning) {
setVFO(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
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.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.acquire();
core::configManager.conf["vfoOffsets"][gui::waterfall.selectedVFO] = vfo->generalOffset;
core::configManager.release(true);
}
}
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
if (gui::waterfall.selectedVFOChanged && vfo != NULL) {
// 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.setFrequency(vfo->generalOffset + gui::waterfall.getCenterFrequency());
gui::freqSelect.frequencyChanged = false;
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
// Handle change in selected frequency
if (gui::freqSelect.frequencyChanged) {
gui::freqSelect.frequencyChanged = false;
setVFO(gui::freqSelect.frequency);
tuner::tune(tuningMode, gui::waterfall.selectedVFO, gui::freqSelect.frequency);
if (vfo != NULL) {
vfo->centerOffsetChanged = false;
vfo->lowerOffsetChanged = false;
vfo->upperOffsetChanged = false;
}
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
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());
@ -407,15 +320,15 @@ void drawWindow() {
else {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
}
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
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.aquire();
core::configManager.acquire();
core::configManager.conf["fftHeight"] = fftHeight;
core::configManager.release(true);
}
@ -428,31 +341,40 @@ void drawWindow() {
// To Bar
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
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)) {
sigpath::sourceManager.stop();
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)) {
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();
// TODO: tune in module instead
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
playing = true;
onPlayStateChange.emit(true);
}
ImGui::PopID();
}
if (playButtonLocked && !tmpPlaySate) { style::endDisabled(); }
ImGui::SameLine();
@ -466,12 +388,12 @@ void drawWindow() {
ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
if (centerTuning) {
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)) {
centerTuning = false;
core::configManager.aquire();
core::configManager.conf["centerTuning"] = centerTuning;
tuningMode = tuner::TUNER_MODE_NORMAL;
core::configManager.acquire();
core::configManager.conf["centerTuning"] = false;
core::configManager.release(true);
}
ImGui::PopID();
@ -479,10 +401,10 @@ void drawWindow() {
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)) {
centerTuning = true;
setVFO(gui::freqSelect.frequency);
core::configManager.aquire();
core::configManager.conf["centerTuning"] = centerTuning;
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();
@ -490,6 +412,15 @@ void drawWindow() {
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);
@ -504,35 +435,38 @@ void drawWindow() {
}
// Handle menu resize
float curY = ImGui::GetCursorPosY();
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
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;
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);
}
}
else {
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
}
if(!down && grabbingMenu) {
grabbingMenu = false;
menuWidth = newWidth;
core::configManager.aquire();
core::configManager.conf["menuWidth"] = menuWidth;
core::configManager.release(true);
}
// Left Column
lockWaterfallControls = false;
if (showMenu) {
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
@ -541,18 +475,51 @@ void drawWindow() {
ImGui::BeginChild("Left Column");
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
gui::menu.draw();
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());
if (ImGui::Checkbox("Test technique", &dcbias.val)) {
//sigpath::signalPath.setDCBiasCorrection(dcbias.val);
}
ImGui::Checkbox("Show demo window", &demoWindow);
ImGui::Checkbox("Experimental zoom", &experimentalZoom);
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();
}
@ -577,6 +544,48 @@ void drawWindow() {
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");
@ -584,7 +593,7 @@ void drawWindow() {
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, "", (experimentalZoom ? 2.0 : 1.0))) {
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
@ -596,9 +605,9 @@ void drawWindow() {
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, -100.0, "")) {
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -160.0f, "")) {
fftMax = std::max<float>(fftMax, fftMin + 10);
core::configManager.aquire();
core::configManager.acquire();
core::configManager.conf["max"] = fftMax;
core::configManager.release(true);
}
@ -608,9 +617,9 @@ void drawWindow() {
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, -100.0, "")) {
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -160.0f, "")) {
fftMin = std::min<float>(fftMax - 10, fftMin);
core::configManager.aquire();
core::configManager.acquire();
core::configManager.conf["min"] = fftMin;
core::configManager.release(true);
}
@ -633,10 +642,35 @@ void drawWindow() {
}
}
void setViewBandwidthSlider(float bandwidth) {
void MainWindow::setViewBandwidthSlider(float bandwidth) {
bw = bandwidth;
}
bool sdrIsRunning() {
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

@ -1,9 +1,67 @@
#pragma once
#include "imgui.h"
#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
void windowInit();
void drawWindow();
void setViewBandwidthSlider(float bandwidth);
bool sdrIsRunning();
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

@ -6,6 +6,9 @@
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
@ -26,21 +29,34 @@ namespace bandplanmenu {
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("##_4_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
if (ImGui::Combo("##_bandplan_name_", &bandplanId, bandplan::bandplanNameTxt.c_str())) {
gui::waterfall.bandplan = &bandplan::bandplans[bandplan::bandplanNames[bandplanId]];
core::configManager.aquire();
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.aquire();
core::configManager.acquire();
core::configManager.conf["bandPlanEnabled"] = bandPlanEnabled;
core::configManager.release(true);
}

View File

@ -4,13 +4,39 @@
#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"];
@ -30,17 +56,86 @@ namespace displaymenu {
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();
if (ImGui::Checkbox("Show Waterfall", &showWaterfall)) {
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.aquire();
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();
@ -48,12 +143,14 @@ namespace displaymenu {
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.aquire();
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,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

@ -1,22 +0,0 @@
#include <gui/menus/scripting.h>
#include <core.h>
#include <gui/style.h>
#include <imgui/imgui.h>
namespace scriptingmenu {
void init() {
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
for (const auto& [name, script] : core::scriptManager.scripts) {
bool running = script->running;
if (running) { style::beginDisabled(); }
if (ImGui::Button(name.c_str(), ImVec2(menuWidth, 0))) {
script->run();
}
if (running) { style::endDisabled(); }
}
}
}

View File

@ -4,7 +4,7 @@
namespace sinkmenu {
void init() {
core::configManager.aquire();
core::configManager.acquire();
sigpath::sinkManager.loadSinksFromConfig();
core::configManager.release();
}

View File

@ -7,55 +7,204 @@
#include <signal_path/signal_path.h>
namespace sourecmenu {
int offsetMode = 0;
int sourceId = 0;
double freqOffset = 0.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.aquire();
std::string name = core::configManager.conf["source"];
auto it = std::find(sigpath::sourceManager.sourceNames.begin(), sigpath::sourceManager.sourceNames.end(), name);
if (it != sigpath::sourceManager.sourceNames.end()) {
sigpath::sourceManager.selectSource(name);
sourceId = std::distance(sigpath::sourceManager.sourceNames.begin(), it);
}
else if (sigpath::sourceManager.sourceNames.size() > 0) {
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[0]);
}
else {
spdlog::warn("No source available...");
}
sigpath::sourceManager.setTuningOffset(core::configManager.conf["offset"]);
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) {
std::string items = "";
for (std::string name : sigpath::sourceManager.sourceNames) {
items += name;
items += '\0';
}
float itemWidth = ImGui::GetContentRegionAvailWidth();
bool running = gui::mainWindow.sdrIsRunning();
if (running) { style::beginDisabled(); }
if (sdrIsRunning()) { style::beginDisabled(); }
ImGui::SetNextItemWidth(itemWidth);
if (ImGui::Combo("##source", &sourceId, items.c_str())) {
sigpath::sourceManager.selectSource(sigpath::sourceManager.sourceNames[sourceId]);
core::configManager.aquire();
core::configManager.conf["source"] = sigpath::sourceManager.sourceNames[sourceId];
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 (sdrIsRunning()) { style::endDisabled(); }
if (running) { style::endDisabled(); }
sigpath::sourceManager.showSelectedMenu();
ImGui::SetNextItemWidth(itemWidth - ImGui::CalcTextSize("Offset (Hz)").x - 10);
if (ImGui::InputDouble("Offset (Hz)##freq_offset", &freqOffset, 1.0, 100.0)) {
sigpath::sourceManager.setTuningOffset(freqOffset);
core::configManager.aquire();
core::configManager.conf["offset"] = freqOffset;
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,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

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

View File

@ -11,107 +11,32 @@ namespace style {
ImFont* bigFont;
ImFont* hugeFont;
bool setDefaultStyle(std::string resDir) {
bool loadFonts(std::string resDir) {
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Inavlid resource directory: {0}", resDir);
return false;
}
ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().ChildRounding = 0.0f;
ImGui::GetStyle().FrameRounding = 0.0f;
ImGui::GetStyle().GrabRounding = 0.0f;
ImGui::GetStyle().PopupRounding = 0.0f;
ImGui::GetStyle().ScrollbarRounding = 0.0f;
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);
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
return true;
}
void testtt() {
ImGui::StyleColorsLight();
}
bool setDarkStyle(std::string resDir) {
if (!std::filesystem::is_directory(resDir)) {
spdlog::error("Inavlid resource directory: {0}", resDir);
return false;
}
ImGui::GetStyle().WindowRounding = 0.0f;
ImGui::GetStyle().ChildRounding = 0.0f;
ImGui::GetStyle().FrameRounding = 0.0f;
ImGui::GetStyle().GrabRounding = 0.0f;
ImGui::GetStyle().PopupRounding = 0.0f;
ImGui::GetStyle().ScrollbarRounding = 0.0f;
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);
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
ImVec4* colors = style.Colors;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
colors[ImGuiCol_ChildBg] = ImVec4(1.00f, 1.00f, 1.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.21f, 0.22f, 0.54f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.24f, 0.52f, 0.88f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.45f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.44f, 0.44f, 0.44f, 0.40f);
colors[ImGuiCol_Header] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.63f, 0.63f, 0.70f, 0.40f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.63f, 0.63f, 0.70f, 0.31f);
colors[ImGuiCol_Separator] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.72f, 0.72f, 0.72f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.91f, 0.91f, 0.91f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.81f, 0.81f, 0.81f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.46f, 0.46f, 0.46f, 0.95f);
colors[ImGuiCol_PlotLines] = ImVec4(0.4f, 0.9f, 1.0f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.73f, 0.60f, 0.15f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.87f, 0.87f, 0.87f, 0.35f);
colors[ImGuiCol_ModalWindowDarkening] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
return true;
}
void beginDisabled() {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.44f, 0.44f, 0.44f, 0.15f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.20f, 0.21f, 0.22f, 0.30f));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.00f, 1.00f, 1.00f, 0.65f));
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() {

View File

@ -8,7 +8,7 @@ namespace style {
extern ImFont* hugeFont;
bool setDefaultStyle(std::string resDir);
bool setDarkStyle(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,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

@ -1,6 +1,9 @@
#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
@ -67,12 +70,31 @@ void FrequencySelect::decrementDigit(int i) {
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();
@ -121,49 +143,87 @@ void FrequencySelect::draw() {
}
}
ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = ImGui::GetIO().MouseWheel;
bool onDigit = false;
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) {
if (rightClick) {
for (int j = i; j < 12; j++) {
digits[j] = 0;
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);
}
frequencyChanged = true;
onDigit = true;
}
if (mw != 0) {
int count = abs(mw);
for (int j = 0; j < count; j++) {
mw > 0 ? incrementDigit(i) : decrementDigit(i);
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);
}
frequency = freq;
uint64_t orig = freq;
freq = std::clamp<uint64_t>(freq, minFreq, maxFreq);
if (freq != orig && limitFreq) {
setFrequency(freq);
}
else {
frequency = orig;
}
ImGui::PopFont();
@ -172,7 +232,8 @@ void FrequencySelect::draw() {
//ImGui::NewLine();
}
void FrequencySelect::setFrequency(uint64_t freq) {
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;

View File

@ -8,16 +8,22 @@ public:
FrequencySelect();
void init();
void draw();
void setFrequency(uint64_t freq);
void setFrequency(int64_t freq);
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;

View File

@ -0,0 +1,91 @@
#include <gui/widgets/line_push_image.h>
namespace ImGui {
LinePushImage::LinePushImage(int frameWidth, int reservedIncrement) {
_frameWidth = frameWidth;
_reservedIncrement = reservedIncrement;
frameBuffer = (uint8_t*)malloc(_frameWidth * _reservedIncrement * 4);
reservedCount = reservedIncrement;
glGenTextures(1, &textureId);
}
void LinePushImage::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;
// Calculate scale
float width = CalcItemWidth();
float height = roundf((width / (float)_frameWidth) * (float)_lineCount);
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), height);
ImRect bb(min, ImVec2(min.x+size.x, min.y+size.y));
float lineHeight = size.y;
// If there are no lines, there is no point in drawing anything
if (_lineCount == 0) { return; }
ItemSize(size, style.FramePadding.y);
if (!ItemAdd(bb, 0)) {
return;
}
if (newData) {
newData = false;
updateTexture();
}
window->DrawList->AddImage((void*)(intptr_t)textureId, min, ImVec2(min.x + width, min.y + height));
}
uint8_t* LinePushImage::acquireNextLine(int count) {
bufferMtx.lock();
int oldLineCount = _lineCount;
_lineCount += count;
// If new data either fills up or excedes the limit, reallocate
// TODO: Change it to avoid bug if count >= reservedIncrement
if (_lineCount > reservedCount) {
printf("Reallocating\n");
reservedCount += _reservedIncrement;
frameBuffer = (uint8_t*)realloc(frameBuffer, _frameWidth * reservedCount * 4);
}
return &frameBuffer[_frameWidth * oldLineCount * 4];
}
void LinePushImage::releaseNextLine() {
newData = true;
bufferMtx.unlock();
}
void LinePushImage::clear() {
std::lock_guard<std::mutex> lck(bufferMtx);
_lineCount = 0;
frameBuffer = (uint8_t*)realloc(frameBuffer, _frameWidth * _reservedIncrement * 4);
reservedCount = _reservedIncrement;
newData = true;
}
void LinePushImage::save(std::string path) {
// TODO: Implement
}
int LinePushImage::getLineCount() {
return _lineCount;
}
void LinePushImage::updateTexture() {
glBindTexture(GL_TEXTURE_2D, textureId);
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, _frameWidth, _lineCount, 0, GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer);
}
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <imgui.h>
#include <imgui_internal.h>
#include <dsp/stream.h>
#include <mutex>
#include <GL/glew.h>
namespace ImGui {
class LinePushImage {
public:
LinePushImage(int frameWidth, int reservedIncrement);
void draw(const ImVec2& size_arg = ImVec2(0, 0));
uint8_t* acquireNextLine(int count = 1);
void releaseNextLine();
void clear();
void save(std::string path);
int getLineCount();
private:
void updateTexture();
std::mutex bufferMtx;
uint8_t* frameBuffer;
int _frameWidth;
int _reservedIncrement;
int _lineCount = 0;
int reservedCount = 0;
GLuint textureId;
bool newData = false;
};
}

View File

@ -1,6 +1,7 @@
#include <gui/widgets/menu.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <gui/style.h>
Menu::Menu() {
@ -13,7 +14,10 @@ void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void*
item.inst = inst;
items[name] = item;
if (!isInOrderList(name)) {
order.push_back(name);
MenuOption_t opt;
opt.name = name;
opt.open = true;
order.push_back(opt);
}
}
@ -21,35 +25,96 @@ void Menu::removeEntry(std::string name) {
items.erase(name);
}
void Menu::draw() {
MenuItem_t item;
bool Menu::draw(bool updateStates) {
bool changed = false;
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGuiWindow* window = ImGui::GetCurrentWindow();
for (std::string name : order) {
if (items.find(name) == items.end()) {
int displayedCount = 0;
int rawId = 0;
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
for (MenuOption_t& opt : order) {
rawId++;
if (items.find(opt.name) == items.end()) {
continue;
}
item = items[name];
if (opt.name == draggedMenuName) {
ImGui::BeginTooltip();
ImGui::Text("%s", draggedMenuName.c_str());
ImGui::EndTooltip();
continue;
}
if (displayedCount == insertBefore && !draggedMenuName.empty()) {
if (updateStates) { ImGui::SetNextItemOpen(false); }
ImVec2 posMin = ImGui::GetCursorScreenPos();
ImVec2 posMax = ImVec2(posMin.x + menuWidth, posMin.y + ImGui::GetFrameHeight());
style::beginDisabled();
ImRect orignalRect = window->WorkRect;
ImGui::CollapsingHeader((draggedMenuName + "##sdrpp_main_menu_dragging").c_str());
if (items[draggedOpt.name].inst != NULL) {
window->WorkRect = orignalRect;
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = items[draggedOpt.name].inst->isEnabled();
ImGui::Checkbox(("##_menu_checkbox_" + draggedOpt.name).c_str(), &enabled);
ImGui::SetCursorPos(pos);
}
style::endDisabled();
window->DrawList->AddRect(posMin, posMax, textColor);
}
displayedCount++;
MenuItem_t& item = items[opt.name];
ImRect orginalRect = window->WorkRect;
if (item.inst != NULL) {
window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y));
}
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
ImVec2 posMin = ImGui::GetCursorScreenPos();
ImVec2 posMax = ImVec2(posMin.x + menuWidth, posMin.y + ImGui::GetFrameHeight());
headerTops[displayedCount-1] = posMin.y;
optionIDs[displayedCount-1] = rawId-1;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsMouseHoveringRect(posMin, posMax)) {
menuClicked = true;
clickedMenuName = opt.name;
}
if (menuClicked && ImGui::IsMouseDragging(ImGuiMouseButton_Left) && draggedMenuName.empty() && clickedMenuName == opt.name) {
draggedMenuName = opt.name;
draggedId = rawId-1;
draggedOpt = opt;
continue;
}
if (updateStates) { ImGui::SetNextItemOpen(opt.open); }
if (ImGui::CollapsingHeader((opt.name + "##sdrpp_main_menu").c_str())) {
if (item.inst != NULL) {
window->WorkRect = orginalRect;
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = item.inst->isEnabled();
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) {
enabled ? item.inst->enable() : item.inst->disable();
changed = true;
}
ImGui::SetCursorPos(pos);
}
// Check if the state changed
if (!opt.open && !updateStates) {
opt.open = true;
changed = true;
}
item.drawHandler(item.ctx);
ImGui::Spacing();
}
@ -59,17 +124,90 @@ void Menu::draw() {
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = item.inst->isEnabled();
if (ImGui::Checkbox(("##_menu_checkbox_" + name).c_str(), &enabled)) {
if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) {
enabled ? item.inst->enable() : item.inst->disable();
changed = true;
}
ImGui::SetCursorPos(pos);
if (opt.open && !updateStates) {
opt.open = false;
changed = true;
}
}
else if (opt.open && !updateStates) {
opt.open = false;
changed = true;
}
}
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left) && menuClicked) {
if (!draggedMenuName.empty()) {
// Move menu
order.erase(order.begin() + draggedId);
if (insertBefore == displayedCount) {
order.push_back(draggedOpt);
}
else if (insertBeforeName != "") {
int beforeId = 0;
for (int i = 0; i < order.size(); i++) {
if (order[i].name == insertBeforeName) {
beforeId = i;
break;
}
}
order.insert(order.begin() + beforeId, draggedOpt);
}
changed = true;
}
menuClicked = false;
draggedMenuName = "";
insertBeforeName = "";
insertBefore = -1;
}
if (insertBefore == displayedCount && !draggedMenuName.empty()) {
if (updateStates) { ImGui::SetNextItemOpen(false); }
ImVec2 posMin = ImGui::GetCursorScreenPos();
ImVec2 posMax = ImVec2(posMin.x + menuWidth, posMin.y + ImGui::GetFrameHeight());
style::beginDisabled();
ImRect orignalRect = window->WorkRect;
ImGui::CollapsingHeader((draggedMenuName + "##sdrpp_main_menu_dragging").c_str());
if (items[draggedOpt.name].inst != NULL) {
window->WorkRect = orignalRect;
ImVec2 pos = ImGui::GetCursorPos();
ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6);
ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight());
bool enabled = items[draggedOpt.name].inst->isEnabled();
ImGui::Checkbox(("##_menu_checkbox_" + draggedOpt.name).c_str(), &enabled);
ImGui::SetCursorPos(pos);
}
style::endDisabled();
window->DrawList->AddRect(posMin, posMax, textColor);
}
if (!draggedMenuName.empty()) {
insertBefore = displayedCount;
ImVec2 mPos = ImGui::GetMousePos();
for (int i = 0; i < displayedCount; i++) {
if (headerTops[i] > mPos.y) {
insertBefore = i;
insertBeforeName = order[optionIDs[i]].name;
break;
}
}
}
return changed;
}
bool Menu::isInOrderList(std::string name) {
for (std::string _name : order) {
if (_name == name) {
for (MenuOption_t opt : order) {
if (opt.name == name) {
return true;
}
}

View File

@ -4,10 +4,17 @@
#include <map>
#include <module.h>
#define MAX_MENU_COUNT 1024
class Menu {
public:
Menu();
struct MenuOption_t {
std::string name;
bool open;
};
struct MenuItem_t {
void (*drawHandler)(void* ctx);
void* ctx;
@ -16,12 +23,24 @@ public:
void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL);
void removeEntry(std::string name);
void draw();
bool draw(bool updateStates);
std::vector<std::string> order;
std::vector<MenuOption_t> order;
private:
bool isInOrderList(std::string name);
bool menuClicked = false;
std::string clickedMenuName = "";
std::string draggedMenuName = "";
int insertBefore = -1;
std::string insertBeforeName = "";
int draggedId = 0;
MenuOption_t draggedOpt;
std::map<std::string, MenuItem_t> items;
float headerTops[MAX_MENU_COUNT];
int optionIDs[MAX_MENU_COUNT];
};

View File

@ -0,0 +1,45 @@
#include <gui/widgets/volume_meter.h>
#include <algorithm>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
namespace ImGui {
void SNRMeter(float val, const ImVec2& size_arg = ImVec2(0, 0)) {
ImGuiWindow* window = GetCurrentWindow();
ImGuiStyle& style = GImGui->Style;
float pad = style.FramePadding.y;
ImVec2 min = window->DC.CursorPos;
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), 26);
ImRect bb(min, min + size);
ImU32 text = ImGui::GetColorU32(ImGuiCol_Text);
float lineHeight = size.y;
ItemSize(size, style.FramePadding.y);
if (!ItemAdd(bb, 0)) {
return;
}
val = std::clamp<float>(val, 0, 100);
float ratio = size.x / 90;
float it = size.x / 9;
char buf[32];
window->DrawList->AddRectFilled(min + ImVec2(0, 1), min + ImVec2(roundf((float)val * ratio), 10), IM_COL32(0, 136, 255, 255));
window->DrawList->AddLine(min, min + ImVec2(0, 9), text);
window->DrawList->AddLine(min + ImVec2(0, 9), min + ImVec2(size.x + 1, 9), text);
for (int i = 0; i < 10; i++) {
window->DrawList->AddLine(min + ImVec2(roundf((float)i * it), 9), min + ImVec2(roundf((float)i * it), 14), text);
sprintf(buf, "%d", i * 10);
ImVec2 sz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(min + ImVec2(roundf(((float)i * it) - (sz.x/2.0)) + 1, 16), text, buf);
}
}
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <imgui/imgui.h>
namespace ImGui {
void SNRMeter(float val, const ImVec2& size_arg = ImVec2(0, 0));
}

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