247 Commits

Author SHA1 Message Date
a2d93915e8 Added airspy_source to defaults 2021-02-04 14:53:12 +01:00
29e9db184f Fixed small bug in SSB tuning 2021-02-02 21:49:35 +01:00
2f93c7ae58 Fixed wrong sample rate at startup 2021-01-30 02:21:30 +01:00
4abfe407da Removed bad files 2021-01-29 18:29:02 +01:00
9b27e81091 Fixed autobuild 3 2021-01-29 18:26:18 +01:00
39787743fd Fixed autobuild 2 2021-01-29 17:44:56 +01:00
22e47807b8 Fixed autobuild 2021-01-29 17:42:02 +01:00
898525a6d8 Added automatic build to actions 2021-01-29 17:24:10 +01:00
1ebcfe7d80 UI improvements 2021-01-29 16:50:57 +01:00
1dbc39b970 Added persistant config to airspyhf_source 2021-01-29 16:15:13 +01:00
80f5f6c288 Added persistant config to airspy_source + bugfix 2021-01-29 15:07:45 +01:00
b18acd469f Fixed missing dependency in CI 2021-01-28 21:13:09 +01:00
cefcd18269 Added airspy module + changes to the UI for scaling 2021-01-28 21:10:53 +01:00
4de3ac176d Update cmake.yml 2021-01-11 21:47:35 +01:00
afd5699ff1 Added new contributors + fixed waterfall bug 2021-01-11 03:19:09 +01:00
b79461e3ce Merge pull request #60 from mnhauke/master
Add bandplan for German mobile networks
2021-01-03 02:49:20 +01:00
de6ab8ecdf Add bandplan for LTE bands used in Germany 2021-01-02 23:43:49 +01:00
d0180d42a8 Add bandplan for German mobile networks 2021-01-02 21:59:13 +01:00
2e504b40f6 Fixed invalid colormap 2021-01-02 15:18:57 +01:00
9b00304c29 Fixed resampling bug + added waterfall colormap selection + general bugfix 2020-12-31 14:42:09 +01:00
979928ded8 Fixed resampling bug + added waterfall colormap selection + general bugfix 2020-12-31 14:26:12 +01:00
7c4e442432 Fixed gain not updated on RTL-SDR 2020-12-28 14:47:34 +01:00
0dd445f101 Merge pull request #55 from cropinghigh/master
Stepped sliders+bandwidth
2020-12-28 14:40:30 +01:00
f217804838 push before merge 2020-12-28 14:39:30 +01:00
9a630fff06 Merge branch 'master' of https://github.com/cropinghigh/SDRPlusPlus 2020-12-28 16:06:34 +03:00
db508214d7 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2020-12-28 16:05:57 +03:00
8e764f48ae Fix unusable bw 2020-12-28 16:05:35 +03:00
2583063f5f Update
Update
2020-12-27 15:30:51 +03:00
dd4ec22b39 + contributors list 2020-12-26 23:12:09 +01:00
42dbcec93f Update
Update
2020-12-27 01:11:05 +03:00
9bbf634f5d Merge pull request #52 from zakrent/master
Added SIMD to polyphase resampler
2020-12-26 23:08:45 +01:00
d6b09759de Merge pull request #51 from cropinghigh/patch-1
Addition to linux guide
2020-12-26 23:08:00 +01:00
5bb8a943ad push before merge 2020-12-26 23:02:07 +01:00
b370eda0d5 Fix bugs+move widget 2020-12-27 00:56:39 +03:00
69bcbf6f27 :Added SIMD to polyphase resampler 2020-12-26 22:18:34 +01:00
04823abb83 Fix bugs 2020-12-26 21:36:16 +03:00
7269a0ea12 Merge branch 'patch-1' of https://github.com/cropinghigh/SDRPlusPlus 2020-12-26 21:02:43 +03:00
153b58fbbd Stepped sliders 2020-12-26 21:00:09 +03:00
149af55e61 Addition to linux guide 2020-12-26 11:47:37 +03:00
9cac95fd82 push before merge 2020-12-26 00:48:12 +01:00
09498f3b18 Merge pull request #50 from AlexandreRouma/revert-49-master
Revert " Added recorder volume meter "
2020-12-26 00:47:22 +01:00
bb919d0f32 Revert " Added recorder volume meter " 2020-12-26 00:46:52 +01:00
6d0abd73a5 Merge pull request #49 from zakrent/master
Added recorder volume meter
2020-12-26 00:42:32 +01:00
717f2a822b Merge branch 'master' of https://github.com/zakrent/SDRPlusPlus 2020-12-26 01:17:37 +01:00
8946b4b4b6 Added recorder volume meter 2020-12-26 01:07:07 +01:00
db279d2b36 Fixed audio freeze on linux 2020-12-25 19:58:52 +01:00
bb7965b3c4 Fixed luckup bug 2020-12-25 18:17:43 +01:00
a33fe5a4cc Added cropinghigh to contributor's list 2020-12-25 17:22:24 +01:00
4a03f0870c Merge pull request #47 from cropinghigh/patch-3
Update russian bandplan
2020-12-25 17:10:33 +01:00
bfe15aff19 Merge pull request #48 from AlexandreRouma/double_bufferd_streams
switched all streams to double buffering
2020-12-25 17:10:13 +01:00
42bc2d01f7 switched all streams to double buffering 2020-12-25 16:58:07 +01:00
0cb9fc0df8 Update russian bandplan 2020-12-25 16:39:24 +03:00
450896b122 OpenGL version fix for shitty SoCs 2020-12-24 23:38:45 +01:00
cc0b89dbe2 fixed wrong dependency in readme 2020-12-24 19:44:17 +01:00
c887b96a77 fixed wrong dependency in readme 2020-12-24 19:44:07 +01:00
22541ae0f4 updated readme 2020-12-24 16:27:12 +01:00
d83da38d79 Added windows build script 2020-12-24 14:43:14 +01:00
b21f8abbd6 fixed missing module 2020-12-24 00:11:33 +01:00
e9aade4d0d fixed debian build again again 2020-12-23 23:39:34 +01:00
2bf2fff3d6 fixed debian build again 2020-12-23 23:34:56 +01:00
463a22fdfb fixed debian build 2 2020-12-23 23:20:28 +01:00
3175022b31 fixed debian build 2020-12-23 23:18:44 +01:00
504d910226 removed automated debian package build 2020-12-23 22:21:46 +01:00
c2769e1a72 fixed CI 2 2020-12-23 21:59:05 +01:00
7577253dbf fixed CI 2020-12-23 21:19:02 +01:00
e4c5b2dbd1 fixed directories 2020-12-23 21:10:24 +01:00
fafd76ff94 Added debian package build 2020-12-23 20:58:02 +01:00
e354d11820 Added debian package build 2020-12-23 20:57:41 +01:00
a3374c7eca fixed airspyhf module missing function 2020-12-23 20:22:20 +01:00
552b886cea fixed volk version in CI 2020-12-23 20:10:55 +01:00
ff9a19381b switch to more recent ubuntu version for CI 2020-12-23 20:09:08 +01:00
77aacc2e5d Fixed compiler version 2020-12-23 19:50:54 +01:00
6a1fa2c13b added more recent GCC version to CI 2020-12-23 19:44:34 +01:00
c3bb64bf6e Update cmake.yml 2020-12-23 19:39:14 +01:00
da68ab4ed0 Create cmake.yml 2020-12-23 19:36:12 +01:00
1aedf92bcd fixes to the rtl-tcp source 2020-12-23 19:23:47 +01:00
abcf484506 removed un-necessary directory 2020-12-23 00:12:52 +01:00
a93681a980 New system for band plans 2020-12-23 00:11:12 +01:00
a08758ea54 fixed directory bug on linux 2 2020-12-22 23:10:49 +01:00
0a0f5b8e8c fixed directory bug on linux 2020-12-22 23:04:46 +01:00
84f67a3ac1 updated readme about new directory system 2020-12-22 22:45:27 +01:00
22d18a9e58 new directory system on linux 2020-12-22 22:39:24 +01:00
d1a8425d43 Create FUNDING.yml 2020-12-22 21:42:16 +01:00
65d94f03e4 Fixed compile bugs 2020-12-22 21:23:49 +01:00
3a49041f27 other fix 2020-12-22 21:09:49 +01:00
eec2f7c4a0 fixed bug in spyserevr source 2020-12-22 20:44:49 +01:00
720df5ce89 Fixed build issues 2 2020-12-22 20:37:10 +01:00
d7cea16d4a Fixed build issues 2020-12-22 20:35:31 +01:00
d5c0fdd525 fixed linux build bug 2020-12-22 20:00:51 +01:00
4a86d6073c added better credit system 2020-12-22 18:42:30 +01:00
98b6e580b4 updated readme 2020-12-22 18:23:26 +01:00
c96c69c112 changes to the build system 2020-12-22 14:56:57 +01:00
bd545feb2c changes to the build system 2020-12-22 14:50:26 +01:00
e90b6656c3 Fixed airspy hf+ module bug 2020-12-16 01:45:17 +01:00
5099c16a12 Added airpyhf_source 2020-12-15 23:05:11 +01:00
d9dcfa4a88 Fixed waterfall non-threadsafe behavior 2020-12-14 19:33:30 +01:00
1fcd783dd9 fixed zoom bug 2020-12-14 18:14:04 +01:00
2c84123158 modified soapy menu 2020-12-14 17:18:43 +01:00
c3d39029b8 Merge pull request #42 from wingrime/sample-rate
Soapysdr: Improve sample rate information
2020-12-14 17:01:50 +01:00
20b703f8bf push before merge 2020-12-14 16:37:56 +01:00
89c579880c Soapy: Refresh button 2020-12-14 19:46:05 +05:00
db389372ad Soapysdr: Improve sample rate information 2020-12-14 19:34:44 +05:00
3a6eaf6526 Removed second radio from the default config 2020-12-14 01:15:52 +01:00
3e27af472b added more info to the readme 2020-12-14 01:07:30 +01:00
7bea6058fe modified readme 2020-12-14 00:47:11 +01:00
b02b6c30b5 Merge pull request #40 from AlexandreRouma/better_dsp
Better dsp
2020-12-14 00:23:35 +01:00
46e9266752 fixed soapy bug 2020-12-13 14:52:54 +01:00
e3db19b16a Bugfixed + performance improvements to the waterfall 2020-12-12 05:34:58 +01:00
774663d70d Added squelch to radio 2020-12-10 05:18:40 +01:00
2c729bf646 Added persistant settings to recorder module 2020-12-09 19:45:32 +01:00
9b1c9e9e29 Added persistant config for the selected demodulator 2020-12-09 15:28:31 +01:00
80badebb37 Added persistant setting sto demodulator of radio module 2020-12-09 15:16:38 +01:00
fc9e155481 random bug fixes 2020-12-09 04:47:30 +01:00
16d8a31c12 center tuning and more 2020-12-08 16:27:52 +01:00
7ba6081cb3 tweaks 2020-12-08 04:44:19 +01:00
c3a8865dd3 Fixed a tone of stuff + new features 2020-12-08 04:36:37 +01:00
929ca50b06 Added AGC + Started working on channel selection 2020-12-07 04:13:16 +01:00
e5123dd8bf Fixed UI bug in radio 2020-12-06 20:02:22 +01:00
fe1de4bed9 Fixed SSB demod bug 2020-12-06 19:51:56 +01:00
c612620ca5 testing 2020-12-06 17:57:44 +01:00
ca9d2c01af other potential fix 2020-12-06 17:26:42 +01:00
51d90c1898 fixed missing include 2020-12-06 17:02:47 +01:00
a6a4193fbb potential fix to audio issues 2020-12-06 16:46:50 +01:00
f4f8c77ffa Fixed bugs + new radio 2020-12-06 16:13:47 +01:00
9b8c1a3072 More bugfix + folder selection in recorder 2020-12-05 22:42:12 +01:00
92b77904f6 more fixes 2020-12-04 20:12:36 +01:00
9805e4a395 Fixed loading screen 2020-11-30 21:17:36 +01:00
6a01c9d426 trying to fix underrun when switching sdr 2020-11-30 17:43:53 +01:00
48df92c8a5 Fixed bug in GUI 2020-11-30 16:45:02 +01:00
5bb2f9bf05 Fixed compile bug on linux 2020-11-30 16:30:45 +01:00
e5dbac4345 Finished sink module system + new icons 2020-11-30 16:05:51 +01:00
618d4ac4cc fix 2020-11-30 05:51:33 +01:00
19e516f206 Push before potential f*ck up 2020-11-29 20:55:00 +01:00
afadb71d64 Fixed sample rate bug 2020-11-28 23:25:14 +01:00
b3d1eabbad Fixed sample rate bug 2020-11-28 23:24:45 +01:00
eac0a7a13f Fixed FIR bug with the pluto module 2020-11-26 19:25:58 +01:00
e06ed84330 fixed wrong ip string 2020-11-25 21:54:30 +01:00
4eae7c3ba5 plutosdr support 2020-11-25 21:52:37 +01:00
61a612cf30 More work on the sink interface 2020-11-22 18:26:48 +01:00
f1084157a3 fixed issues at 64MS/s 2020-11-13 02:04:37 +01:00
de3b056133 Partial RX888 source module 2020-11-12 21:23:18 +01:00
02ae50905d added rx888 2020-11-12 00:53:38 +01:00
0a5fd5c271 Fixed small bug in waterfall 2020-11-04 05:08:42 +01:00
ef968ac1fb finally fixed the waterfall7 2020-11-04 04:11:51 +01:00
3156236745 Fixed DSP 2020-11-04 00:42:39 +01:00
5d320fdd53 Fixes 2020-11-03 19:22:53 +01:00
cee6af1e14 fixing audio bug 2020-11-02 21:13:28 +01:00
35c7f0e3cf Fixed stall 2020-11-02 17:48:17 +01:00
fc9bc496cb fixed 2020-11-02 16:16:21 +01:00
75f8a45119 new dsp 2020-11-02 03:57:44 +01:00
50a73a380d more fixes 2020-10-31 15:07:49 +01:00
e62042d26a Update readme.md 2020-10-24 18:56:32 +02:00
c109de3949 Merge pull request #31 from aosync/openbsd
Build on OpenBSD
2020-10-24 18:54:47 +02:00
39c87782db Merge branch 'experimental' into openbsd 2020-10-24 18:54:34 +02:00
b6566dde14 Merge pull request #32 from howard0su/experimental
Add instruction for Linux
2020-10-24 17:50:32 +02:00
ef36283370 Add instruction for Linux 2020-10-24 23:41:40 +08:00
922a226028 fixed OpenBSD build 4 2020-10-24 17:34:18 +02:00
ba81f25933 Added OpenBSD build instructions 2020-10-24 17:18:54 +02:00
da9528576a fixed openbsd build 3 2020-10-24 17:09:25 +02:00
3fdd2477e5 Fixed typo in root CMakeLists.txt 2020-10-24 15:33:00 +02:00
62368e35a7 Merge pull request #29 from aosync/experimental
make prepare_root.sh an executable and take advantage of globbing
2020-10-24 14:56:10 +02:00
82d3431f1d Merge pull request #28 from howard0su/experimental
Build system fix and cleanup
2020-10-24 14:55:41 +02:00
edbc0c149d fixed linux bugs 2020-10-24 14:51:55 +02:00
b8987e6d2d make prepare_root.sh a proper program and take advantage of globbing 2020-10-24 09:44:15 +02:00
6296b8865b fftw lib is not used but fftw3f only 2020-10-23 17:07:02 +08:00
2df185e340 Copy volk.dll when building on Windows 2020-10-23 17:06:10 +08:00
6262c64daa Consolidate Linux and OSX build
Use pkg-config to find the right include path and lib names.
2020-10-23 10:53:48 +08:00
0fe5af9816 Fix volk.h include path. It should be volk/volk.h 2020-10-23 10:39:20 +08:00
e94888d533 Use pkg for every module, and link them static 2020-10-23 10:37:10 +08:00
6130428989 glfwSetWindowMaximizeCallback is added in 3.3 2020-10-23 10:36:14 +08:00
5400a4e18a Fix build on Debian 10 2020-10-22 23:55:49 +08:00
0d45217dfd Added baseband recording 2020-10-22 12:53:46 +02:00
fa1e647235 Merge pull request #26 from AlexandreRouma/better_modules
Better modules
2020-10-22 11:03:32 +02:00
d637cb9e75 Merge pull request #24 from howard0su/osx
another warning
2020-10-22 11:01:24 +02:00
406f18bf11 another warning 2020-10-22 09:18:58 +08:00
72611b5fa7 Fixed OpenGL memory leak 2020-10-22 03:16:11 +02:00
313b786d88 Merge pull request #23 from howard0su/warnings
Fix warnings
2020-10-22 02:54:01 +02:00
801a56787f Merge pull request #22 from howard0su/osx
Fix OSX build
2020-10-22 02:53:50 +02:00
62868b2533 Fix warnings 2020-10-22 08:48:31 +08:00
4bf88739b5 Fix OSX build 2020-10-22 08:33:55 +08:00
087380c966 Push before merge 2020-10-22 02:33:50 +02:00
fbd7321b48 Fixed typo 2020-10-20 19:55:14 +02:00
f6cfe83d45 Fixed typo 2020-10-20 19:15:43 +02:00
71f6be8d08 Fixed VFO alignment 2020-10-20 14:59:42 +02:00
6e5450ed24 new fixes 2020-10-20 00:32:17 +02:00
027054b57e Fixed waterfall inaccuracies + started adding stepped VFO 2020-10-15 16:09:01 +02:00
3b6a3ff94d Fixed scripting + cleaner code + fixed RTLTCP 2020-10-07 22:44:54 +02:00
46d5b8a750 new scripting system 2020-10-07 14:44:39 +02:00
ac068036b8 Finished soapy module + added file source + added RTL_TCP source (windows only rn) 2020-10-06 15:50:46 +02:00
ace1fe1e5e added linux support for rtl_tcp 2020-10-06 10:54:00 +02:00
14a8e81662 Fixed missing label bug of soapysdr 2020-10-05 18:20:27 +02:00
eff7bbdd5a added new patrons 2020-10-04 15:23:40 +02:00
60342de9c0 Finished soapy module + added file source + added RTL_TCP source (windows only rn) 2020-10-04 02:56:02 +02:00
47b04ffef4 More work on the source module system 2020-10-02 01:44:18 +02:00
1507e6ec31 New module system 2020-10-01 13:46:12 +02:00
524f20bc2f Save before changes 2020-10-01 01:21:15 +02:00
2c4d7cbf09 Moved menus to their own respective files 2020-09-25 14:25:36 +02:00
5fedda08d7 More fixes 3 2020-09-24 19:50:22 +02:00
2056eae139 More fixes 2 2020-09-24 19:38:05 +02:00
48a8b04eaa More fixes 2020-09-24 19:36:57 +02:00
51ee02f9da Fixed windows bugs 2020-09-20 02:18:01 +02:00
ab8ce4c53f Fixed warnings on linux 2020-09-20 01:36:25 +02:00
2aaf254565 Fixed issues with new module system 6 2020-09-20 00:56:00 +02:00
109696c65a Fixed issues with new module system 5 2020-09-20 00:44:45 +02:00
35ef99c6e9 Fixed issues with new module system 4 2020-09-20 00:41:35 +02:00
91d382ca0c Fixed issues with new module system 3 2020-09-20 00:33:38 +02:00
ec234e99a1 Fixed issues with new module system 2 2020-09-20 00:26:45 +02:00
9de585190f Fixed issues with new module system 2020-09-20 00:19:39 +02:00
d6b9e1d86a new modole system 2020-09-19 12:48:34 +02:00
1ef31f0f8b new stuff 2020-09-18 00:23:03 +02:00
c1052b1b28 easier build 2020-09-06 16:31:50 +02:00
e497122c06 Merge pull request #17 from howard0su/fix_warn
Add a missing else to shutdown a warning message
2020-09-06 16:43:16 +03:00
407fcaadc6 Merge pull request #18 from howard0su/fil
cleanup DecimatingFIRFilter code
2020-09-06 16:43:05 +03:00
7e6f24d203 Merge pull request #16 from howard0su/fix_vfo
Freq can be zero
2020-09-06 16:42:41 +03:00
c0825dbeeb Push before merge 2020-09-06 15:39:09 +02:00
39a65b51fb optimize DecimatingFIRFilter 2020-09-06 08:42:05 +08:00
acf3fe0297 Add a missing else to shutdown a warning message 2020-09-06 08:40:42 +08:00
70c2ef36f5 Freq can be zero
In Zero-IF scenerio, freq can be zero.
2020-09-06 08:38:50 +08:00
7190acfe9e Fixed band plan name not appearing correctly 2020-08-21 17:12:48 +02:00
bf6210721d Fixed config not saved properly 2020-08-21 17:11:12 +02:00
27731f376a fixed default config file 2020-08-21 15:47:19 +02:00
d82260d4d4 New stuff ++++ 2020-08-21 15:38:27 +02:00
78086a79f4 New stuff +++ 2020-08-21 15:37:34 +02:00
aa2caa67ad New stuff ++ 2020-08-21 15:34:50 +02:00
709627a738 New stuff lmao 2020-08-20 18:29:23 +02:00
649be86cb6 added a recorder module 2020-08-18 00:56:51 +02:00
b56aab8f74 new styles 2020-08-17 02:39:56 +02:00
53ec38766a bruh 2020-08-16 19:34:53 +02:00
dbe811b47a bruh 2020-08-16 19:28:14 +02:00
f08515420e bruh 2020-08-16 19:07:17 +02:00
11913ab683 bruh 2020-08-16 19:01:59 +02:00
1cecc78c0c bruh 2020-08-16 18:57:50 +02:00
6717c43fc2 bruh 2020-08-16 18:51:20 +02:00
e44d20bdbc bruh 2020-08-16 18:46:30 +02:00
e50ed1b960 bruh 2020-08-16 18:30:40 +02:00
cff5987329 Fixed delete[] in soapy.h 2020-08-16 18:22:22 +02:00
b2191c5d2c Fixed delete[] in soapy.h 2020-08-16 18:18:10 +02:00
03dc5d2042 Fixed SoapySDR trying to set gain on no device 2020-08-16 18:06:21 +02:00
19e07eb767 Fixed gain memory error 2020-08-16 17:56:12 +02:00
c4f49203a1 testing on linux 2020-08-16 17:36:48 +02:00
9830337103 small fixes 2020-08-16 14:26:22 +02:00
eadaf3ce6b a LOT of new stuff 2020-08-16 03:39:05 +02:00
31a95031e4 Full module system 2020-08-12 16:43:44 +02:00
cdea80f8c5 modules 2020-08-11 18:33:42 +02:00
b65bddc1b3 multi-vfo 2020-08-10 02:30:25 +02:00
7759de96da added module system 2020-08-07 14:29:06 +02:00
9d2b60b88e Updated screenshot 2020-08-05 23:27:39 +02:00
302 changed files with 128846 additions and 3806 deletions

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

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

45
.github/workflows/cmake.yml vendored Normal file
View File

@ -0,0 +1,45 @@
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

10
.gitignore vendored
View File

@ -1,3 +1,11 @@
build/ build/
.vscode/ .vscode/
*.old *.old
*.dll
*.exe
*.zip
*.wav
.DS_Store
root_dev/
sdrpp_v0.2.5_beta_x64
sdrpp_v0.2.5_beta_x64_new_wf

View File

@ -1,59 +1,81 @@
cmake_minimum_required(VERSION 3.9) cmake_minimum_required(VERSION 3.13)
project(sdrpp) 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)
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_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON)
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: portaudio)" ON)
# 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_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_PLUTOSDR_SOURCE)
add_subdirectory("plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_AUDIO_SINK)
add_subdirectory("audio_sink")
endif (OPT_BUILD_AUDIO_SINK)
if (MSVC) if (MSVC)
set(CMAKE_CXX_FLAGS "-O2 /std:c++17") set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc")
link_directories(sdrpp "C:/Program Files/PothosSDR/lib/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/volk/")
include_directories(sdrpp "C:/Program Files/PothosSDR/include/")
else() else()
set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -fsanitize=address -g") set(CMAKE_CXX_FLAGS "-O3 -std=c++17")
include_directories(sdrpp "/usr/include/volk")
link_libraries(pthread)
link_libraries(GL)
link_libraries(GLEW)
link_libraries(glfw)
link_libraries(fftw3)
link_libraries(fftw3f)
link_libraries(portaudio)
link_libraries(X11)
link_libraries(Xxf86vm)
endif (MSVC) endif (MSVC)
link_libraries(volk) add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
link_libraries(SoapySDR) target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Main code
include_directories(sdrpp "src/")
include_directories(sdrpp "src/imgui")
file(GLOB SRC "src/*.cpp")
file(GLOB IMGUI "src/imgui/*.cpp")
add_executable(sdrpp ${SRC} ${IMGUI})
# Copy dynamic libs over
if (MSVC) if (MSVC)
# Glew add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
find_package(GLEW REQUIRED) add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
target_link_libraries(sdrpp PRIVATE GLEW::GLEW)
# GLFW3
find_package(glfw3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE glfw)
# FFTW3
find_package(FFTW3 CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3)
find_package(FFTW3f CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE FFTW3::fftw3f)
# PortAudio
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp PRIVATE portaudio portaudio_static)
endif (MSVC) endif (MSVC)
# # Copy resource directories
# if (!MSVC)
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/res ${CMAKE_BINARY_DIR}/res)
# add_custom_command(TARGET sdrpp POST_BUILD COMMAND cmake -E copy_directory ${CMAKE_SOURCE_DIR}/bandplans ${CMAKE_BINARY_DIR}/bandplans)
# endif (MSVC)
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64" 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 "Linux")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.so\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/Users/Alex/vcpkg/scripts/buildsystems/vcpkg.cmake" -G "Visual Studio 15 2017 Win64"

View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.13)
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)
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(airspy_source SHARED ${SRC})
target_link_libraries(airspy_source PRIVATE sdrpp_core)
set_target_properties(airspy_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(airspy_source PUBLIC airspy)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBAIRSPYHF 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)

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

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

View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.13)
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)
include_directories("src/")
file(GLOB SRC "src/*.cpp")
add_library(airspyhf_source SHARED ${SRC})
target_link_libraries(airspyhf_source PRIVATE sdrpp_core)
set_target_properties(airspyhf_source PROPERTIES PREFIX "")
if (MSVC)
# Lib path
target_link_directories(sdrpp_core PUBLIC "C:/Program Files/PothosSDR/bin/")
target_link_libraries(airspyhf_source PUBLIC airspyhf)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(LIBAIRSPYHF REQUIRED libairspyhf)
target_include_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_INCLUDE_DIRS})
target_link_directories(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARY_DIRS})
target_link_libraries(airspyhf_source PUBLIC ${LIBAIRSPYHF_LIBRARIES})
endif (MSVC)

View File

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

32
audio_sink/CMakeLists.txt Normal file
View File

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.13)
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)
file(GLOB SRC "src/*.cpp")
include_directories("src/")
add_library(audio_sink SHARED ${SRC})
target_link_libraries(audio_sink PRIVATE sdrpp_core)
set_target_properties(audio_sink PROPERTIES PREFIX "")
if (MSVC)
find_package(portaudio CONFIG REQUIRED)
target_link_libraries(sdrpp_core PUBLIC portaudio)
else (MSVC)
find_package(PkgConfig)
pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0)
target_include_directories(sdrpp_core PUBLIC ${PORTAUDIO_INCLUDE_DIRS})
target_link_directories(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARY_DIRS})
target_link_libraries(sdrpp_core PUBLIC ${PORTAUDIO_LIBRARIES})
endif (MSVC)

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

@ -0,0 +1,290 @@
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <signal_path/sink.h>
#include <portaudio.h>
#include <dsp/audio.h>
#include <spdlog/spdlog.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO {
/* Name: */ "audio_sink",
/* Description: */ "Audio sink module for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ 1
};
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);
// Initialize PortAudio
devCount = Pa_GetDeviceCount();
devId = Pa_GetDefaultOutputDevice();
const PaDeviceInfo *deviceInfo;
PaStreamParameters outputParams;
outputParams.sampleFormat = paFloat32;
outputParams.hostApiSpecificStreamInfo = NULL;
// 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;
txtDevList += '\0';
}
// Load config from file
}
~AudioSink() {
for (auto const& dev : devices) {
delete dev;
}
}
void start() {
if (running) {
return;
}
doStart();
running = true;
}
void stop() {
if (!running) {
return;
}
doStop();
running = false;
}
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
}
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 (running) {
doStop();
doStart();
}
// TODO: Save to config
}
}
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;
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);
}
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));
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;
}
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();
}
static int _mono_cb(const void *input, void *output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) {
AudioSink* _this = (AudioSink*)userData;
_this->monoRB.data.read((float*)output, frameCount);
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);
return 0;
}
SinkManager::Stream* _stream;
dsp::StereoToMono s2m;
dsp::RingBufferSink<float> monoRB;
dsp::RingBufferSink<dsp::stereo_t> stereoRB;
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
};
std::vector<AudioDevice_t*> devices;
std::vector<std::string> deviceNames;
std::string txtDevList;
};
class AudioSinkModule : public ModuleManager::Instance {
public:
AudioSinkModule(std::string name) {
this->name = name;
provider.create = create_sink;
provider.ctx = this;
Pa_Initialize();
sigpath::sinkManager.registerSinkProvider("Audio", provider);
}
~AudioSinkModule() {
Pa_Terminate();
}
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static SinkManager::Sink* create_sink(SinkManager::Stream* stream, std::string streamName, void* ctx) {
return (SinkManager::Sink*)(new AudioSink(stream, streamName));
}
std::string name;
bool enabled = true;
SinkManager::SinkProvider provider;
};
MOD_EXPORT void _INIT_() {
// Nothing here
// TODO: Do instancing here (in source modules as well) to prevent multiple loads
}
MOD_EXPORT void* _CREATE_INSTANCE_(std::string name) {
AudioSinkModule* instance = new AudioSinkModule(name);
return instance;
}
MOD_EXPORT void _DELETE_INSTANCE_() {
}
MOD_EXPORT void _END_() {
}

View File

@ -1,7 +0,0 @@
{
"broadcast": "#0000FFFF",
"amateur": "#FF0000FF",
"aviation": "#00FF00FF",
"marine": "#00FFFFFF",
"military": "#FFFF00FF"
}

90
core/CMakeLists.txt Normal file
View File

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

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

@ -0,0 +1,118 @@
#include <config.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <filesystem>
ConfigManager::ConfigManager() {
}
ConfigManager::~ConfigManager() {
disableAutoSave();
}
void ConfigManager::setPath(std::string file) {
path = file;
}
void ConfigManager::load(json def, bool lock) {
if (lock) { mtx.lock(); }
if (path == "") {
spdlog::error("Config manager tried to load file with no path specified");
return;
}
if (!std::filesystem::exists(path)) {
spdlog::warn("Config file '{0}' does not exist, creating it", path);
conf = def;
save(false);
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Config file '{0}' isn't a file", path);
return;
}
std::ifstream file(path.c_str());
file >> conf;
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::save(bool lock) {
if (lock) { mtx.lock(); }
std::ofstream file(path.c_str());
file << conf.dump(4);
file.close();
if (lock) { mtx.unlock(); }
}
void ConfigManager::enableAutoSave() {
if (!autoSaveEnabled) {
autoSaveEnabled = true;
autoSaveThread = std::thread(autoSaveWorker, this);
}
}
void ConfigManager::disableAutoSave() {
if (autoSaveEnabled) {
autoSaveEnabled = false;
autoSaveThread.join();
}
}
void ConfigManager::aquire() {
mtx.lock();
}
void ConfigManager::release(bool changed) {
this->changed |= changed;
mtx.unlock();
}
void ConfigManager::autoSaveWorker(ConfigManager* _this) {
while (_this->autoSaveEnabled) {
if (!_this->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);
}
_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;
// }

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

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

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

@ -0,0 +1,358 @@
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <gui/icons.h>
#include <version.h>
#include <spdlog/spdlog.h>
#include <gui/widgets/bandplan.h>
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <options.h>
#include <duktape/duktape.h>
#include <duktape/duk_console.h>
#include <filesystem>
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#ifdef _WIN32
#include <Windows.h>
#endif
namespace core {
ConfigManager configManager;
ScriptManager scriptManager;
ModuleManager moduleManager;
void setInputSampleRate(double samplerate) {
// NOTE: Zoom controls won't work
gui::waterfall.setBandwidth(samplerate);
gui::waterfall.setViewOffset(0);
gui::waterfall.setViewBandwidth(samplerate);
sigpath::signalPath.setSampleRate(samplerate);
setViewBandwidthSlider(samplerate);
}
};
bool maximized = false;
bool fullScreen = false;
static void glfw_error_callback(int error, const char* description) {
spdlog::error("Glfw Error {0}: {1}", error, description);
}
static void maximized_callback(GLFWwindow* window, int n) {
if (n == GLFW_TRUE) {
maximized = true;
}
else {
maximized = false;
}
}
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; }
// Check root directory
if (!std::filesystem::exists(options::opts.root)) {
spdlog::warn("Root directory {0} does not exist, creating it", options::opts.root);
if (!std::filesystem::create_directory(options::opts.root)) {
spdlog::error("Could not create root directory {0}", options::opts.root);
return -1;
}
}
if (!std::filesystem::is_directory(options::opts.root)) {
spdlog::error("{0} is not a directory", options::opts.root);
return -1;
}
// ======== DEFAULT CONFIG ========
json defConfig;
defConfig["bandColors"]["amateur"] = "#FF0000FF";
defConfig["bandColors"]["aviation"] = "#00FF00FF";
defConfig["bandColors"]["broadcast"] = "#0000FFFF";
defConfig["bandColors"]["marine"] = "#00FFFFFF";
defConfig["bandColors"]["military"] = "#FFFF00FF";
defConfig["bandPlan"] = "General";
defConfig["bandPlanEnabled"] = true;
defConfig["centerTuning"] = false;
defConfig["colorMap"] = "Classic";
defConfig["fftHeight"] = 300;
defConfig["frequency"] = 100000000.0;
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";
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["modules"] = json::array();
defConfig["offset"] = 0.0;
defConfig["showWaterfall"] = true;
defConfig["source"] = "";
defConfig["streams"] = json::object();
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";
#ifdef _WIN32
defConfig["modulesDirectory"] = "./modules";
defConfig["resourcesDirectory"] = "./res";
#else
defConfig["modulesDirectory"] = "/usr/lib/sdrpp/plugins";
defConfig["resourcesDirectory"] = "/usr/share/sdrpp";
#endif
// Load config
spdlog::info("Loading config");
core::configManager.setPath(options::opts.root + "/config.json");
core::configManager.load(defConfig);
core::configManager.enableAutoSave();
// Fix config
core::configManager.aquire();
for (auto const& item : defConfig.items()) {
if (!core::configManager.conf.contains(item.key())) {
spdlog::warn("Missing key in config {0}, repairing", item.key());
core::configManager.conf[item.key()] = defConfig[item.key()];
}
}
core::configManager.release(true);
// Setup window
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
return 1;
}
#ifdef __APPLE__
// GL 3.2 + GLSL 150
const char* glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
// GL 3.0 + GLSL 120
const char* glsl_version = "#version 120";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
core::configManager.aquire();
int winWidth = core::configManager.conf["windowSize"]["w"];
int winHeight = core::configManager.conf["windowSize"]["h"];
maximized = core::configManager.conf["maximized"];
std::string resDir = core::configManager.conf["resourcesDirectory"];
json bandColors = core::configManager.conf["bandColors"];
core::configManager.release();
// 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)
return 1;
glfwMakeContextCurrent(window);
#if (GLFW_VERSION_MAJOR == 3) && (GLFW_VERSION_MINOR >= 3)
if (maximized) {
glfwMaximizeWindow(window);
}
glfwSetWindowMaximizeCallback(window, maximized_callback);
#endif
// Load app icon
GLFWimage icons[10];
icons[0].pixels = stbi_load(((std::string)(resDir + "/icons/sdrpp.png")).c_str(), &icons[0].width, &icons[0].height, 0, 4);
icons[1].pixels = (unsigned char*)malloc(16 * 16 * 4); icons[1].width = icons[1].height = 16;
icons[2].pixels = (unsigned char*)malloc(24 * 24 * 4); icons[2].width = icons[2].height = 24;
icons[3].pixels = (unsigned char*)malloc(32 * 32 * 4); icons[3].width = icons[3].height = 32;
icons[4].pixels = (unsigned char*)malloc(48 * 48 * 4); icons[4].width = icons[4].height = 48;
icons[5].pixels = (unsigned char*)malloc(64 * 64 * 4); icons[5].width = icons[5].height = 64;
icons[6].pixels = (unsigned char*)malloc(96 * 96 * 4); icons[6].width = icons[6].height = 96;
icons[7].pixels = (unsigned char*)malloc(128 * 128 * 4); icons[7].width = icons[7].height = 128;
icons[8].pixels = (unsigned char*)malloc(196 * 196 * 4); icons[8].width = icons[8].height = 196;
icons[9].pixels = (unsigned char*)malloc(256 * 256 * 4); icons[9].width = icons[9].height = 256;
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[1].pixels, 16, 16, 16 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[2].pixels, 24, 24, 24 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[3].pixels, 32, 32, 32 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[4].pixels, 48, 48, 48 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[5].pixels, 64, 64, 64 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[6].pixels, 96, 96, 96 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[7].pixels, 128, 128, 128 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[8].pixels, 196, 196, 196 * 4, 4);
stbir_resize_uint8(icons[0].pixels, icons[0].width, icons[0].height, icons[0].width * 4, icons[9].pixels, 256, 256, 256 * 4, 4);
glfwSetWindowIcon(window, 10, icons);
stbi_image_free(icons[0].pixels);
for (int i = 1; i < 10; i++) {
free(icons[i].pixels);
}
if (glewInit() != GLEW_OK) {
spdlog::error("Failed to initialize OpenGL loader!");
return 1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.IniFilename = NULL;
// Setup Platform/Renderer bindings
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
if (!style::setDarkStyle(resDir)) { return -1; }
LoadingScreen::setWindow(window);
LoadingScreen::show("Loading icons");
spdlog::info("Loading icons");
if (!icons::load(resDir)) { return -1; }
LoadingScreen::show("Loading band plans");
spdlog::info("Loading band plans");
bandplan::loadFromDir(resDir + "/bandplans");
LoadingScreen::show("Loading band plan colors");
spdlog::info("Loading band plans color table");
bandplan::loadColorTable(bandColors);
windowInit();
spdlog::info("Ready.");
bool _maximized = maximized;
int fsWidth, fsHeight, fsPosX, fsPosY;
// Main loop
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
//ImGui::ShowDemoWindow();
if (_maximized != maximized) {
_maximized = maximized;
core::configManager.aquire();
core::configManager.conf["maximized"]= _maximized;
if (!maximized) {
glfwSetWindowSize(window, core::configManager.conf["windowSize"]["w"], core::configManager.conf["windowSize"]["h"]);
}
core::configManager.release(true);
}
int _winWidth, _winHeight;
glfwGetWindowSize(window, &_winWidth, &_winHeight);
if (ImGui::IsKeyPressed(GLFW_KEY_F11)) {
fullScreen = !fullScreen;
if (fullScreen) {
spdlog::info("Fullscreen: ON");
fsWidth = _winWidth;
fsHeight = _winHeight;
glfwGetWindowPos(window, &fsPosX, &fsPosY);
const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, 0);
}
else {
spdlog::info("Fullscreen: OFF");
glfwSetWindowMonitor(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.conf["windowSize"]["w"] = winWidth;
core::configManager.conf["windowSize"]["h"] = winHeight;
core::configManager.release(true);
}
if (winWidth > 0 && winHeight > 0) {
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(ImVec2(_winWidth, _winHeight));
drawWindow();
}
// Rendering
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(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);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapInterval(1); // Enable vsync
glfwSwapBuffers(window);
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}

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

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

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

@ -0,0 +1,33 @@
#include <credits.h>
namespace sdrpp_credits {
const char* contributors[] = {
"Ryzerth (Author)",
"aosync",
"Alexsey Shestacov",
"Benjamin Kyd",
"Cropinghigh",
"Howard0su",
"Martin Hauke",
"Raov",
"Szymon Zakrent",
"Tobias Mädel"
};
const char* libraries[] = {
"Dear ImGui (ocornut)",
"json (nlohmann)",
"portaudio (P.A. comm.)",
"SoapySDR (PothosWare)",
"spdlog (gabime)",
};
const char* patrons[] = {
"SignalsEverywhere",
"Lee Donaghy"
};
const int contributorCount = sizeof(contributors) / sizeof(char*);
const int libraryCount = sizeof(libraries) / sizeof(char*);
const int patronCount = sizeof(patrons) / sizeof(char*);
}

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

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

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

@ -0,0 +1,93 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class MonoToStereo : public generic_block<MonoToStereo> {
public:
MonoToStereo() {}
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);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<MonoToStereo>::ctrlMtx);
generic_block<MonoToStereo>::tempStop();
generic_block<MonoToStereo>::unregisterInput(_in);
_in = in;
generic_block<MonoToStereo>::registerInput(_in);
generic_block<MonoToStereo>::tempStart();
}
int run() {
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];
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
int count;
stream<float>* _in;
};
class StereoToMono : public generic_block<StereoToMono> {
public:
StereoToMono() {}
StereoToMono(stream<stereo_t>* in) { init(in); }
~StereoToMono() { generic_block<StereoToMono>::stop(); }
void init(stream<stereo_t>* in) {
_in = in;
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::registerOutput(&out);
}
void setInput(stream<stereo_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<StereoToMono>::ctrlMtx);
generic_block<StereoToMono>::tempStop();
generic_block<StereoToMono>::unregisterInput(_in);
_in = in;
generic_block<StereoToMono>::registerInput(_in);
generic_block<StereoToMono>::tempStart();
}
int run() {
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;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
int count;
stream<stereo_t>* _in;
};
}

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

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

View File

@ -1,34 +1,23 @@
#pragma once #pragma once
#include <condition_variable> #include <dsp/block.h>
#include <algorithm>
#include <math.h>
#include <string.h> #include <string.h>
#define STREAM_BUF_SZ 1000000 #define RING_BUF_SZ 1000000
namespace dsp { namespace dsp {
template <class T> template <class T>
class stream { class RingBuffer {
public: public:
stream() { RingBuffer() {
} }
stream(int maxLatency) { RingBuffer(int maxLatency) { init(maxLatency); }
size = STREAM_BUF_SZ;
_buffer = new T[size]; ~RingBuffer() { delete _buffer; }
_stopReader = false;
_stopWriter = false;
this->maxLatency = maxLatency;
writec = 0;
readc = 0;
readable = 0;
writable = size;
memset(_buffer, 0, size * sizeof(T));
}
void init(int maxLatency) { void init(int maxLatency) {
size = STREAM_BUF_SZ; size = RING_BUF_SZ;
_buffer = new T[size]; _buffer = new T[size];
_stopReader = false; _stopReader = false;
_stopWriter = false; _stopWriter = false;

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

@ -0,0 +1,185 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class ComplexToStereo : public generic_block<ComplexToStereo> {
public:
ComplexToStereo() {}
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);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToStereo>::ctrlMtx);
generic_block<ComplexToStereo>::tempStop();
generic_block<ComplexToStereo>::unregisterInput(_in);
_in = in;
generic_block<ComplexToStereo>::registerInput(_in);
generic_block<ComplexToStereo>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<stereo_t> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class ComplexToReal : public generic_block<ComplexToReal> {
public:
ComplexToReal() {}
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);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToReal>::ctrlMtx);
generic_block<ComplexToReal>::tempStop();
generic_block<ComplexToReal>::unregisterInput(_in);
_in = in;
generic_block<ComplexToReal>::registerInput(_in);
generic_block<ComplexToReal>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_real_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class ComplexToImag : public generic_block<ComplexToImag> {
public:
ComplexToImag() {}
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);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<ComplexToImag>::ctrlMtx);
generic_block<ComplexToImag>::tempStop();
generic_block<ComplexToImag>::unregisterInput(_in);
_in = in;
generic_block<ComplexToImag>::registerInput(_in);
generic_block<ComplexToImag>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
volk_32fc_deinterleave_imag_32f(out.writeBuf, (lv_32fc_t*)_in->readBuf, count);
_in->flush();
if(!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class RealToComplex : public generic_block<RealToComplex> {
public:
RealToComplex() {}
RealToComplex(stream<float>* in) { init(in); }
~RealToComplex() {
delete[] nullBuffer;
generic_block<RealToComplex>::stop();
}
void init(stream<float>* in) {
_in = in;
nullBuffer = new float[STREAM_BUFFER_SIZE];
memset(nullBuffer, 0, STREAM_BUFFER_SIZE * sizeof(float));
generic_block<RealToComplex>::registerInput(_in);
generic_block<RealToComplex>::registerOutput(&out);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<RealToComplex>::ctrlMtx);
generic_block<RealToComplex>::tempStop();
generic_block<RealToComplex>::unregisterInput(_in);
_in = in;
generic_block<RealToComplex>::registerInput(_in);
generic_block<RealToComplex>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
volk_32f_x2_interleave_32fc((lv_32fc_t*)out.writeBuf, _in->readBuf, nullBuffer, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
float avg;
int count;
float* nullBuffer;
stream<float>* _in;
};
}

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

@ -0,0 +1,279 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#define FAST_ATAN2_COEF1 FL_M_PI / 4.0f
#define FAST_ATAN2_COEF2 3.0f * FAST_ATAN2_COEF1
inline float fast_arctan2(float y, float x) {
float abs_y = fabsf(y);
float r, angle;
if (x == 0.0f && y == 0.0f) { return 0.0f; }
if (x>=0.0f) {
r = (x - abs_y) / (x + abs_y);
angle = FAST_ATAN2_COEF1 - FAST_ATAN2_COEF1 * r;
}
else {
r = (x + abs_y) / (abs_y - x);
angle = FAST_ATAN2_COEF2 - FAST_ATAN2_COEF1 * r;
}
if (y < 0.0f) {
return -angle;
}
return angle;
}
namespace dsp {
class FMDemod : public generic_block<FMDemod> {
public:
FMDemod() {}
FMDemod(stream<complex_t>* in, float sampleRate, float deviation) { init(in, sampleRate, deviation); }
~FMDemod() { generic_block<FMDemod>::stop(); }
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);
}
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);
_in = in;
generic_block<FMDemod>::registerInput(_in);
generic_block<FMDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
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() {
return _sampleRate;
}
void setDeviation(float deviation) {
std::lock_guard<std::mutex> lck(generic_block<FMDemod>::ctrlMtx);
generic_block<FMDemod>::tempStop();
_deviation = deviation;
phasorSpeed = (2 * FL_M_PI) / (_sampleRate / _deviation);
generic_block<FMDemod>::tempStart();
}
float getDeviation() {
return _deviation;
}
int run() {
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);
diff = currentPhase - phase;
if (diff > 3.1415926535f) { diff -= 2 * 3.1415926535f; }
else if (diff <= -3.1415926535f) { diff += 2 * 3.1415926535f; }
out.writeBuf[i] = diff / phasorSpeed;
phase = currentPhase;
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
int count;
float phase, phasorSpeed, _sampleRate, _deviation;
stream<complex_t>* _in;
};
class AMDemod : public generic_block<AMDemod> {
public:
AMDemod() {}
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);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<AMDemod>::ctrlMtx);
generic_block<AMDemod>::tempStop();
generic_block<AMDemod>::unregisterInput(_in);
_in = in;
generic_block<AMDemod>::registerInput(_in);
generic_block<AMDemod>::tempStart();
}
int run() {
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;
}
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
float avg;
int count;
stream<complex_t>* _in;
};
class SSBDemod : public generic_block<SSBDemod> {
public:
SSBDemod() {}
SSBDemod(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) { init(in, sampleRate, bandWidth, mode); }
~SSBDemod() {
generic_block<SSBDemod>::stop();
delete[] buffer;
}
enum {
MODE_USB,
MODE_LSB,
MODE_DSB
};
void init(stream<complex_t>* in, float sampleRate, float bandWidth, int mode) {
_in = in;
_sampleRate = sampleRate;
_bandWidth = bandWidth;
_mode = mode;
phase = lv_cmake(1.0f, 0.0f);
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
buffer = new lv_32fc_t[STREAM_BUFFER_SIZE];
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<SSBDemod>::ctrlMtx);
generic_block<SSBDemod>::tempStop();
generic_block<SSBDemod>::unregisterInput(_in);
_in = in;
generic_block<SSBDemod>::registerInput(_in);
generic_block<SSBDemod>::tempStart();
}
void setSampleRate(float sampleRate) {
// No need to restart
_sampleRate = sampleRate;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setBandWidth(float bandWidth) {
// No need to restart
_bandWidth = bandWidth;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
void setMode(int mode) {
_mode = mode;
switch (_mode) {
case MODE_USB:
phaseDelta = lv_cmake(std::cos((_bandWidth / _sampleRate) * FL_M_PI), std::sin((_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_LSB:
phaseDelta = lv_cmake(std::cos(-(_bandWidth / _sampleRate) * FL_M_PI), std::sin(-(_bandWidth / _sampleRate) * FL_M_PI));
break;
case MODE_DSB:
phaseDelta = lv_cmake(1.0f, 0.0f);
break;
}
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
volk_32fc_s32fc_x2_rotator_32fc(buffer, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
volk_32fc_deinterleave_real_32f(out.writeBuf, buffer, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
int count;
int _mode;
float _sampleRate, _bandWidth;
stream<complex_t>* _in;
lv_32fc_t* buffer;
lv_32fc_t phase;
lv_32fc_t phaseDelta;
};
}

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

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

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

@ -0,0 +1,105 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
namespace dsp {
template <class T>
class Add : public generic_block<Add<T>> {
public:
Add() {}
Add(stream<T>* a, stream<T>* b) { init(a, b); }
~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);
}
int run() {
a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if constexpr (std::is_same_v<T, complex_t> || std::is_same_v<T, stereo_t>) {
volk_32fc_x2_add_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
else {
volk_32f_x2_add_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
_a->flush();
_b->flush();
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<T> out;
private:
int a_count, b_count;
stream<T>* _a;
stream<T>* _b;
};
template <class T>
class Multiply : public generic_block<Multiply<T>> {
public:
Multiply() {}
Multiply(stream<T>* a, stream<T>* b) { init(a, b); }
~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);
}
int run() {
a_count = _a->read();
if (a_count < 0) { return -1; }
b_count = _b->read();
if (b_count < 0) { return -1; }
if (a_count != b_count) {
_a->flush();
_b->flush();
return 0;
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_x2_multiply_32fc(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
else {
volk_32f_x2_multiply_32f(out.writeBuf, _a->readBuf, _b->readBuf, a_count);
}
_a->flush();
_b->flush();
if (!out.swap(a_count)) { return -1; }
return a_count;
}
stream<T> out;
private:
int a_count, b_count;
stream<T>* _a;
stream<T>* _b;
};
}

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

@ -0,0 +1,99 @@
#pragma once
#include <dsp/block.h>
#include <fftw3.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <dsp/types.h>
#include <string.h>
namespace dsp {
class VolumeMeasure : public generic_block<VolumeMeasure> {
public:
VolumeMeasure() {}
VolumeMeasure(stream<stereo_t>* in) { init(in); }
~VolumeMeasure() {
generic_block<VolumeMeasure>::stop();
delete[] leftBuf;
delete[] rightBuf;
}
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);
}
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);
_in = in;
generic_block<VolumeMeasure>::registerInput(_in);
generic_block<VolumeMeasure>::tempStart();
}
int run() {
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
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]; }
}
// 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);
return count;
}
stream<stereo_t> out;
stereo_t peak = {0, 0};
stereo_t average = {0, 0};
private:
int count;
float peakFall = 10.0f; // dB/S
float avgFilt = 0.2f; // IIR filter coef
float sampleRate = 48000;
stream<stereo_t>* _in;
float* leftBuf;
float* rightBuf;
};
}

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

@ -0,0 +1,300 @@
#pragma once
#include <dsp/block.h>
#include <volk/volk.h>
#include <spdlog/spdlog.h>
#include <string.h>
namespace dsp {
template <class T>
class FrequencyXlator : public generic_block<FrequencyXlator<T>> {
public:
FrequencyXlator() {}
FrequencyXlator(stream<complex_t>* in, float sampleRate, float freq) { init(in, sampleRate, freq); }
~FrequencyXlator() {
generic_block<FrequencyXlator<T>>::stop();
}
void init(stream<complex_t>* in, float sampleRate, float freq) {
_in = in;
_sampleRate = sampleRate;
_freq = freq;
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::registerOutput(&out);
}
void setInputSize(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<FrequencyXlator<T>>::ctrlMtx);
generic_block<FrequencyXlator<T>>::tempStop();
generic_block<FrequencyXlator<T>>::unregisterInput(_in);
_in = in;
generic_block<FrequencyXlator<T>>::registerInput(_in);
generic_block<FrequencyXlator<T>>::tempStart();
}
void setSampleRate(float sampleRate) {
// No need to restart
_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() {
return _sampleRate;
}
void setFrequency(float freq) {
// No need to restart
_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() {
return _freq;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
// TODO: Do float xlation
if constexpr (std::is_same_v<T, float>) {
spdlog::error("XLATOR NOT IMPLEMENTED FOR FLOAT");
}
if constexpr (std::is_same_v<T, complex_t>) {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
int count;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
stream<complex_t>* _in;
};
class AGC : public generic_block<AGC> {
public:
AGC() {}
AGC(stream<float>* in, float fallRate, float sampleRate) { init(in, fallRate, sampleRate); }
~AGC() { generic_block<AGC>::stop(); }
void init(stream<float>* in, float fallRate, float sampleRate) {
_in = in;
_sampleRate = sampleRate;
_fallRate = fallRate;
_CorrectedFallRate = _fallRate / _sampleRate;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::registerOutput(&out);
}
void setInput(stream<float>* in) {
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
generic_block<AGC>::tempStop();
generic_block<AGC>::unregisterInput(_in);
_in = in;
generic_block<AGC>::registerInput(_in);
generic_block<AGC>::tempStart();
}
void setSampleRate(float sampleRate) {
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
_sampleRate = sampleRate;
_CorrectedFallRate = _fallRate / _sampleRate;
}
void setFallRate(float fallRate) {
std::lock_guard<std::mutex> lck(generic_block<AGC>::ctrlMtx);
_fallRate = fallRate;
_CorrectedFallRate = _fallRate / _sampleRate;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
level = pow(10, ((10.0f * log10f(level)) - (_CorrectedFallRate * count)) / 10.0f);
for (int i = 0; i < count; i++) {
if (_in->readBuf[i] > level) { level = _in->readBuf[i]; }
}
volk_32f_s32f_multiply_32f(out.writeBuf, _in->readBuf, 1.0f / level, count);
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<float> out;
private:
int count;
float level = 0.0f;
float _fallRate;
float _CorrectedFallRate;
float _sampleRate;
stream<float>* _in;
};
template <class T>
class Volume : public generic_block<Volume<T>> {
public:
Volume() {}
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);
}
void setInputSize(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Volume<T>>::ctrlMtx);
generic_block<Volume<T>>::tempStop();
generic_block<Volume<T>>::unregisterInput(_in);
_in = in;
generic_block<Volume<T>>::registerInput(_in);
generic_block<Volume<T>>::tempStart();
}
void setVolume(float volume) {
_volume = volume;
level = powf(_volume, 2);
}
float getVolume() {
return _volume;
}
void setMuted(bool muted) {
_muted = muted;
}
bool getMuted() {
return _muted;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (_muted) {
if constexpr (std::is_same_v<T, stereo_t>) {
memset(out.writeBuf, 0, sizeof(stereo_t) * count);
}
else {
memset(out.writeBuf, 0, sizeof(float) * count);
}
}
else {
if constexpr (std::is_same_v<T, stereo_t>) {
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count * 2);
}
else {
volk_32f_s32f_multiply_32f((float*)out.writeBuf, (float*)_in->readBuf, level, count);
}
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<T> out;
private:
int count;
float level = 1.0f;
float _volume = 1.0f;
bool _muted = false;
stream<T>* _in;
};
class Squelch : public generic_block<Squelch> {
public:
Squelch() {}
Squelch(stream<complex_t>* in, float level) { init(in, level); }
~Squelch() {
generic_block<Squelch>::stop();
delete[] normBuffer;
}
void init(stream<complex_t>* in, float level) {
_in = in;
_level = level;
normBuffer = new float[STREAM_BUFFER_SIZE];
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::registerOutput(&out);
}
void setInput(stream<complex_t>* in) {
std::lock_guard<std::mutex> lck(generic_block<Squelch>::ctrlMtx);
generic_block<Squelch>::tempStop();
generic_block<Squelch>::unregisterInput(_in);
_in = in;
generic_block<Squelch>::registerInput(_in);
generic_block<Squelch>::tempStart();
}
void setLevel(float level) {
_level = level;
}
float getLevel() {
return _level;
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
float sum = 0.0f;
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)_in->readBuf, count);
volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count;
if (10.0f * log10f(sum) >= _level) {
memcpy(out.writeBuf, _in->readBuf, count * sizeof(complex_t));
}
else {
memset(out.writeBuf, 0, count * sizeof(complex_t));
}
_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
stream<complex_t> out;
private:
int count;
float* normBuffer;
float _level = -50.0f;
stream<complex_t>* _in;
};
}

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

@ -0,0 +1,267 @@
#pragma once
#include <dsp/block.h>
#include <dsp/window.h>
#include <numeric>
#include <string.h>
namespace dsp {
template <class T>
class PolyphaseResampler : public generic_block<PolyphaseResampler<T>> {
public:
PolyphaseResampler() {}
PolyphaseResampler(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) { init(in, window, inSampleRate, outSampleRate); }
~PolyphaseResampler() {
generic_block<PolyphaseResampler<T>>::stop();
volk_free(buffer);
volk_free(taps);
freeTapPhases();
}
void init(stream<T>* in, dsp::filter_window::generic_window* window, float inSampleRate, float outSampleRate) {
_in = in;
_window = window;
_inSampleRate = inSampleRate;
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
tapCount = _window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
_window->createTaps(taps, tapCount, _interp);
buildTapPhases();
buffer = (T*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(T) * 2, volk_get_alignment());
memset(buffer, 0, STREAM_BUFFER_SIZE * sizeof(T) * 2);
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::registerOutput(&out);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
generic_block<PolyphaseResampler<T>>::unregisterInput(_in);
_in = in;
generic_block<PolyphaseResampler<T>>::registerInput(_in);
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setInSampleRate(float inSampleRate) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_inSampleRate = inSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
buildTapPhases();
generic_block<PolyphaseResampler<T>>::tempStart();
}
void setOutSampleRate(float outSampleRate) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_outSampleRate = outSampleRate;
int _gcd = std::gcd((int)_inSampleRate, (int)_outSampleRate);
_interp = _outSampleRate / _gcd;
_decim = _inSampleRate / _gcd;
buildTapPhases();
generic_block<PolyphaseResampler<T>>::tempStart();
}
int getInterpolation() {
return _interp;
}
int getDecimation() {
return _decim;
}
void updateWindow(dsp::filter_window::generic_window* window) {
std::lock_guard<std::mutex> lck(generic_block<PolyphaseResampler<T>>::ctrlMtx);
generic_block<PolyphaseResampler<T>>::tempStop();
_window = window;
volk_free(taps);
tapCount = window->getTapCount();
taps = (float*)volk_malloc(tapCount * sizeof(float), volk_get_alignment());
window->createTaps(taps, tapCount, _interp);
buildTapPhases();
generic_block<PolyphaseResampler<T>>::tempStart();
}
int calcOutSize(int in) {
return (in * _interp) / _decim;
}
virtual int run() override {
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;
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++;
}
}
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 (!out.swap(outCount)) { return -1; }
memmove(buffer, &buffer[count], tapsPerPhase * sizeof(T));
return count;
}
stream<T> out;
private:
void buildTapPhases(){
if(!taps){
return;
}
if(!tapPhases.empty()){
freeTapPhases();
}
int phases = _interp;
tapsPerPhase = (tapCount+phases-1)/phases; //Integer division ceiling
bufStart = &buffer[tapsPerPhase];
for(int i = 0; i < phases; i++){
tapPhases.push_back((float*)volk_malloc(tapsPerPhase * sizeof(float), volk_get_alignment()));
}
int currentTap = 0;
for(int tap = 0; tap < tapsPerPhase; tap++) {
for (int phase = 0; phase < phases; phase++) {
if(currentTap < tapCount) {
tapPhases[(_interp - 1) - phase][tap] = taps[currentTap++];
}
else{
tapPhases[(_interp - 1) - phase][tap] = 0;
}
}
}
}
void freeTapPhases(){
for(auto & tapPhase : tapPhases){
volk_free(tapPhase);
}
tapPhases.clear();
}
int count;
stream<T>* _in;
dsp::filter_window::generic_window* _window;
T* bufStart;
T* buffer;
int tapCount;
int _interp, _decim;
float _inSampleRate, _outSampleRate;
float* taps;
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;
};
}

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

@ -0,0 +1,189 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
#include <string.h>
#include <numeric>
#include <spdlog/spdlog.h>
namespace dsp {
template <class T>
class Splitter : public generic_block<Splitter<T>> {
public:
Splitter() {}
Splitter(stream<T>* in) { init(in); }
~Splitter() { generic_block<Splitter>::stop(); }
void init(stream<T>* in) {
_in = in;
generic_block<Splitter>::registerInput(_in);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterInput(_in);
_in = in;
generic_block<Splitter>::registerInput(_in);
generic_block<Splitter>::tempStart();
}
void bindStream(stream<T>* stream) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
out.push_back(stream);
generic_block<Splitter>::registerOutput(stream);
generic_block<Splitter>::tempStart();
}
void unbindStream(stream<T>* stream) {
std::lock_guard<std::mutex> lck(generic_block<Splitter>::ctrlMtx);
generic_block<Splitter>::tempStop();
generic_block<Splitter>::unregisterOutput(stream);
out.erase(std::remove(out.begin(), out.end(), stream), out.end());
generic_block<Splitter>::tempStart();
}
private:
int run() {
// TODO: If too slow, buffering might be necessary
int count = _in->read();
if (count < 0) { return -1; }
for (const auto& stream : out) {
memcpy(stream->writeBuf, _in->readBuf, count * sizeof(T));
if (!stream->swap(count)) { return -1; }
}
_in->flush();
return count;
}
stream<T>* _in;
std::vector<stream<T>*> out;
};
// NOTE: I'm not proud of this, it's BAD and just taken from the previous DSP, but it works...
template <class T>
class Reshaper : public generic_block<Reshaper<T>> {
public:
Reshaper() {}
Reshaper(stream<T>* in, int keep, int skip) { init(in, keep, skip); }
~Reshaper() { generic_block<Reshaper<T>>::stop(); }
void init(stream<T>* in, int keep, int skip) {
_in = in;
_keep = keep;
_skip = skip;
ringBuf.init(keep * 2);
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::registerOutput(&out);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<Reshaper<T>>::ctrlMtx);
generic_block<Reshaper<T>>::tempStop();
generic_block<Reshaper<T>>::unregisterInput(_in);
_in = in;
generic_block<Reshaper<T>>::registerInput(_in);
generic_block<Reshaper<T>>::tempStart();
}
void setKeep(int keep) {
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) {
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();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
ringBuf.write(_in->readBuf, count);
_in->flush();
return count;
}
stream<T> out;
private:
void doStart() {
workThread = std::thread(&Reshaper<T>::loop, this);
bufferWorkerThread = std::thread(&Reshaper<T>::bufferWorker, this);
}
void loop() {
while (run() >= 0);
}
void doStop() {
_in->stopReader();
ringBuf.stopReader();
out.stopWriter();
ringBuf.stopWriter();
if (workThread.joinable()) {
workThread.join();
}
if (bufferWorkerThread.joinable()) {
bufferWorkerThread.join();
}
_in->clearReadStop();
ringBuf.clearReadStop();
out.clearWriteStop();
ringBuf.clearWriteStop();
}
void bufferWorker() {
complex_t* buf = new complex_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 delayCount = (-_skip);
complex_t* start = &buf[std::max<int>(-_skip, 0)];
complex_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 (ringBuf.readAndSkip(start, readCount, skip) < 0) { break; };
memcpy(out.writeBuf, buf, _keep * sizeof(complex_t));
if (!out.swap(_keep)) { break; }
}
delete[] buf;
}
stream<T>* _in;
int _outBlockSize;
RingBuffer<T> ringBuf;
std::thread bufferWorkerThread;
std::thread workThread;
int _keep, _skip;
};
}

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

@ -0,0 +1,140 @@
#pragma once
#include <dsp/block.h>
#include <dsp/buffer.h>
namespace dsp {
template <class T>
class HandlerSink : public generic_block<HandlerSink<T>> {
public:
HandlerSink() {}
HandlerSink(stream<T>* in, void (*handler)(T* data, int count, void* ctx), void* ctx) { init(in, handler, ctx); }
~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);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
generic_block<HandlerSink<T>>::unregisterInput(_in);
_in = in;
generic_block<HandlerSink<T>>::registerInput(_in);
generic_block<HandlerSink<T>>::tempStart();
}
void setHandler(void (*handler)(T* data, int count, void* ctx), void* ctx) {
std::lock_guard<std::mutex> lck(generic_block<HandlerSink<T>>::ctrlMtx);
generic_block<HandlerSink<T>>::tempStop();
_handler = handler;
_ctx = ctx;
generic_block<HandlerSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
_handler(_in->readBuf, count, _ctx);
_in->flush();
return count;
}
private:
int count;
stream<T>* _in;
void (*_handler)(T* data, int count, void* ctx);
void* _ctx;
};
template <class T>
class RingBufferSink : public generic_block<RingBufferSink<T>> {
public:
RingBufferSink() {}
RingBufferSink(stream<T>* in) { init(in); }
~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);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<RingBufferSink<T>>::ctrlMtx);
generic_block<RingBufferSink<T>>::tempStop();
generic_block<RingBufferSink<T>>::unregisterInput(_in);
_in = in;
generic_block<RingBufferSink<T>>::registerInput(_in);
generic_block<RingBufferSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
if (data.write(_in->readBuf, count) < 0) { return -1; }
_in->flush();
return count;
}
RingBuffer<T> data;
private:
void doStop() {
_in->stopReader();
data.stopWriter();
if (generic_block<RingBufferSink<T>>::workerThread.joinable()) {
generic_block<RingBufferSink<T>>::workerThread.join();
}
_in->clearReadStop();
data.clearWriteStop();
}
int count;
stream<T>* _in;
};
template <class T>
class NullSink : public generic_block<NullSink<T>> {
public:
NullSink() {}
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);
}
void setInput(stream<T>* in) {
std::lock_guard<std::mutex> lck(generic_block<NullSink<T>>::ctrlMtx);
generic_block<NullSink<T>>::tempStop();
generic_block<NullSink<T>>::unregisterInput(_in);
_in = in;
generic_block<NullSink<T>>::registerInput(_in);
generic_block<NullSink<T>>::tempStart();
}
int run() {
count = _in->read();
if (count < 0) { return -1; }
_in->flush();
return count;
}
private:
int count;
stream<T>* _in;
};
}

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

@ -0,0 +1,74 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
class SineSource : public generic_block<SineSource> {
public:
SineSource() {}
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;
_freq = freq;
zeroPhase = (lv_32fc_t*)volk_malloc(STREAM_BUFFER_SIZE * sizeof(lv_32fc_t), volk_get_alignment());
for (int i = 0; i < STREAM_BUFFER_SIZE; i++) {
zeroPhase[i] = lv_cmake(1.0f, 0.0f);
}
phase = lv_cmake(1.0f, 0.0f);
phaseDelta = lv_cmake(std::cos((_freq / _sampleRate) * 2.0f * FL_M_PI), std::sin((_freq / _sampleRate) * 2.0f * FL_M_PI));
generic_block<SineSource>::registerOutput(&out);
}
void setBlockSize(int blockSize) {
std::lock_guard<std::mutex> lck(generic_block<SineSource>::ctrlMtx);
generic_block<SineSource>::tempStop();
_blockSize = blockSize;
generic_block<SineSource>::tempStart();
}
int getBlockSize() {
return _blockSize;
}
void setSampleRate(float sampleRate) {
// No need to restart
_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() {
return _sampleRate;
}
void setFrequency(float freq) {
// No need to restart
_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() {
return _freq;
}
int run() {
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out.writeBuf, zeroPhase, phaseDelta, &phase, _blockSize);
if(!out.swap(_blockSize)) { return -1; }
return _blockSize;
}
stream<complex_t> out;
private:
int _blockSize;
float _sampleRate;
float _freq;
lv_32fc_t phaseDelta;
lv_32fc_t phase;
lv_32fc_t* zeroPhase;
};
}

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

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

View File

@ -5,4 +5,9 @@ namespace dsp {
float q; float q;
float i; float i;
}; };
};
struct stereo_t {
float l;
float r;
};
}

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

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

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

@ -0,0 +1,76 @@
#pragma once
#include <dsp/block.h>
namespace dsp {
namespace filter_window {
class generic_window {
public:
virtual int getTapCount() { return -1; }
virtual void createTaps(float* taps, int tapCount, float factor = 1.0f) {}
};
class BlackmanWindow : public filter_window::generic_window {
public:
BlackmanWindow() {}
BlackmanWindow(float cutoff, float transWidth, float sampleRate) { init(cutoff, transWidth, sampleRate); }
void init(float cutoff, float transWidth, float sampleRate) {
_cutoff = cutoff;
_transWidth = transWidth;
_sampleRate = sampleRate;
}
void setSampleRate(float sampleRate) {
_sampleRate = sampleRate;
}
void setCutoff(float cutoff) {
_cutoff = cutoff;
}
void setTransWidth(float transWidth) {
_transWidth = transWidth;
}
int getTapCount() {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
int _M = 4.0f / (_transWidth / _sampleRate);
if (_M < 4) {
_M = 4;
}
if (_M % 2 == 0) { _M++; }
return _M;
}
void createTaps(float* taps, int tapCount, float factor = 1.0f) {
float fc = _cutoff / _sampleRate;
if (fc > 1.0f) {
fc = 1.0f;
}
float tc = tapCount;
float sum = 0.0f;
float val;
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
sum += val;
}
for (int i = 0; i < tapCount; i++) {
taps[i] *= factor;
taps[i] /= sum;
}
}
private:
float _cutoff, _transWidth, _sampleRate;
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,185 @@
/*
* 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

@ -0,0 +1,29 @@
#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 */

99755
core/src/duktape/duktape.c Normal file

File diff suppressed because it is too large Load Diff

1450
core/src/duktape/duktape.h Normal file

File diff suppressed because it is too large Load Diff

43
core/src/event.h Normal file
View File

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

View File

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

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

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

View File

@ -0,0 +1,61 @@
#include <gui/dialogs/credits.h>
#include <imgui.h>
#include <gui/icons.h>
#include <gui/style.h>
#include <config.h>
#include <credits.h>
#include <version.h>
namespace credits {
ImFont* bigFont;
void init() {
}
void show() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(20.0f, 20.0f));
ImGui::OpenPopup("Credits");
ImGui::BeginPopupModal("Credits", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
ImGui::PushFont(style::hugeFont);
ImGui::Text("SDR++ ");
ImGui::PopFont();
ImGui::SameLine();
ImGui::Image(icons::LOGO, ImVec2(128, 128));
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("This software is brought to you by\n\n");
ImGui::Columns(3, "CreditColumns", true);
ImGui::Text("Contributors");
for (int i = 0; i < sdrpp_credits::contributorCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::contributors[i]);
}
ImGui::NextColumn();
ImGui::Text("Libraries");
for (int i = 0; i < sdrpp_credits::libraryCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::libraries[i]);
}
ImGui::NextColumn();
ImGui::Text("Patrons");
for (int i = 0; i < sdrpp_credits::patronCount; i++) {
ImGui::BulletText("%s", sdrpp_credits::patrons[i]);
}
ImGui::Columns(1, "CreditColumnsEnd", true);
ImGui::Spacing();
ImGui::Spacing();
ImGui::Spacing();
ImGui::Text("SDR++ v" VERSION_STR);
ImGui::EndPopup();
ImGui::PopStyleVar(1);
}
}

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

@ -0,0 +1,642 @@
#include <gui/main_window.h>
#include <gui/gui.h>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <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>
#include <core.h>
#include <gui/menus/source.h>
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/sink.h>
#include <gui/menus/scripting.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>
// 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() {
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>>();
std::string modulesDir = core::configManager.conf["modulesDirectory"];
std::string resourcesDir = core::configManager.conf["resourcesDirectory"];
core::configManager.release();
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::freqSelect.init();
// Set default values for waterfall in case no source init's it
gui::waterfall.setBandwidth(8000000);
gui::waterfall.setViewBandwidth(8000000);
fft_in = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
fft_out = (fftwf_complex*) fftwf_malloc(sizeof(fftwf_complex) * fftSize);
p = 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.start();
spdlog::info("Loading modules");
// Load modules from /module directory
if (std::filesystem::is_directory(modulesDir)) {
for (const auto & file : std::filesystem::directory_iterator(modulesDir)) {
std::string path = file.path().generic_string();
if (file.path().extension().generic_string() != SDRPP_MOD_EXTENTSION) {
continue;
}
if (!file.is_regular_file()) { continue; }
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
}
else {
spdlog::warn("Module directory {0} does not exist, not loading modules from directory", modulesDir);
}
// Read module config
core::configManager.aquire();
std::vector<std::string> modules = core::configManager.conf["modules"];
std::map<std::string, std::string> modList = core::configManager.conf["moduleInstances"];
core::configManager.release();
// Load additional modules specified through config
for (auto const& path : modules) {
spdlog::info("Loading {0}", path);
LoadingScreen::show("Loading " + path);
core::moduleManager.loadModule(path);
}
// Create module instances
for (auto const& [name, module] : modList) {
spdlog::info("Initializing {0} ({1})", name, module);
LoadingScreen::show("Initializing " + name + " (" + module + ")");
core::moduleManager.createInstance(name, module);
}
// Load color maps
LoadingScreen::show("Loading color maps");
spdlog::info("Loading color maps");
if (std::filesystem::is_directory(resourcesDir + "/colormaps")) {
for (const auto & file : std::filesystem::directory_iterator(resourcesDir + "/colormaps")) {
std::string path = file.path().generic_string();
LoadingScreen::show("Loading " + path);
spdlog::info("Loading {0}", path);
if (file.path().extension().generic_string() != ".json") {
continue;
}
if (!file.is_regular_file()) { continue; }
colormaps::loadMap(path);
}
}
else {
spdlog::warn("Color map directory {0} does not exist, not loading modules from directory", modulesDir);
}
gui::waterfall.updatePalletteFromArray(colormaps::maps["Turbo"].map, colormaps::maps["Turbo"].entryCount);
sourecmenu::init();
sinkmenu::init();
scriptingmenu::init();
bandplanmenu::init();
displaymenu::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();
fftMin = core::configManager.conf["min"];
fftMax = core::configManager.conf["max"];
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMax(fftMax);
double frequency = core::configManager.conf["frequency"];
gui::freqSelect.setFrequency(frequency);
gui::freqSelect.frequencyChanged = false;
sigpath::sourceManager.tune(frequency);
gui::waterfall.setCenterFrequency(frequency);
bw = gui::waterfall.getBandwidth();
gui::waterfall.vfoFreqChanged = false;
gui::waterfall.centerFreqMoved = false;
gui::waterfall.selectFirstVFO();
menuWidth = core::configManager.conf["menuWidth"];
newWidth = menuWidth;
fftHeight = core::configManager.conf["fftHeight"];
gui::waterfall.setFFTHeight(fftHeight);
centerTuning = core::configManager.conf["centerTuning"];
core::configManager.release();
}
void setVFO(double freq) {
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;
}
ImGui::WaterfallVFO* vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
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);
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() {
ImGui::Begin("Main", NULL, WINDOW_FLAGS);
ImGui::WaterfallVFO* vfo = NULL;
if (gui::waterfall.selectedVFO != "") {
vfo = gui::waterfall.vfos[gui::waterfall.selectedVFO];
}
if (vfo != NULL) {
if (vfo->centerOffsetChanged) {
if (centerTuning) {
setVFO(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.release(true);
}
}
sigpath::vfoManager.updateFromWaterfall(&gui::waterfall);
if (gui::waterfall.selectedVFOChanged && vfo != NULL) {
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);
}
if (gui::freqSelect.frequencyChanged) {
gui::freqSelect.frequencyChanged = false;
setVFO(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.release(true);
}
if (gui::waterfall.centerFreqMoved) {
gui::waterfall.centerFreqMoved = false;
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
if (vfo != NULL) {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency() + vfo->generalOffset);
}
else {
gui::freqSelect.setFrequency(gui::waterfall.getCenterFrequency());
}
core::configManager.aquire();
core::configManager.conf["frequency"] = gui::freqSelect.frequency;
core::configManager.release(true);
}
int _fftHeight = gui::waterfall.getFFTHeight();
if (fftHeight != _fftHeight) {
fftHeight = _fftHeight;
core::configManager.aquire();
core::configManager.conf["fftHeight"] = fftHeight;
core::configManager.release(true);
}
ImVec2 vMin = ImGui::GetWindowContentRegionMin();
ImVec2 vMax = ImGui::GetWindowContentRegionMax();
int width = vMax.x - vMin.x;
int height = vMax.y - vMin.y;
// To Bar
ImGui::PushID(ImGui::GetID("sdrpp_menu_btn"));
if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
showMenu = !showMenu;
}
ImGui::PopID();
ImGui::SameLine();
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();
playing = false;
}
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)) {
sigpath::sourceManager.start();
// TODO: tune in module instead
sigpath::sourceManager.tune(gui::waterfall.getCenterFrequency());
playing = true;
}
ImGui::PopID();
}
ImGui::SameLine();
//ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8);
sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true);
ImGui::SameLine();
gui::freqSelect.draw();
ImGui::SameLine();
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9);
if (centerTuning) {
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;
core::configManager.release(true);
}
ImGui::PopID();
}
else { // TODO: Might need to check if there even is a device
ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn"));
if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) {
centerTuning = true;
setVFO(gui::freqSelect.frequency);
core::configManager.aquire();
core::configManager.conf["centerTuning"] = centerTuning;
core::configManager.release(true);
}
ImGui::PopID();
}
ImGui::SameLine();
// Logo button
ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48);
ImGui::SetCursorPosY(10);
if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) {
showCredits = true;
}
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
showCredits = false;
}
if (ImGui::IsKeyPressed(GLFW_KEY_ESCAPE)) {
showCredits = false;
}
// Handle menu resize
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;
}
}
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
if (showMenu) {
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60);
ImGui::SetColumnWidth(2, 60);
ImGui::BeginChild("Left Column");
float menuColumnWidth = ImGui::GetContentRegionAvailWidth();
gui::menu.draw();
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::Spacing();
}
ImGui::EndChild();
}
else {
// When hiding the menu bar
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, 8);
ImGui::SetColumnWidth(1, winSize.x - 8 - 60);
ImGui::SetColumnWidth(2, 60);
}
// Right Column
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGui::NextColumn();
ImGui::PopStyleVar();
ImGui::BeginChild("Waterfall");
gui::waterfall.draw();
ImGui::EndChild();
ImGui::NextColumn();
ImGui::BeginChild("WaterfallControls");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0));
ImGui::Text("Zoom");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, gui::waterfall.getBandwidth(), 1000.0, "", (experimentalZoom ? 2.0 : 1.0))) {
gui::waterfall.setViewBandwidth(bw);
if (vfo != NULL) {
gui::waterfall.setViewOffset(vfo->centerOffset); // center vfo on screen
}
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0));
ImGui::Text("Max");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -100.0, "")) {
fftMax = std::max<float>(fftMax, fftMin + 10);
core::configManager.aquire();
core::configManager.conf["max"] = fftMax;
core::configManager.release(true);
}
ImGui::NewLine();
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0));
ImGui::Text("Min");
ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10);
if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -100.0, "")) {
fftMin = std::min<float>(fftMax - 10, fftMin);
core::configManager.aquire();
core::configManager.conf["min"] = fftMin;
core::configManager.release(true);
}
ImGui::EndChild();
gui::waterfall.setFFTMin(fftMin);
gui::waterfall.setFFTMax(fftMax);
gui::waterfall.setWaterfallMin(fftMin);
gui::waterfall.setWaterfallMax(fftMax);
ImGui::End();
if (showCredits) {
credits::show();
}
if (demoWindow) {
ImGui::ShowDemoWindow();
}
}
void setViewBandwidthSlider(float bandwidth) {
bw = bandwidth;
}
bool sdrIsRunning() {
return playing;
}

View File

@ -1,12 +1,9 @@
#pragma once #pragma once
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground #define WINDOW_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoBackground
void windowInit(); void windowInit();
void drawWindow(); void drawWindow();
void setViewBandwidthSlider(float bandwidth);
bool sdrIsRunning();

View File

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

View File

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

View File

@ -0,0 +1,59 @@
#include <gui/menus/display.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
#include <gui/colormaps.h>
#include <gui/gui.h>
namespace displaymenu {
bool showWaterfall;
int colorMapId = 0;
std::vector<std::string> colorMapNames;
std::string colorMapNamesTxt = "";
std::string colorMapAuthor = "";
void init() {
showWaterfall = core::configManager.conf["showWaterfall"];
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
std::string colormapName = core::configManager.conf["colorMap"];
if (colormaps::maps.find(colormapName) != colormaps::maps.end()) {
colormaps::Map map = colormaps::maps[colormapName];
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
}
for (auto const& [name, map] : colormaps::maps) {
colorMapNames.push_back(name);
colorMapNamesTxt += name;
colorMapNamesTxt += '\0';
if (name == colormapName) {
colorMapId = (colorMapNames.size() - 1);
colorMapAuthor = map.author;
}
}
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
if (ImGui::Checkbox("Show Waterfall", &showWaterfall)) {
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.aquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
}
if (colorMapNames.size() > 0) {
ImGui::Text("Color Map");
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##_sdrpp_color_map_sel", &colorMapId, colorMapNamesTxt.c_str())) {
colormaps::Map map = colormaps::maps[colorMapNames[colorMapId]];
gui::waterfall.updatePalletteFromArray(map.map, map.entryCount);
core::configManager.aquire();
core::configManager.conf["colorMap"] = colorMapNames[colorMapId];
core::configManager.release(true);
colorMapAuthor = map.author;
}
ImGui::Text("Color map Author: %s", colorMapAuthor.c_str());
}
}
}

View File

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

View File

@ -0,0 +1,22 @@
#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

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

View File

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

View File

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

View File

@ -0,0 +1,61 @@
#include <gui/menus/source.h>
#include <imgui.h>
#include <gui/gui.h>
#include <core.h>
#include <gui/main_window.h>
#include <gui/style.h>
#include <signal_path/signal_path.h>
namespace sourecmenu {
int sourceId = 0;
double freqOffset = 0.0;
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.release();
}
void draw(void* ctx) {
std::string items = "";
for (std::string name : sigpath::sourceManager.sourceNames) {
items += name;
items += '\0';
}
float itemWidth = ImGui::GetContentRegionAvailWidth();
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];
core::configManager.release(true);
}
if (sdrIsRunning()) { 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;
core::configManager.release(true);
}
}
}

View File

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

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

@ -0,0 +1,121 @@
#include <gui/style.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <config.h>
#include <options.h>
#include <spdlog/spdlog.h>
#include <filesystem>
namespace style {
ImFont* baseFont;
ImFont* bigFont;
ImFont* hugeFont;
bool setDefaultStyle(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));
}
void endDisabled() {
ImGui::PopItemFlag();
ImGui::PopStyleColor(3);
}
}

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

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

View File

@ -1,4 +1,9 @@
#include <bandplan.h> #include <gui/widgets/bandplan.h>
#include <fstream>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <sstream>
#include <iomanip>
namespace bandplan { namespace bandplan {
std::map<std::string, BandPlan_t> bandplans; std::map<std::string, BandPlan_t> bandplans;
@ -71,7 +76,7 @@ namespace bandplan {
void loadBandPlan(std::string path) { void loadBandPlan(std::string path) {
std::ifstream file(path.c_str()); std::ifstream file(path.c_str());
json data; json data;
data << file; file >> data;
file.close(); file.close();
BandPlan_t plan = data.get<BandPlan_t>(); BandPlan_t plan = data.get<BandPlan_t>();
@ -103,20 +108,7 @@ namespace bandplan {
} }
} }
void loadColorTable(std::string path) { void loadColorTable(json table) {
if (!std::filesystem::exists(path)) { colorTable = table.get<std::map<std::string, BandPlanColor_t>>();
spdlog::error("Band Plan Color Table file does not exist");
return;
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("Band Plan Color Table file isn't a file...");
return;
}
std::ifstream file(path.c_str());
json data;
data << file;
file.close();
colorTable = data.get<std::map<std::string, BandPlanColor_t>>();
} }
}; };

View File

@ -1,11 +1,7 @@
#pragma once #pragma once
#include <json.hpp> #include <json.hpp>
#include <fstream>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <sstream>
#include <iomanip>
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <stdint.h>
using nlohmann::json; using nlohmann::json;
@ -13,8 +9,8 @@ namespace bandplan {
struct Band_t { struct Band_t {
std::string name; std::string name;
std::string type; std::string type;
float start; double start;
float end; double end;
}; };
void to_json(json& j, const Band_t& b); void to_json(json& j, const Band_t& b);
@ -42,7 +38,7 @@ namespace bandplan {
void loadBandPlan(std::string path); void loadBandPlan(std::string path);
void loadFromDir(std::string path); void loadFromDir(std::string path);
void loadColorTable(std::string path); void loadColorTable(json table);
extern std::map<std::string, BandPlan_t> bandplans; extern std::map<std::string, BandPlan_t> bandplans;
extern std::vector<std::string> bandplanNames; extern std::vector<std::string> bandplanNames;

View File

@ -1,4 +1,11 @@
#include <frequency_select.h> #include <gui/widgets/frequency_select.h>
#include <config.h>
#include <gui/style.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <imgui/imgui_internal.h>
bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) { bool isInArea(ImVec2 val, ImVec2 min, ImVec2 max) {
return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y; return val.x >= min.x && val.x < max.x && val.y >= min.y && val.y < max.y;
@ -9,24 +16,27 @@ FrequencySelect::FrequencySelect() {
} }
void FrequencySelect::init() { void FrequencySelect::init() {
font = ImGui::GetIO().Fonts->AddFontFromFileTTF("res/fonts/Roboto-Medium.ttf", 42.0f);
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
digits[i] = 0; digits[i] = 0;
} }
} }
void FrequencySelect::onPosChange() { void FrequencySelect::onPosChange() {
int digitHeight = ImGui::CalcTextSize("0").y; ImVec2 digitSz = ImGui::CalcTextSize("0");
ImVec2 commaSz = ImGui::CalcTextSize(".");
int digitHeight = digitSz.y;
int digitWidth = digitSz.x;
int commaOffset = 0; int commaOffset = 0;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
digitTopMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y); digitTopMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y);
digitBottomMins[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y + (digitHeight / 2)); digitBottomMins[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y + (digitHeight / 2));
digitTopMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + (digitHeight / 2)); digitTopMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + (digitHeight / 2));
digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * 22) + commaOffset + 22, widgetPos.y + digitHeight); digitBottomMaxs[i] = ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + digitWidth, widgetPos.y + digitHeight);
if ((i + 1) % 3 == 0 && i < 11) { if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += 12; commaOffset += commaSz.x;
} }
} }
} }
@ -67,13 +77,14 @@ void FrequencySelect::draw() {
window = ImGui::GetCurrentWindow(); window = ImGui::GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin(); widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax(); widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x + 255; ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x;
widgetPos.y += window->Pos.y - 3; widgetPos.y += window->Pos.y - 3;
widgetEndPos.x += window->Pos.x + 255; widgetEndPos.x += window->Pos.x + cursorPos.x;
widgetEndPos.y += window->Pos.y - 3; widgetEndPos.y += window->Pos.y - 3;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y); widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
ImGui::PushFont(font); ImGui::PushFont(style::bigFont);
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) { if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos; lastWidgetPos = widgetPos;
@ -84,19 +95,29 @@ void FrequencySelect::draw() {
onResize(); onResize();
} }
ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f);
ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text);
ImVec2 digitSz = ImGui::CalcTextSize("0");
ImVec2 commaSz = ImGui::CalcTextSize(".");
int digitHeight = digitSz.y;
int digitWidth = digitSz.x;
int commaOffset = 0; int commaOffset = 0;
bool zeros = true; bool zeros = true;
ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y)));
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
if (digits[i] != 0) { if (digits[i] != 0) {
zeros = false; zeros = false;
} }
sprintf(buf, "%d", digits[i]); sprintf(buf, "%d", digits[i]);
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset, widgetPos.y), window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), buf); zeros ? disabledColor : textColor, buf);
if ((i + 1) % 3 == 0 && i < 11) { if ((i + 1) % 3 == 0 && i < 11) {
commaOffset += 12; commaOffset += commaSz.x;
window->DrawList->AddText(ImVec2(widgetPos.x + (i * 22) + commaOffset + 10, widgetPos.y), window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y),
zeros ? IM_COL32(90, 90, 90, 255) : IM_COL32(255, 255, 255, 255), "."); zeros ? disabledColor : textColor, ".");
} }
} }
@ -138,19 +159,22 @@ void FrequencySelect::draw() {
} }
} }
long freq = 0; uint64_t freq = 0;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
freq += digits[i] * pow(10, 11 - i); freq += digits[i] * pow(10, 11 - i);
} }
frequency = freq; frequency = freq;
ImGui::PopFont(); ImGui::PopFont();
ImGui::NewLine();
ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17);
//ImGui::NewLine();
} }
void FrequencySelect::setFrequency(long freq) { void FrequencySelect::setFrequency(uint64_t freq) {
int i = 11; int i = 11;
for (long f = freq; i >= 0; i--) { for (uint64_t f = freq; i >= 0; i--) {
digits[i] = f % 10; digits[i] = f % 10;
f -= digits[i]; f -= digits[i];
f /= 10; f /= 10;

View File

@ -1,15 +1,16 @@
#pragma once #pragma once
#include <imgui.h> #include <imgui.h>
#include <imgui_internal.h> #include <imgui_internal.h>
#include <stdint.h>
class FrequencySelect { class FrequencySelect {
public: public:
FrequencySelect(); FrequencySelect();
void init(); void init();
void draw(); void draw();
void setFrequency(long freq); void setFrequency(uint64_t freq);
long frequency; uint64_t frequency;
bool frequencyChanged = false; bool frequencyChanged = false;
private: private:
@ -26,7 +27,6 @@ private:
ImVec2 lastWidgetSize; ImVec2 lastWidgetSize;
ImGuiWindow* window; ImGuiWindow* window;
ImFont* font;
int digits[12]; int digits[12];
ImVec2 digitBottomMins[12]; ImVec2 digitBottomMins[12];

View File

@ -0,0 +1,77 @@
#include <gui/widgets/menu.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
Menu::Menu() {
}
void Menu::registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx, ModuleManager::Instance* inst) {
MenuItem_t item;
item.drawHandler = drawHandler;
item.ctx = ctx;
item.inst = inst;
items[name] = item;
if (!isInOrderList(name)) {
order.push_back(name);
}
}
void Menu::removeEntry(std::string name) {
items.erase(name);
}
void Menu::draw() {
MenuItem_t item;
float menuWidth = ImGui::GetContentRegionAvailWidth();
ImGuiWindow* window = ImGui::GetCurrentWindow();
for (std::string name : order) {
if (items.find(name) == items.end()) {
continue;
}
item = items[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)) {
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)) {
enabled ? item.inst->enable() : item.inst->disable();
}
ImGui::SetCursorPos(pos);
}
item.drawHandler(item.ctx);
ImGui::Spacing();
}
else 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)) {
enabled ? item.inst->enable() : item.inst->disable();
}
ImGui::SetCursorPos(pos);
}
}
}
bool Menu::isInOrderList(std::string name) {
for (std::string _name : order) {
if (_name == name) {
return true;
}
}
return false;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <module.h>
class Menu {
public:
Menu();
struct MenuItem_t {
void (*drawHandler)(void* ctx);
void* ctx;
ModuleManager::Instance* inst;
};
void registerEntry(std::string name, void (*drawHandler)(void* ctx), void* ctx = NULL, ModuleManager::Instance* inst = NULL);
void removeEntry(std::string name);
void draw();
std::vector<std::string> order;
private:
bool isInOrderList(std::string name);
std::map<std::string, MenuItem_t> items;
};

View File

@ -0,0 +1,23 @@
#include <gui/widgets/stepped_slider.h>
#include <imgui.h>
#include <imgui_internal.h>
namespace ImGui {
bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format) {
if (!display_format) {
display_format = "%.3f";
}
char text_buf[64] = {};
ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), display_format, *v);
// Map from [v_min,v_max] to [0,N]
const int countValues = int((v_max-v_min)/v_step);
int v_i = int((*v - v_min)/v_step);
const bool value_changed = ImGui::SliderInt(label, &v_i, 0, countValues, text_buf);
// Remap from [0,N] to [v_min,v_max]
*v = v_min + float(v_i) * v_step;
return value_changed;
}
}

View File

@ -0,0 +1,5 @@
#pragma once
namespace ImGui {
bool SliderFloatWithSteps(const char* label, float* v, float v_min, float v_max, float v_step, const char* display_format = "%.3f");
}

View File

@ -0,0 +1,53 @@
#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 VolumeMeter(float avg, float peak, float val_min, float val_max, const ImVec2& size_arg) {
ImGuiWindow* window = GetCurrentWindow();
ImGuiStyle& style = GImGui->Style;
avg = std::clamp<float>(avg, val_min, val_max);
peak = std::clamp<float>(peak, val_min, val_max);
float pad = style.FramePadding.y;
ImVec2 min = window->DC.CursorPos;
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (GImGui->FontSize / 2) + style.FramePadding.y);
ImRect bb(min, min + size);
float lineHeight = size.y;
ItemSize(size, style.FramePadding.y);
if (!ItemAdd(bb, 0)) {
return;
}
float zeroDb = roundf(((-val_min) / (val_max - val_min)) * size.x);
window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 127 ));
window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(size.x, lineHeight), IM_COL32( 255, 0, 0, 127 ));
float end = roundf(((avg - val_min) / (val_max - val_min)) * size.x);
float endP = roundf(((peak - val_min) / (val_max - val_min)) * size.x);
if (avg <= 0) {
window->DrawList->AddRectFilled(min, min + ImVec2(end, lineHeight), IM_COL32( 0, 255, 0, 255 ));
}
else {
window->DrawList->AddRectFilled(min, min + ImVec2(zeroDb, lineHeight), IM_COL32( 0, 255, 0, 255 ));
window->DrawList->AddRectFilled(min + ImVec2(zeroDb, 0), min + ImVec2(end, lineHeight), IM_COL32( 255, 0, 0, 255 ));
}
if (peak <= 0) {
window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 127, 255, 127, 255 ));
}
else {
window->DrawList->AddLine(min + ImVec2(endP, -1), min + ImVec2(endP, lineHeight - 1), IM_COL32( 255, 127, 127, 255 ));
}
}
}

View File

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

View File

@ -0,0 +1,919 @@
#include <gui/widgets/waterfall.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <GL/glew.h>
#include <imutils.h>
#include <algorithm>
#include <spdlog/spdlog.h>
float DEFAULT_COLOR_MAP[][3] = {
{0x00, 0x00, 0x20},
{0x00, 0x00, 0x30},
{0x00, 0x00, 0x50},
{0x00, 0x00, 0x91},
{0x1E, 0x90, 0xFF},
{0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0x00},
{0xFE, 0x6D, 0x16},
{0xFF, 0x00, 0x00},
{0xC6, 0x00, 0x00},
{0x9F, 0x00, 0x00},
{0x75, 0x00, 0x00},
{0x4A, 0x00, 0x00}
};
void doZoom(int offset, int width, int outWidth, float* data, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 65535) {
width = 65535;
}
float factor = (float)width / (float)outWidth;
for (int i = 0; i < outWidth; i++) {
out[i] = data[(int)(offset + ((float)i * factor))];
}
}
// TODO: Fix this hacky BS
double freq_ranges[] = {
1.0, 2.0, 2.5, 5.0,
10.0, 20.0, 25.0, 50.0,
100.0, 200.0, 250.0, 500.0,
1000.0, 2000.0, 2500.0, 5000.0,
10000.0, 20000.0, 25000.0, 50000.0,
100000.0, 200000.0, 250000.0, 500000.0,
1000000.0, 2000000.0, 2500000.0, 5000000.0,
10000000.0, 20000000.0, 25000000.0, 50000000.0
};
double findBestRange(double bandwidth, int maxSteps) {
for (int i = 0; i < 32; i++) {
if (bandwidth / freq_ranges[i] < (double)maxSteps) {
return freq_ranges[i];
}
}
return 50000000.0;
}
void printAndScale(double freq, char* buf) {
if (freq < 1000) {
sprintf(buf, "%.3lf", freq);
}
else if (freq < 1000000) {
sprintf(buf, "%.3lfK", freq / 1000.0);
}
else if (freq < 1000000000) {
sprintf(buf, "%.3lfM", freq / 1000000.0);
}
else if (freq < 1000000000000) {
sprintf(buf, "%.3lfG", freq / 1000000000.0);
}
for (int i = strlen(buf) - 2; i >= 0; i--) {
if (buf[i] != '0') {
if (buf[i] == '.') {
i--;
}
char scale = buf[strlen(buf) - 1];
buf[i + 1] = scale;
buf[i + 2] = 0;
return;
}
}
}
namespace ImGui {
WaterFall::WaterFall() {
fftMin = -70.0;
fftMax = 0.0;
waterfallMin = -70.0;
waterfallMax = 0.0;
FFTAreaHeight = 300;
newFFTAreaHeight = FFTAreaHeight;
fftHeight = FFTAreaHeight - 50;
dataWidth = 600;
lastWidgetPos.x = 0;
lastWidgetPos.y = 0;
lastWidgetSize.x = 0;
lastWidgetSize.y = 0;
latestFFT = new float[1];
waterfallFb = new uint32_t[1];
viewBandwidth = 1.0;
wholeBandwidth = 1.0;
updatePallette(DEFAULT_COLOR_MAP, 13);
}
void WaterFall::init() {
glGenTextures(1, &textureId);
}
void WaterFall::drawFFT() {
// Calculate scaling factor
float startLine = floorf(fftMax / vRange) * vRange;
float vertRange = fftMax - fftMin;
float scaleFactor = fftHeight / vertRange;
char buf[100];
ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines);
ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2);
// Vertical scale
for (float line = startLine; line > fftMin; line -= vRange) {
float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor);
window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)),
ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)),
IM_COL32(50, 50, 50, 255), 1.0);
sprintf(buf, "%d", (int)line);
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), IM_COL32( 255, 255, 255, 255 ), buf);
}
// Horizontal scale
double startFreq = ceilf(lowerFreq / range) * range;
double horizScale = (double)dataWidth / viewBandwidth;
for (double freq = startFreq; freq < upperFreq; freq += range) {
double xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale);
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10),
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
IM_COL32(50, 50, 50, 255), 1.0);
window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10),
ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17),
IM_COL32(255, 255, 255, 255), 1.0);
printAndScale(freq, buf);
ImVec2 txtSz = ImGui::CalcTextSize(buf);
window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), IM_COL32( 255, 255, 255, 255 ), buf);
}
// Data
if (latestFFT != NULL && fftLines != 0) {
for (int i = 1; i < dataWidth; i++) {
double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor);
double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor);
aPos = std::clamp<double>(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
bPos = std::clamp<double>(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10);
window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)),
ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0);
window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)),
ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0);
}
}
// X Axis
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10),
ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10),
IM_COL32(255, 255, 255, 255), 1.0);
// Y Axis
window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9),
ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9),
IM_COL32(255, 255, 255, 255), 1.0);
}
void WaterFall::drawWaterfall() {
if (waterfallUpdate) {
waterfallUpdate = false;
updateWaterfallTexture();
}
window->DrawList->AddImage((void*)(intptr_t)textureId, ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51),
ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight));
}
void WaterFall::drawVFOs() {
for (auto const& [name, vfo] : vfos) {
if (vfo->redrawRequired) {
vfo->redrawRequired = false;
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
}
vfo->draw(window, name == selectedVFO);
}
}
void WaterFall::selectFirstVFO() {
bool available = false;
for (auto const& [name, vfo] : vfos) {
available = true;
selectedVFO = name;
return;
}
if (!available) {
selectedVFO = "";
}
}
void WaterFall::processInputs() {
WaterfallVFO* vfo = NULL;
if (selectedVFO != "") {
vfo = vfos[selectedVFO];
}
ImVec2 mousePos = ImGui::GetMousePos();
ImVec2 drag = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
ImVec2 dragOrigin(mousePos.x - drag.x, mousePos.y - drag.y);
bool mouseHovered, mouseHeld;
bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, fftAreaMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld,
ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick);
bool draging = ImGui::IsMouseDragging(ImGuiMouseButton_Left) && ImGui::IsWindowFocused();
bool mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax);
bool mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax);
// If mouse was clicked on a VFO, select VFO and return
// If mouse was clicked but not on a VFO, move selected VFO to position
if (mouseClicked) {
for (auto const& [name, _vfo] : vfos) {
if (name == selectedVFO) {
continue;
}
if (IS_IN_AREA(mousePos, _vfo->rectMin, _vfo->rectMax)) {
selectedVFO = name;
selectedVFOChanged = true;
return;
}
}
if (vfo != NULL) {
int refCenter = mousePos.x - (widgetPos.x + 50);
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y)) {
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
off += centerFreq;
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
vfo->setOffset(off);
}
}
}
// Draging VFO
if (draging && mouseInFFT) {
int refCenter = mousePos.x - (widgetPos.x + 50);
if (refCenter >= 0 && refCenter < dataWidth && mousePos.y > widgetPos.y && mousePos.y < (widgetPos.y + widgetSize.y) && vfo != NULL) {
double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset;
off += centerFreq;
off = (round(off / vfo->snapInterval) * vfo->snapInterval) - centerFreq;
vfo->setOffset(off);
}
}
// Draging frequency scale
if (draging && mouseInFreq) {
double deltax = drag.x - lastDrag;
lastDrag = drag.x;
double viewDelta = deltax * (viewBandwidth / (double)dataWidth);
viewOffset -= viewDelta;
if (viewOffset + (viewBandwidth / 2.0) > wholeBandwidth / 2.0) {
double freqOffset = (viewOffset + (viewBandwidth / 2.0)) - (wholeBandwidth / 2.0);
viewOffset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
centerFreq += freqOffset;
centerFreqMoved = true;
}
if (viewOffset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
double freqOffset = (viewOffset - (viewBandwidth / 2.0)) + (wholeBandwidth / 2.0);
viewOffset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
centerFreq += freqOffset;
centerFreqMoved = true;
}
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
if (viewBandwidth != wholeBandwidth) {
updateAllVFOs();
updateWaterfallFb();
}
}
else {
lastDrag = 0;
}
}
void WaterFall::updateWaterfallFb() {
if (!waterfallVisible || rawFFTs == NULL) {
return;
}
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
int drawDataSize;
int drawDataStart;
// TODO: Maybe put on the stack for faster alloc?
float* tempData = new float[dataWidth];
float pixel;
float dataRange = waterfallMax - waterfallMin;
int count = std::min<float>(waterfallHeight, fftLines);
if (rawFFTs != NULL) {
for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[((i + currentFFTLine) % waterfallHeight) * rawFFTSize], tempData);
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
}
}
}
delete[] tempData;
waterfallUpdate = true;
}
void WaterFall::drawBandPlan() {
int count = bandplan->bands.size();
double horizScale = (double)dataWidth / viewBandwidth;
double start, end, center, aPos, bPos, cPos, width;
ImVec2 txtSz;
bool startVis, endVis;
uint32_t color, colorTrans;
for (int i = 0; i < count; i++) {
start = bandplan->bands[i].start;
end = bandplan->bands[i].end;
if (start < lowerFreq && end < lowerFreq) {
continue;
}
if (start > upperFreq && end > upperFreq) {
continue;
}
startVis = (start > lowerFreq);
endVis = (end < upperFreq);
start = std::clamp<double>(start, lowerFreq, upperFreq);
end = std::clamp<double>(end, lowerFreq, upperFreq);
center = (start + end) / 2.0;
aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale);
bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale);
cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale);
width = bPos - aPos;
txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str());
float height = txtSz.y * 2.5f;
if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) {
color = bandplan::colorTable[bandplan->bands[i].type].colorValue;
colorTrans = bandplan::colorTable[bandplan->bands[i].type].transColorValue;
}
else {
color = IM_COL32(255, 255, 255, 255);
colorTrans = IM_COL32(255, 255, 255, 100);
}
if (aPos <= widgetPos.x + 50) {
aPos = widgetPos.x + 51;
}
if (bPos <= widgetPos.x + 50) {
bPos = widgetPos.x + 51;
}
if (width >= 1.0) {
window->DrawList->AddRectFilled(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height),
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10), colorTrans);
if (startVis) {
window->DrawList->AddLine(ImVec2(roundf(aPos), widgetPos.y + fftHeight + 10 - height - 1),
ImVec2(roundf(aPos), widgetPos.y + fftHeight + 9), color);
}
if (endVis) {
window->DrawList->AddLine(ImVec2(roundf(bPos), widgetPos.y + fftHeight + 10 - height - 1),
ImVec2(roundf(bPos), widgetPos.y + fftHeight + 9), color);
}
}
if (txtSz.x <= width) {
window->DrawList->AddText(ImVec2(cPos - (txtSz.x / 2.0), widgetPos.y + fftHeight + 10 - (height / 2.0f) - (txtSz.y / 2.0f)),
IM_COL32(255, 255, 255, 255), bandplan->bands[i].name.c_str());
}
}
}
void WaterFall::updateWaterfallTexture() {
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, dataWidth, waterfallHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (uint8_t*)waterfallFb);
}
void WaterFall::onPositionChange() {
// Nothing to see here...
}
void WaterFall::onResize() {
// return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) {
return;
}
int lastWaterfallHeight = waterfallHeight;
if (waterfallVisible) {
FFTAreaHeight = std::min<int>(FFTAreaHeight, widgetSize.y - 50);
fftHeight = FFTAreaHeight - 50;
waterfallHeight = widgetSize.y - fftHeight - 52;
}
else {
fftHeight = widgetSize.y - 50;
}
dataWidth = widgetSize.x - 60.0;
if (waterfallVisible) {
// Raw FFT resize
fftLines = std::min<int>(fftLines, waterfallHeight) - 1;
if (rawFFTs != NULL) {
if (currentFFTLine != 0) {
float* tempWF = new float[currentFFTLine * rawFFTSize];
int moveCount = lastWaterfallHeight - currentFFTLine;
memcpy(tempWF, rawFFTs, currentFFTLine * rawFFTSize * sizeof(float));
memmove(rawFFTs, &rawFFTs[currentFFTLine * rawFFTSize], moveCount * rawFFTSize * sizeof(float));
memcpy(&rawFFTs[moveCount * rawFFTSize], tempWF, currentFFTLine * rawFFTSize * sizeof(float));
delete[] tempWF;
}
currentFFTLine = 0;
rawFFTs = (float*)realloc(rawFFTs, waterfallHeight * rawFFTSize * sizeof(float));
}
else {
rawFFTs = (float*)malloc(waterfallHeight * rawFFTSize * sizeof(float));
}
// ==============
}
if (latestFFT != NULL) {
delete[] latestFFT;
}
latestFFT = new float[dataWidth];
if (waterfallVisible) {
delete[] waterfallFb;
waterfallFb = new uint32_t[dataWidth * waterfallHeight];
memset(waterfallFb, 0, dataWidth * waterfallHeight * sizeof(uint32_t));
}
for (int i = 0; i < dataWidth; i++) {
latestFFT[i] = -1000.0; // Hide everything
}
fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9);
fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10);
freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11);
freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50);
maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10);
maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y);
range = findBestRange(viewBandwidth, maxHSteps);
vRange = findBestRange(fftMax - fftMin, maxVSteps);
vRange = 10.0;
updateWaterfallFb();
updateAllVFOs();
}
void WaterFall::draw() {
buf_mtx.lock();
window = GetCurrentWindow();
widgetPos = ImGui::GetWindowContentRegionMin();
widgetEndPos = ImGui::GetWindowContentRegionMax();
widgetPos.x += window->Pos.x;
widgetPos.y += window->Pos.y;
widgetEndPos.x += window->Pos.x - 4; // Padding
widgetEndPos.y += window->Pos.y;
widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y);
if (selectedVFO == "" && vfos.size() > 0) {
selectFirstVFO();
}
if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) {
lastWidgetPos = widgetPos;
onPositionChange();
}
if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) {
lastWidgetSize = widgetSize;
onResize();
}
window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 ));
window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32( 50, 50, 50, 255 ));
window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0);
processInputs();
drawFFT();
if (waterfallVisible) {
drawWaterfall();
}
drawVFOs();
if (bandplan != NULL && bandplanVisible) {
drawBandPlan();
}
if (!waterfallVisible) {
buf_mtx.unlock();
return;
}
// Handle fft resize
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
mousePos.x -= widgetPos.x;
mousePos.y -= widgetPos.y;
bool click = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool down = ImGui::IsMouseDown(ImGuiMouseButton_Left);
if (draggingFW) {
newFFTAreaHeight = mousePos.y;
newFFTAreaHeight = std::clamp<float>(newFFTAreaHeight, 150, widgetSize.y - 50);
ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y),
ImGui::GetColorU32(ImGuiCol_SeparatorActive));
}
if (mousePos.y >= newFFTAreaHeight - 2 && mousePos.y <= newFFTAreaHeight + 2 && mousePos.x > 0 && mousePos.x < widgetSize.x) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
if (click) {
draggingFW = true;
}
}
if(!down && draggingFW) {
draggingFW = false;
FFTAreaHeight = newFFTAreaHeight;
onResize();
}
buf_mtx.unlock();
}
float* WaterFall::getFFTBuffer() {
if (rawFFTs == NULL) { return NULL; }
buf_mtx.lock();
if (waterfallVisible) {
currentFFTLine--;
fftLines++;
currentFFTLine = ((currentFFTLine + waterfallHeight) % waterfallHeight);
fftLines = std::min<float>(fftLines, waterfallHeight);
return &rawFFTs[currentFFTLine * rawFFTSize];
}
return rawFFTs;
}
void WaterFall::pushFFT() {
if (rawFFTs == NULL) { return; }
double offsetRatio = viewOffset / (wholeBandwidth / 2.0);
int drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
if (waterfallVisible) {
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(latestFFT[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
int id = (int)(pixel * (WATERFALL_RESOLUTION - 1));
waterfallFb[j] = waterfallPallet[id];
}
waterfallUpdate = true;
}
else {
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
fftLines = 1;
}
buf_mtx.unlock();
}
void WaterFall::updatePallette(float colors[][3], int colorCount) {
std::lock_guard<std::mutex> lck(buf_mtx);
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
float r = (colors[lowerId][0] * (1.0 - ratio)) + (colors[upperId][0] * (ratio));
float g = (colors[lowerId][1] * (1.0 - ratio)) + (colors[upperId][1] * (ratio));
float b = (colors[lowerId][2] * (1.0 - ratio)) + (colors[upperId][2] * (ratio));
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
updateWaterfallFb();
}
void WaterFall::updatePalletteFromArray(float* colors, int colorCount) {
std::lock_guard<std::mutex> lck(buf_mtx);
for (int i = 0; i < WATERFALL_RESOLUTION; i++) {
int lowerId = floorf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
int upperId = ceilf(((float)i / (float)WATERFALL_RESOLUTION) * colorCount);
lowerId = std::clamp<int>(lowerId, 0, colorCount - 1);
upperId = std::clamp<int>(upperId, 0, colorCount - 1);
float ratio = (((float)i / (float)WATERFALL_RESOLUTION) * colorCount) - lowerId;
float r = (colors[(lowerId * 3) + 0] * (1.0 - ratio)) + (colors[(upperId * 3) + 0] * (ratio));
float g = (colors[(lowerId * 3) + 1] * (1.0 - ratio)) + (colors[(upperId * 3) + 1] * (ratio));
float b = (colors[(lowerId * 3) + 2] * (1.0 - ratio)) + (colors[(upperId * 3) + 2] * (ratio));
waterfallPallet[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
updateWaterfallFb();
}
void WaterFall::autoRange() {
float min = INFINITY;
float max = -INFINITY;
for (int i = 0; i < dataWidth; i++) {
if (latestFFT[i] < min) {
min = latestFFT[i];
}
if (latestFFT[i] > max) {
max = latestFFT[i];
}
}
fftMin = min - 5;
fftMax = max + 5;
}
void WaterFall::setCenterFrequency(double freq) {
centerFreq = freq;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
updateAllVFOs();
}
double WaterFall::getCenterFrequency() {
return centerFreq;
}
void WaterFall::setBandwidth(double bandWidth) {
double currentRatio = viewBandwidth / wholeBandwidth;
wholeBandwidth = bandWidth;
setViewBandwidth(bandWidth * currentRatio);
for (auto const& [name, vfo] : vfos) {
if (vfo->lowerOffset < -(bandWidth / 2)) {
vfo->setCenterOffset(-(bandWidth / 2));
}
if (vfo->upperOffset > (bandWidth / 2)) {
vfo->setCenterOffset(bandWidth / 2);
}
}
updateAllVFOs();
}
double WaterFall::getBandwidth() {
return wholeBandwidth;
}
void WaterFall::setViewBandwidth(double bandWidth) {
std::lock_guard<std::mutex> lck(buf_mtx);
if (bandWidth == viewBandwidth) {
return;
}
if (abs(viewOffset) + (bandWidth / 2.0) > wholeBandwidth / 2.0) {
if (viewOffset < 0) {
viewOffset = (bandWidth / 2.0) - (wholeBandwidth / 2.0);
}
else {
viewOffset = (wholeBandwidth / 2.0) - (bandWidth / 2.0);
}
}
viewBandwidth = bandWidth;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
range = findBestRange(bandWidth, maxHSteps);
updateWaterfallFb();
updateAllVFOs();
}
double WaterFall::getViewBandwidth() {
return viewBandwidth;
}
void WaterFall::setViewOffset(double offset) {
std::lock_guard<std::mutex> lck(buf_mtx);
if (offset == viewOffset) {
return;
}
if (offset - (viewBandwidth / 2.0) < -(wholeBandwidth / 2.0)) {
offset = (viewBandwidth / 2.0) - (wholeBandwidth / 2.0);
}
if (offset + (viewBandwidth / 2.0) > (wholeBandwidth / 2.0)) {
offset = (wholeBandwidth / 2.0) - (viewBandwidth / 2.0);
}
viewOffset = offset;
lowerFreq = (centerFreq + viewOffset) - (viewBandwidth / 2.0);
upperFreq = (centerFreq + viewOffset) + (viewBandwidth / 2.0);
updateWaterfallFb();
updateAllVFOs();
}
double WaterFall::getViewOffset() {
return viewOffset;
}
void WaterFall::setFFTMin(float min) {
fftMin = min;
vRange = findBestRange(fftMax - fftMin, maxVSteps);
}
float WaterFall::getFFTMin() {
return fftMin;
}
void WaterFall::setFFTMax(float max) {
fftMax = max;
vRange = findBestRange(fftMax - fftMin, maxVSteps);
}
float WaterFall::getFFTMax() {
return fftMax;
}
void WaterFall::setWaterfallMin(float min) {
std::lock_guard<std::mutex> lck(buf_mtx);
if (min == waterfallMin) {
return;
}
waterfallMin = min;
updateWaterfallFb();
}
float WaterFall::getWaterfallMin() {
return waterfallMin;
}
void WaterFall::setWaterfallMax(float max) {
std::lock_guard<std::mutex> lck(buf_mtx);
if (max == waterfallMax) {
return;
}
waterfallMax = max;
updateWaterfallFb();
}
float WaterFall::getWaterfallMax() {
return waterfallMax;
}
void WaterFall::updateAllVFOs() {
for (auto const& [name, vfo] : vfos) {
vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight);
}
}
void WaterFall::setRawFFTSize(int size, bool lock) {
std::lock_guard<std::mutex> lck(buf_mtx);
rawFFTSize = size;
if (rawFFTs != NULL) {
int wfSize = std::max<int>(1, waterfallHeight);
rawFFTs = (float*)realloc(rawFFTs, rawFFTSize * wfSize * sizeof(float));
}
else {
int wfSize = std::max<int>(1, waterfallHeight);
rawFFTs = (float*)malloc(rawFFTSize * wfSize * sizeof(float));
}
memset(rawFFTs, 0, rawFFTSize * waterfallHeight * sizeof(float));
}
void WaterfallVFO::setOffset(double offset) {
generalOffset = offset;
if (reference == REF_CENTER) {
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
}
else if (reference == REF_LOWER) {
lowerOffset = offset;
centerOffset = offset + (bandwidth / 2.0);
upperOffset = offset + bandwidth;
}
else if (reference == REF_UPPER) {
upperOffset = offset;
centerOffset = offset - (bandwidth / 2.0);
lowerOffset = offset - bandwidth;
}
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setCenterOffset(double offset) {
if (reference == REF_CENTER) {
generalOffset = offset;
}
else if (reference == REF_LOWER) {
generalOffset = offset - (bandwidth / 2.0);
}
else if (reference == REF_UPPER) {
generalOffset = offset + (bandwidth / 2.0);
}
centerOffset = offset;
lowerOffset = offset - (bandwidth / 2.0);
upperOffset = offset + (bandwidth / 2.0);
centerOffsetChanged = true;
upperOffsetChanged = true;
lowerOffsetChanged = true;
redrawRequired = true;
}
void WaterfallVFO::setBandwidth(double bw) {
if (bandwidth == bw || bw < 0) {
return;
}
bandwidth = bw;
if (reference == REF_CENTER) {
lowerOffset = centerOffset - (bandwidth / 2.0);
upperOffset = centerOffset + (bandwidth / 2.0);
}
else if (reference == REF_LOWER) {
centerOffset = lowerOffset + (bandwidth / 2.0);
upperOffset = lowerOffset + bandwidth;
centerOffsetChanged = true;
}
else if (reference == REF_UPPER) {
centerOffset = upperOffset - (bandwidth / 2.0);
lowerOffset = upperOffset - bandwidth;
centerOffsetChanged = true;
}
redrawRequired = true;
}
void WaterfallVFO::setReference(int ref) {
if (reference == ref || ref < 0 || ref >= _REF_COUNT) {
return;
}
reference = ref;
setOffset(generalOffset);
}
void WaterfallVFO::updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight) {
double width = (bandwidth / viewBandwidth) * (double)dataWidth;
int center = roundf((((centerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
int left = roundf((((lowerOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
int right = roundf((((upperOffset - viewOffset) / (viewBandwidth / 2.0)) + 1.0) * ((double)dataWidth / 2.0));
if (left >= 0 && left < dataWidth && reference == REF_LOWER) {
lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else if (center >= 0 && center < dataWidth && reference == REF_CENTER) {
lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else if (right >= 0 && right < dataWidth && reference == REF_UPPER) {
lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9);
lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9);
lineVisible = true;
}
else {
lineVisible = false;
}
left = std::clamp<int>(left, 0, dataWidth - 1);
right = std::clamp<int>(right, 0, dataWidth - 1);
rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10);
rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10);
}
void WaterfallVFO::draw(ImGuiWindow* window, bool selected) {
window->DrawList->AddRectFilled(rectMin, rectMax, IM_COL32(255, 255, 255, 50));
if (lineVisible) {
window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255));
}
};
void WaterFall::showWaterfall() {
buf_mtx.lock();
waterfallVisible = true;
onResize();
memset(rawFFTs, 0, waterfallHeight * rawFFTSize * sizeof(float));
updateWaterfallFb();
buf_mtx.unlock();
}
void WaterFall::hideWaterfall() {
buf_mtx.lock();
waterfallVisible = false;
onResize();
buf_mtx.unlock();
}
void WaterFall::setFFTHeight(int height) {
FFTAreaHeight = height;
newFFTAreaHeight = height;
buf_mtx.lock();
onResize();
buf_mtx.unlock();
}
int WaterFall::getFFTHeight() {
return FFTAreaHeight;
}
void WaterFall::showBandplan() {
bandplanVisible = true;
}
void WaterFall::hideBandplan() {
bandplanVisible = false;
}
void WaterfallVFO::setSnapInterval(double interval) {
snapInterval = interval;
}
};

View File

@ -0,0 +1,203 @@
#pragma once
#include <vector>
#include <mutex>
#include <gui/widgets/bandplan.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <GL/glew.h>
#define WATERFALL_RESOLUTION 1000000
namespace ImGui {
class WaterfallVFO {
public:
void setOffset(double offset);
void setCenterOffset(double offset);
void setBandwidth(double bw);
void setReference(int ref);
void setSnapInterval(double interval);
void updateDrawingVars(double viewBandwidth, float dataWidth, double viewOffset, ImVec2 widgetPos, int fftHeight); // NOTE: Datawidth double???
void draw(ImGuiWindow* window, bool selected);
enum {
REF_LOWER,
REF_CENTER,
REF_UPPER,
_REF_COUNT
};
double generalOffset;
double centerOffset;
double lowerOffset;
double upperOffset;
double bandwidth;
double snapInterval = 5000;
int reference = REF_CENTER;
ImVec2 rectMin;
ImVec2 rectMax;
ImVec2 lineMin;
ImVec2 lineMax;
bool centerOffsetChanged = false;
bool lowerOffsetChanged = false;
bool upperOffsetChanged = false;
bool redrawRequired = true;
bool lineVisible = true;
};
class WaterFall {
public:
WaterFall();
void init();
void draw();
float* getFFTBuffer();
void pushFFT();
void updatePallette(float colors[][3], int colorCount);
void updatePalletteFromArray(float* colors, int colorCount);
void setCenterFrequency(double freq);
double getCenterFrequency();
void setBandwidth(double bandWidth);
double getBandwidth();
void setViewBandwidth(double bandWidth);
double getViewBandwidth();
void setViewOffset(double offset);
double getViewOffset();
void setFFTMin(float min);
float getFFTMin();
void setFFTMax(float max);
float getFFTMax();
void setWaterfallMin(float min);
float getWaterfallMin();
void setWaterfallMax(float max);
float getWaterfallMax();
void setZoom(double zoomLevel);
void setOffset(double zoomOffset);
void autoRange();
void selectFirstVFO();
void showWaterfall();
void hideWaterfall();
void showBandplan();
void hideBandplan();
void setFFTHeight(int height);
int getFFTHeight();
void setRawFFTSize(int size, bool lock = true);
bool centerFreqMoved = false;
bool vfoFreqChanged = false;
bool bandplanEnabled = false;
bandplan::BandPlan_t* bandplan = NULL;
std::map<std::string, WaterfallVFO*> vfos;
std::string selectedVFO = "";
bool selectedVFOChanged = false;
enum {
REF_LOWER,
REF_CENTER,
REF_UPPER,
_REF_COUNT
};
private:
void drawWaterfall();
void drawFFT();
void drawVFOs();
void drawBandPlan();
void processInputs();
void onPositionChange();
void onResize();
void updateWaterfallFb();
void updateWaterfallTexture();
void updateAllVFOs();
bool waterfallUpdate = false;
uint32_t waterfallPallet[WATERFALL_RESOLUTION];
ImVec2 widgetPos;
ImVec2 widgetEndPos;
ImVec2 widgetSize;
ImVec2 lastWidgetPos;
ImVec2 lastWidgetSize;
ImVec2 fftAreaMin;
ImVec2 fftAreaMax;
ImVec2 freqAreaMin;
ImVec2 freqAreaMax;
ImVec2 waterfallAreaMin;
ImVec2 waterfallAreaMax;
ImGuiWindow* window;
GLuint textureId;
std::mutex buf_mtx;
float vRange;
int maxVSteps;
int maxHSteps;
int dataWidth; // Width of the FFT and waterfall
int fftHeight; // Height of the fft graph
int waterfallHeight = 0; // Height of the waterfall
double viewBandwidth;
double viewOffset;
double lowerFreq;
double upperFreq;
double range;
float lastDrag;
int vfoRef = REF_CENTER;
// Absolute values
double centerFreq;
double wholeBandwidth;
// Ranges
float fftMin;
float fftMax;
float waterfallMin;
float waterfallMax;
//std::vector<std::vector<float>> rawFFTs;
int rawFFTSize;
float* rawFFTs = NULL;
float* latestFFT;
int currentFFTLine = 0;
int fftLines = 0;
uint32_t* waterfallFb;
bool draggingFW = false;
int FFTAreaHeight;
int newFFTAreaHeight;
bool waterfallVisible = true;
bool bandplanVisible = false;
};
};

View File

@ -2640,6 +2640,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
// Slider behavior // Slider behavior
ImRect grab_bb; ImRect grab_bb;
const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb); const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, power, ImGuiSliderFlags_None, &grab_bb);
if (value_changed) if (value_changed)
MarkItemEdited(id); MarkItemEdited(id);

File diff suppressed because it is too large Load Diff

145
core/src/module.cpp Normal file
View File

@ -0,0 +1,145 @@
#include <module.h>
#include <filesystem>
#include <spdlog/spdlog.h>
ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
Module_t mod;
if (!std::filesystem::exists(path)) {
spdlog::error("{0} does not exist", path);
mod.handle = NULL;
return mod;
}
if (!std::filesystem::is_regular_file(path)) {
spdlog::error("{0} isn't a loadable module", path);
mod.handle = NULL;
return mod;
}
#ifdef _WIN32
mod.handle = LoadLibraryA(path.c_str());
if (mod.handle == NULL) {
spdlog::error("Couldn't load {0}.", path);
mod.handle = NULL;
return mod;
}
mod.info = (ModuleInfo_t*)GetProcAddress(mod.handle, "_INFO_");
mod.init = (void(*)())GetProcAddress(mod.handle, "_INIT_");
mod.createInstance = (Instance*(*)(std::string))GetProcAddress(mod.handle, "_CREATE_INSTANCE_");
mod.deleteInstance = (void(*)(Instance*))GetProcAddress(mod.handle, "_DELETE_INSTANCE_");
mod.end = (void(*)())GetProcAddress(mod.handle, "_END_");
#else
mod.handle = dlopen(path.c_str(), RTLD_LAZY);
if (mod.handle == NULL) {
spdlog::error("Couldn't load {0}.", path);
mod.handle = NULL;
return mod;
}
mod.info = (ModuleInfo_t*)dlsym(mod.handle, "_INFO_");
mod.init = (void(*)())dlsym(mod.handle, "_INIT_");
mod.createInstance = (Instance*(*)(std::string))dlsym(mod.handle, "_CREATE_INSTANCE_");
mod.deleteInstance = (void(*)(Instance*))dlsym(mod.handle, "_DELETE_INSTANCE_");
mod.end = (void(*)())dlsym(mod.handle, "_END_");
#endif
if (mod.info == NULL) {
spdlog::error("{0} is missing _INFO_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.init == NULL) {
spdlog::error("{0} is missing _INIT_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.createInstance == NULL) {
spdlog::error("{0} is missing _CREATE_INSTANCE_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.deleteInstance == NULL) {
spdlog::error("{0} is missing _DELETE_INSTANCE_ symbol", path);
mod.handle = NULL;
return mod;
}
if (mod.end == NULL) {
spdlog::error("{0} is missing _END_ symbol", path);
mod.handle = NULL;
return mod;
}
if (modules.find(mod.info->name) != modules.end()) {
spdlog::error("{0} has the same name as an already loaded module", path);
mod.handle = NULL;
return mod;
}
for (auto const& [name, _mod] : modules) {
if (mod.handle == _mod.handle) {
return _mod;
}
}
mod.init();
modules[mod.info->name] = mod;
return mod;
}
void ModuleManager::createInstance(std::string name, std::string module) {
if (modules.find(module) == modules.end()) {
spdlog::error("Module '{0}' doesn't exist", module);
return;
}
if (instances.find(name) != instances.end()) {
spdlog::error("A module instance with the name '{0}' already exists", name);
return;
}
int maxCount = modules[module].info->maxInstances;
if (countModuleInstances(module) >= maxCount && maxCount > 0) {
spdlog::error("Maximum number of instances reached for '{0}'", module);
return;
}
Instance_t inst;
inst.module = modules[module];
inst.instance = inst.module.createInstance(name);
instances[name] = inst;
}
void ModuleManager::deleteInstance(std::string name) {
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
}
void ModuleManager::deleteInstance(ModuleManager::Instance* instance) {
spdlog::error("DELETE INSTANCE NOT IMPLEMENTED");
}
void ModuleManager::enableInstance(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot enable '{0}', instance doesn't exist", name);
return;
}
instances[name].instance->enable();
}
void ModuleManager::disableInstance(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot disable '{0}', instance doesn't exist", name);
return;
}
instances[name].instance->disable();
}
bool ModuleManager::instanceEnabled(std::string name) {
if (instances.find(name) == instances.end()) {
spdlog::error("Cannot check if '{0}' is enabled, instance doesn't exist", name);
return false;
}
return instances[name].instance->isEnabled();
}
int ModuleManager::countModuleInstances(std::string module) {
if (modules.find(module) == modules.end()) {
spdlog::error("Cannot count instances of '{0}', Module doesn't exist", module);
return -1;
}
ModuleManager::Module_t mod = modules[module];
int count = 0;
for (auto const& [name, instance] : instances) {
if (instance.module == mod) { count++; }
}
return count;
}

91
core/src/module.h Normal file
View File

@ -0,0 +1,91 @@
#pragma once
#include <string>
#include <map>
#include <json.hpp>
#ifdef _WIN32
#ifdef SDRPP_IS_CORE
#define SDRPP_EXPORT extern "C" __declspec(dllexport)
#else
#define SDRPP_EXPORT extern "C" __declspec(dllimport)
#endif
#else
#define SDRPP_EXPORT extern
#endif
#ifdef _WIN32
#include <Windows.h>
#define MOD_EXPORT extern "C" __declspec(dllexport)
#define SDRPP_MOD_EXTENTSION ".dll"
#else
#include <dlfcn.h>
#define MOD_EXPORT extern "C"
#define SDRPP_MOD_EXTENTSION ".so"
#endif
class ModuleManager {
public:
struct ModuleInfo_t {
const char* name;
const char* description;
const char* author;
const int versionMajor;
const int versionMinor;
const int versionBuild;
const int maxInstances;
};
class Instance {
public:
virtual void enable() = 0;
virtual void disable() = 0;
virtual bool isEnabled() = 0;
};
struct Module_t {
#ifdef _WIN32
HMODULE handle;
#else
void* handle;
#endif
ModuleManager::ModuleInfo_t* info;
void (*init)();
ModuleManager::Instance* (*createInstance)(std::string name);
void (*deleteInstance)(ModuleManager::Instance* instance);
void (*end)();
friend bool operator==(const Module_t& a, const Module_t& b) {
if (a.handle != b.handle) { return false; }
if (a.info != b.info) { return false; }
if (a.init != b.init) { return false; }
if (a.createInstance != b.createInstance) { return false; }
if (a.deleteInstance != b.deleteInstance) { return false; }
if (a.end != b.end) { return false; }
return true;
}
};
struct Instance_t {
ModuleManager::Module_t module;
ModuleManager::Instance* instance;
};
ModuleManager::Module_t loadModule(std::string path);
void createInstance(std::string name, std::string module);
void deleteInstance(std::string name);
void deleteInstance(ModuleManager::Instance* instance);
void enableInstance(std::string name);
void disableInstance(std::string name);
bool instanceEnabled(std::string name);
int countModuleInstances(std::string module);
private:
std::map<std::string, ModuleManager::Module_t> modules;
std::map<std::string, ModuleManager::Instance_t> instances;
};
#define SDRPP_MOD_INFO MOD_EXPORT const ModuleManager::ModuleInfo_t _INFO_

31
core/src/options.cpp Normal file
View File

@ -0,0 +1,31 @@
#include <options.h>
#include <spdlog/spdlog.h>
#include <stdlib.h>
namespace options {
CMDLineOptions opts;
void loadDefaults() {
#ifdef _WIN32
opts.root = ".";
#else
std::string homedir = getenv("HOME");
opts.root = homedir + "/.config/sdrpp";
#endif
}
bool parse(int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
char* arg = argv[i];
if (!strcmp(arg, "-r") || !strcmp(arg, "--root")) {
if (i == argc - 1) { return false; }
opts.root = argv[++i];
}
else {
spdlog::error("Invalid command line option: {0}", arg);
return false;
}
}
return true;
}
}

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