256 Commits

Author SHA1 Message Date
40f1ee5651 Merge pull request #1385 from AlexandreRouma/master
Keep new_sinks branch up to date
2024-04-18 03:14:40 +02:00
2813aa7c93 fix some tables not scaled along with the rest of the UI as described in #1382 2024-04-17 01:31:49 +02:00
c61fc400a6 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-04-17 01:20:25 +02:00
779ef7ecf1 fix memcpy erroneously used on overlapping buffer regions as described in #1379 2024-04-17 01:20:17 +02:00
6fdab5e0c2 undo problematic commit 2024-04-15 06:23:39 +02:00
ea08fac32e handle deprecation of rotator in volk 3.1.0 2024-04-14 03:41:00 +02:00
632a4eebab Deprecated SoapySDR support 2024-04-10 22:09:07 +02:00
e118598f57 Fix waterfall size related crash described in #1230 2024-04-10 18:28:59 +02:00
a2d49b2f87 update development status of various modules in the readme 2024-04-08 17:17:16 +02:00
38abfc715e potential fix for #677 2024-04-08 17:01:27 +02:00
07eebd7018 potential fix for #677 2024-04-08 16:59:05 +02:00
d12021fc2f potential fix for #1361 2024-04-08 16:37:03 +02:00
db1682a2ac fix #1034 2024-04-08 16:21:33 +02:00
fdfb1dbf5e remove useless menu item in pager decoder 2024-04-05 20:15:21 +02:00
17f698577f disable FLEX protocol from pager module since it's not implemented and add setBaudrate function 2024-04-05 19:50:14 +02:00
8eaa987d90 fix spectran http source samplerate detection 2024-04-04 19:42:58 +02:00
12f7efed32 Switch spectran http source module to use the remoteconfig endpoint as per #1354 2024-04-04 19:25:02 +02:00
065a5b4c40 add audio source to readme 2024-04-01 19:56:36 +02:00
70f90fd570 MacOS CI workaround (I hate you microsoft) 2024-03-31 23:27:07 +02:00
a2054ad780 fix #1367 2024-03-31 23:00:50 +02:00
e1c48e9a1f try to fix macos CI again (fix your shit microsoft...) 2024-03-26 00:30:31 +01:00
867a8680e1 another MacOS CI fix attempt (this is getting ridiculous, fix your shit github) 2024-03-22 19:14:17 +01:00
bf831e3a50 attempt to fix the MacOS CI yet again 2024-03-22 18:48:27 +01:00
eb8b852ea6 fix macos build 2024-03-22 02:06:11 +01:00
67520ea45e fix linux CI 2024-03-20 01:58:16 +01:00
a3f0ad238a Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-03-20 01:38:44 +01:00
feb9789896 Update sdrplay api version for nightlies to 3.14.0 2024-03-20 01:38:36 +01:00
3a5096092d Merge pull request #1338 from LEDFlighter/master
Update germany.json
2024-02-24 22:33:57 +01:00
9dc0196a16 Update germany.json
Added the Name after a ","
recovered the "author_url" from the original author (I don't have an URL to refer to)
2024-02-24 12:56:33 +01:00
b1603f0e72 Update germany.json
Updated and extended the German bandplan
2024-02-23 23:26:36 +01:00
09467439e3 Update germany.json
Updated and extended the German bandplan
2024-02-23 23:21:42 +01:00
021928bbda fix show/hide waterfall keybind not working with the Display menu hidden 2024-02-21 21:59:55 +01:00
7c933d5103 fix typo mentioned in #174 2024-02-21 18:47:50 +01:00
a987c112a3 fix crash when USRP has two frontends named the same 2024-02-17 17:37:37 +01:00
f1339f08cf add workaround for uhd bug reporting same device twice 2024-02-17 06:30:06 +01:00
650a61930c fix SDR++ server crash at high samplerates #1326 2024-02-13 18:39:11 +01:00
61ffb3e6bf revert pager decoder to traditional clock recovery 2024-02-13 16:27:54 +01:00
9ab3c97c44 don't include rc file on platforms other than windows 2024-02-13 16:18:25 +01:00
edc08ddc08 more progress on the network source 2024-02-13 16:17:17 +01:00
95052c34ff more work on network source and syntax cleanup in iq exporter 2024-02-13 03:11:37 +01:00
34171d4edc fix windows CI 2024-02-12 23:24:00 +01:00
726e1069bf fix wrong mode name in rigctl server as described in #1327 2024-02-12 22:46:03 +01:00
61c14bab48 fix iq_exporter module crashing when removed in the case it's disabled and in baseband mode 2024-02-12 22:43:16 +01:00
01ab1831e8 more progress on the network source 2024-02-12 22:07:17 +01:00
2b752bb267 disable M17 decoder on M1 CI 2024-02-11 19:57:13 +01:00
5204cfec56 disable perseus source on macos M1 2024-02-11 19:41:49 +01:00
c616892eda attempt to add MacOS M1 CI 2024-02-11 19:36:26 +01:00
5f23c1f312 added new patrons and hardware donors 2024-02-10 20:59:37 +01:00
5e0c4449f8 switched back ATV demod to black and white 2024-02-09 22:13:25 +01:00
cd3e2b6c05 fix network source build on windows 2024-02-08 21:45:58 +01:00
ba5380f9bb started work on the network source 2024-02-08 15:01:11 +01:00
daf0f8c159 more work on new clock recovery 2024-02-08 14:17:35 +01:00
63aa45de9e beginning of new pager clock recovery 2024-02-08 09:03:46 +01:00
c0a84f8703 pocsag menu cleanup 2024-02-06 22:16:21 +01:00
f66f2c25e1 improve pocsag alpha decoding 2024-02-05 16:46:08 +01:00
bddfe5396f fix iq exporter config name 2024-02-03 01:43:16 +01:00
d5fa76df06 removed archived decoder from readme 2024-02-02 23:15:58 +01:00
8029cef4da promote iq_exporter module to Beta status 2024-02-02 23:12:44 +01:00
d84bb9bdec fix module not enabled 2024-02-02 23:07:13 +01:00
a0ff745b63 fix windows CI missing module 2024-02-02 23:06:03 +01:00
a08d2a0f85 more work on debugging the pager decoder 2024-02-02 22:52:19 +01:00
c4b0cb37a6 Merge pull request #1315 from AlexandreRouma/master
keep new_sinks branch up to date
2024-02-02 14:51:34 +01:00
7ab743d05b finish iq exporter and fix network lib send not closing socket on error 2024-02-02 04:11:29 +01:00
122e67ef65 finished VFO mode of the iq exporter 2024-02-01 21:38:13 +01:00
fbeb2195da fix make_windows_package.ps1 issue 2024-02-01 18:54:18 +01:00
1f2b50c9bb add beginning of IQ exporter module 2024-02-01 18:36:25 +01:00
f486c657c1 fix cmake to prevent always enabling the pager decoder 2024-02-01 01:12:51 +01:00
f1f04d59fe add missing files 2024-02-01 00:55:36 +01:00
ef42ea01d8 add flex decoder menu entry and fix pocsag decoding 2024-02-01 00:55:17 +01:00
3fc893568a beginning of pager decoder 2024-01-31 23:34:40 +01:00
4b6835141e fix low PI RDS callsign decoding 2024-01-30 22:18:18 +01:00
cd7dabda51 convert network sink to new stream system 2024-01-30 22:04:36 +01:00
139f63ad25 limit stream configuration to creator 2024-01-30 20:28:32 +01:00
75800e0ca2 fix merge issue 2024-01-30 18:09:59 +01:00
84da183559 Merge pull request #1310 from AlexandreRouma/master
New sink/stream system
2024-01-30 18:08:41 +01:00
0144d8e8ce resolve conflict in radio 2024-01-30 18:08:17 +01:00
7ef88f23a8 resolve conflict in audio_sink 2 2024-01-30 18:06:31 +01:00
c934e41a61 resolve conflict in audio_sink 2024-01-30 18:05:28 +01:00
9dd6c8546d resolve conflict in spectran_http_client.cpp 2024-01-30 18:02:16 +01:00
a9e59bdf3c removed useless logging again 2024-01-30 00:01:10 +01:00
f0bd17f9f4 fix north americal RDS callsign decoding 2024-01-29 23:57:23 +01:00
a8ed213ed3 remove useless debug logging 2024-01-29 21:49:13 +01:00
f8183739f7 add rds region selection 2024-01-29 21:28:43 +01:00
120745de19 add rds program type name decoding 2024-01-29 21:00:23 +01:00
05ab17add3 Merge pull request #1307 from AlexandreRouma/new_rds
New rds demod and decode
2024-01-29 19:43:06 +01:00
2ef8ee3629 disable rds symbol diagram data stream when not visible 2024-01-29 19:15:45 +01:00
14cb839863 clean up rds code and fix use before init 2024-01-29 18:43:46 +01:00
9501371c6c Merge pull request #1302 from AlexandreRouma/master
keep new_rds branch updated
2024-01-29 01:45:32 +01:00
ff23d7e43f fix warnings 2024-01-29 01:40:20 +01:00
f541328e5c Merge pull request #1301 from AlexandreRouma/new_plutosdr_enum
New plutosdr enum
2024-01-28 23:39:33 +01:00
be8edbfa9e revamp sdr++ server source networking code 2024-01-28 21:46:54 +01:00
11a7c382e8 update more github action versions 2024-01-28 17:28:07 +01:00
54276177ae update github action version 2024-01-28 17:25:27 +01:00
bc77bab45f improved plutosdr device naming 2024-01-28 17:23:18 +01:00
97d0a07ec7 fix plutosdr commit persistence 2024-01-28 15:26:34 +01:00
6b5de78e80 fix plutosdr source not updating samplerate on select 2024-01-28 14:39:01 +01:00
1cd8c2510a fix plutosdr source error checking 2024-01-28 14:38:36 +01:00
32cbd726fd implement enumeration and settings for plutosdr source 2024-01-28 02:16:20 +01:00
175992b081 add android workaround 2024-01-28 00:25:46 +01:00
31c9e5767e beginning of PlutoSDR context enumeration 2024-01-28 00:03:04 +01:00
e6a02a3944 fix bad plutosdr bandwidth selection at very high samplerates 2024-01-27 22:52:11 +01:00
00e6832055 add plutosdr bandwidth selection for #563 2024-01-27 22:49:39 +01:00
bc8baca190 clean up plutosdr source code 2024-01-27 21:35:13 +01:00
08e75b6d14 fix gain mode selection not always applying 2024-01-27 21:30:06 +01:00
e9ec79f6ef modernise gain mode selection for plutosdr 2024-01-27 21:29:44 +01:00
cd996292bc revamp plutosdr samplerate selection code 2024-01-27 21:12:26 +01:00
06b7ad5c98 remove old useless debug code from audio sink 2024-01-27 16:34:54 +01:00
38a95b4011 update SDRplay API in MacOS CI 2024-01-26 19:31:32 +01:00
f6052d913a bump version of updated modules 2024-01-26 19:00:36 +01:00
2b00370cf3 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 18:47:18 +01:00
09f4071803 Switch SDRplay API in Linux nightly builds to version 3.12 2024-01-26 18:47:13 +01:00
e61ef29e0f Merge pull request #1292 from daviderud/aligned_master
additions and corrections to NL band plan
2024-01-26 17:56:54 +01:00
eb36f86d41 added missing include 2024-01-26 17:21:51 +01:00
29889a289f Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 16:57:19 +01:00
859af77bd3 revamp RFspace source to use new networking library and fix buffering 2024-01-26 16:57:11 +01:00
0a3d1de02f removed tabs and unnecessary pipes 2024-01-26 07:43:01 +01:00
db3fbd2975 update issue template to reflect that fact that filling it out is not optional 2024-01-26 01:40:17 +01:00
f5adc7c587 fix hermes lite buffering 2024-01-25 23:28:03 +01:00
255988ee46 fix hermes lite enumeration issues and fix copy paste fail in network library 2024-01-25 22:27:59 +01:00
68bf2fc16f fix bad network lib broadcast implementation 2024-01-25 21:34:13 +01:00
3aa167701e fix networking library to allow multicast 2024-01-25 20:27:04 +01:00
97c1a132a5 move zoom waterfall's zoom function in the cpp file to avoid annoying recomps 2024-01-25 19:48:04 +01:00
118e56897c Add ubuntu mantic CI build 2024-01-25 15:41:23 +01:00
af8c085d43 switch osx to macos 2024-01-24 18:14:29 +01:00
708f74e179 additions and corrections 2024-01-24 06:14:00 +01:00
eab4264604 Merge pull request #1291 from AlexandreRouma/citest
Allow MacOS build to run on Catalina and newer
2024-01-22 11:32:25 -08:00
4b77d8c395 prepare for merge back part 2, electric boogaloo 2024-01-22 20:31:18 +01:00
854ed89b82 prepare for merge back 2024-01-22 20:30:53 +01:00
27ab5bf3c1 Fix exceptions referenced in #1287 2024-01-22 19:46:01 +01:00
159f59b858 fix return warning for #1234 2024-01-22 19:34:56 +01:00
93cafe7109 fix typo referenced in #1083 2024-01-22 19:22:26 +01:00
74ae8a45d9 fix incorrect formatting again for #1288 2024-01-22 18:17:23 +01:00
691216a298 fix incorrect formatting #1288 this time in the right branch... 2024-01-22 17:31:09 +01:00
191f652fc3 fix incorrect formatting #1288 2024-01-22 17:05:50 +01:00
3e58d4ba31 fix missing return statement 2024-01-22 02:10:48 +01:00
86dcec7495 Merge pull request #1284 from hzeller/20240121-adapt-rtaudio
Make compatible with rtaudio API 5 and 6.
2024-01-21 16:31:31 -08:00
5a003e99d2 Address review comments. 2024-01-21 15:36:41 -08:00
f197cf6bd9 Fix styling in the other file 2024-01-21 23:49:23 +01:00
8cefeadbd4 Fix styling, modern computers can display more than 40 columns 2024-01-21 23:48:04 +01:00
23ae66151b Make compatible with rtaudio API 5 and 6.
Recent rtaudio changed the API to not throw exceptions anymore and
also have DeviceIDs not queried by index but IDs that are provided
separately ( https://github.com/thestk/rtaudio/releases ).

Adapt the code-base to be compatible with the old and the new API
as we have to exepect that in a transition period both APIs are
common on various build platforms.
2024-01-21 14:37:30 -08:00
fa76b4e865 Made the contributing policy more clear 2024-01-21 20:19:46 +01:00
5e195a0d43 testing something for macos (do not use this CI build) 2024-01-19 20:53:25 +01:00
5e299d9d23 testing something silly 2 2024-01-19 20:01:05 +01:00
fd5813df6d testing something silly 2024-01-19 19:43:31 +01:00
eabb842b6b switched MacOS CI to debug mode 2024-01-19 19:11:01 +01:00
5a1945f779 allow running code on older macos version (test) 2024-01-17 07:07:24 +01:00
052167962d fix the rtlsdr source on android again 2024-01-17 06:52:08 +01:00
6fc41a81a7 fix build on android due to new librtlsdr function name 2024-01-17 06:41:25 +01:00
e710b6c6dc Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-17 06:29:41 +01:00
a91434c5fe fix windows CI (hopefully) and rebuild with Android BlogV4 support 2024-01-17 06:28:16 +01:00
193580caf3 Merge pull request #1270 from AlexandreRouma/master
merge
2024-01-08 06:18:00 +01:00
ead2ac6128 Merge pull request #1267 from clouserw/readme-fix
add additional library to debian install instructions
2024-01-05 00:20:29 +01:00
505cbb0ba2 add additional library to debian install instructions 2024-01-04 14:01:00 -08:00
b1030cbdfb Implement #1260 2023-12-27 03:53:43 +01:00
f3c5b2c31f Fixed #1259 2023-12-26 23:10:44 +01:00
e208511bde more work 2023-12-16 19:57:48 +01:00
348bf75281 Merge pull request #1252 from AlexandreRouma/master
Merging back modern stuff into the new sinks branch
2023-12-14 20:54:04 +01:00
2432390600 Completely redid the RDS demod 2023-12-13 23:25:46 +01:00
794d6ff5ac clean up 2023-10-29 02:40:34 +01:00
4803271115 fix #1212 2023-10-29 02:27:19 +01:00
78daed7879 Implemented #1205 2023-10-11 21:50:23 +02:00
5f297b1a69 undid the oopsie 2023-10-10 15:21:55 +02:00
7c5d4226eb removed MacOS CI since it required payment (ew...) 2023-10-09 22:43:39 +02:00
ec086ebbdf attempt at fixing M1 ci 2023-10-09 22:42:07 +02:00
d10d420467 added MacOS M1 CI 2023-10-02 23:57:06 +02:00
5bf989f49d fixed android build 2023-09-29 15:35:48 +02:00
4b3b6976d6 removed rpi install script since it is not to be used 2023-09-29 14:52:30 +02:00
55ddd383d2 Fix #1199 2023-09-29 14:42:45 +02:00
a043ab2dd3 Merge pull request #1185 from daviderud/master
Netherlands bandplan and readme correction
2023-09-14 09:58:58 +02:00
d270e1c5e8 Merge branch 'master' of https://github.com/daviderud/SDRPlusPlus 2023-09-14 05:53:25 +02:00
e41f24a95e first version 2023-09-14 05:50:53 +02:00
766b3db363 added clarification and correction 2023-09-14 05:43:00 +02:00
0632342bb7 fixed MacOS packaging 2023-09-13 05:14:07 +02:00
27b07ed0e8 Merge pull request #1163 from theverygaming/master
fixed buffer overflow in file source
2023-09-13 03:43:45 +02:00
a824c83848 Update main.cpp 2023-09-13 03:16:12 +02:00
0e50ee0e67 Update main.cpp 2023-09-13 02:51:44 +02:00
a55d1d9c06 Merge pull request #1179 from isorochan/master
Fix typo in fastAmplitude()
2023-09-07 20:05:24 +02:00
97187b790f Fix typo in fastAmplitude() 2023-09-07 17:27:34 +03:00
9c1361a8a9 Added radio mode info in the recorder and fixed horrible code in the rigctl_server module 2023-09-06 05:38:00 +02:00
4d0d14856b added support for RTL-SDR v4 on MacOS 2023-09-06 04:12:04 +02:00
365ab2325e MacOS CI fix 2023-09-06 03:39:12 +02:00
4da7e686f3 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-09-04 22:19:59 +02:00
c1d9ab64f8 fixed CI 2023-09-04 22:19:51 +02:00
ea33135bf1 Merge pull request #1174 from Bastiti/master
Add 8m - Amateur to the Belgian Bandplan
2023-09-04 15:20:54 +02:00
2081384905 Update belgium.json
https://www.ibpt.be/consommateurs/publication/decision-du-29-aout-2023-concernant-l-attribution-du-spectre-40.660-mhz---40.690-mhz-a-des-stations-de-radiocommunications-privees-dinstruction-individuelle-dintercommunication-technique-et-detudes-utilisees-par-des-radioamateurs
2023-09-04 14:47:13 +02:00
6b31134af2 do not play file when samplerate == 0 2023-08-27 13:28:21 +02:00
99d2786c25 Merge pull request #1169 from nmaster2042/master
Update france.json
2023-08-26 16:07:07 +02:00
320f4459ed Update france.json
Change France Band plan 11m band high limit to 27,410 Mhz, see https://www.legifrance.gouv.fr/loda/article_lc/LEGIARTI000006430657/2020-10-31
2023-08-26 13:21:30 +02:00
cbf1d6703e more work 2023-08-20 21:06:36 +02:00
de5816f79f Merge pull request #1160 from themeadery/usa-bandplan
Update usa.json bandplan with MURS frequencies
2023-08-07 07:40:49 +02:00
7b9c01ec73 fixed buffer overflow in file source 2023-08-06 04:02:51 +02:00
f06eccd97c Merge pull request #1161 from Manawyrm/manawyrm-bandplan
Update name in bandplan and contributors list
2023-08-05 00:03:25 +02:00
88baa8a48e Update name in bandplan and contributors list 2023-08-04 22:50:12 +02:00
b436fd0745 Update usa.json
Added MURS frequencies
2023-08-03 10:01:48 -07:00
6fa4299136 fixed bookworm build 2023-07-24 00:54:17 +02:00
220dcbcc76 added debian bookworm nightly 2023-07-24 00:05:25 +02:00
03c30f202e Merge pull request #1132 from AlexandreRouma/master
Merge back
2023-07-11 01:15:03 +02:00
15ad065feb Merge pull request #1131 from rmdlv/patch-1
Fixed russia.json
2023-07-11 01:14:04 +02:00
dddf84510e Fixed russia.json
Source - https://digital.gov.ru/ru/documents/3634/
2023-07-10 21:18:33 +08:00
acd9ad9781 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2023-07-10 04:41:41 +02:00
168e28cc44 added SNR meter smoothing 2023-07-10 04:41:34 +02:00
25bc9f60ed more work 2023-07-10 03:48:59 +02:00
3b9867c1d7 Merge pull request #1104 from jj1bdx/jj1bdx-rigctl-get_powerstat-fix
rigctl_server: Add answering code for rigctl \get_powerstat
2023-07-09 18:53:40 +02:00
2e80882ab5 Merge pull request #1130 from AlexandreRouma/master
Merging back master changes
2023-07-09 18:35:44 +02:00
4c584847de more work 2023-07-09 18:34:52 +02:00
2a741932e0 more or less finished the new stream system 2023-07-09 04:09:18 +02:00
ff7ef78b8f Merge pull request #1114 from bvernoux/master
Workaround for RtAudio Exception when no Input are connected on Input MIC on some PC
2023-07-02 18:00:46 +02:00
87add9ad83 Fix formatting 2023-07-02 17:03:52 +02:00
6cd09f9b60 Workaround for RtAudio Exception when no Input are connected on MIC IN on some computer
Issue reproduced with Windows11 Pro + ROG CROSSHAIR X670E HERO
Workaround for RtAudio just add try/catch and log the error but allow to start SDR++
2023-07-02 16:44:47 +02:00
8d05c1e181 bugfix 2023-06-30 01:36:28 +02:00
1c081cad78 added OQPSK mode to the meteor demodulator 2023-06-29 01:19:15 +02:00
aa929a1e79 Add answering code for rigctl \get_powerstat
* Answer "1" (Power is ON) to \get_powerstat inquiry
2023-06-25 13:51:51 +09:00
5acdab0d22 Instantiated the perseus source by default and perseus on raspberrypi 2023-06-10 21:48:02 +02:00
3e3846daa1 another fix 2023-06-10 21:17:17 +02:00
47617e1acd trying again, powershell belongs in the trash 2023-06-10 20:37:38 +02:00
1df51020aa hopefully last windows CI fix 2023-06-10 20:22:32 +02:00
aa1fd9e573 enabled perseus-sdr build on windows 2023-06-10 19:24:40 +02:00
21816fb438 maybe CI fix 2023-06-10 03:56:46 +02:00
3a06612ff5 fixed missing autoconf on macos 2023-05-24 03:09:35 +02:00
a53bf05ed3 and another one... 2023-05-24 02:55:37 +02:00
78c57db116 I hate autotools so much 2023-05-24 02:52:57 +02:00
c16281f68a hopefully last fix... 2023-05-24 02:49:22 +02:00
32c580ba57 another fix 2023-05-24 02:47:04 +02:00
664b5d85e2 fixed again 2023-05-24 02:43:37 +02:00
6bcb62bfb2 fixed ci 2023-05-24 02:40:23 +02:00
0e7c754b8b Enabled perseus build on all unix 2023-05-24 02:32:27 +02:00
71327cd695 fixed driver download link 2023-05-17 18:37:21 +02:00
f296730302 fixed recorder frequency bug with multivfo 2023-05-17 03:58:49 +02:00
ff655caf31 saving work 2023-05-17 03:55:47 +02:00
b89fdba433 Update united-kingdom.json 2023-05-02 21:15:02 +02:00
2c3b522787 Update contributing.md 2023-04-24 03:49:30 +02:00
528763d10e disabled spectran http sampleFreq autodetection 2023-04-22 00:27:31 +02:00
0277232bdb redesign 2023-04-21 22:44:45 +02:00
2e3e2a7dca Merge pull request #1049 from AlexandreRouma/master
Merging back changes
2023-04-20 09:36:52 +02:00
271a77e4ce More work on the stream system 2023-04-20 09:35:06 +02:00
582aeed640 Added samplerate and center frequency tracking to http spectran source 2023-04-20 08:32:31 +02:00
9c0b57a036 Work on the spectran HTTP source 2023-04-19 02:25:44 +02:00
604f95fd96 Added perseus source (still a bit buggy) 2023-04-14 22:12:13 +02:00
c892e51000 Added Archlinux disclaimer 2023-04-10 22:53:41 +02:00
48e9708d74 added disclaimer 2023-04-10 22:52:50 +02:00
3336ae4aa5 added rigctl client to the macos bundle 2023-04-03 20:50:11 +02:00
961cd3f133 beginning of documentation 2023-04-03 20:49:27 +02:00
2676190d3a Merge pull request #1035 from AlexandreRouma/master
merge back changes to master
2023-04-03 20:40:25 +02:00
8851735cb8 initial structure 2023-04-03 20:28:17 +02:00
190cea8e4e added missing include 2023-04-03 19:44:47 +02:00
13a268a3e1 Update module.cpp 2023-04-01 19:10:47 +02:00
800a8b22c7 fixed fft smoothing not set on startup when enabled 2023-03-31 01:38:14 +02:00
2eb030dd83 Added stdexcept 2023-03-30 19:55:54 +02:00
365fe1930c Update smgui.h 2023-03-30 19:17:15 +02:00
342a677c3f Merge pull request #1001 from konung-yaropolk/master
Added new colormaps: Vivid, Smoke and Temper
2023-03-30 08:41:58 +02:00
b5c41bcb3a added missing argument to macos instructions 2023-03-29 22:04:51 +02:00
f578adceef more work 2023-03-27 17:26:37 +02:00
1e01313612 Update vivid.json 2023-03-08 00:26:26 -06:00
e64c343645 Update vivid.json 2023-03-01 02:24:08 -06:00
kek
b9effce7d6 Added new colormap: Temper Colors 2023-02-26 05:19:34 -06:00
kek
45a13227de new file: root/res/colormaps/smoke.json
new file:   root/res/colormaps/vivid.json
2023-02-26 04:35:55 -06:00
144 changed files with 10897 additions and 1633 deletions

View File

@ -7,6 +7,8 @@ assignees: ''
---
# WARNING: Filling out the template below is NOT optional. Issues not filling out this template will be closed without review.
FIRST: Before reporting any bug, make sure that the bug you are reporting has not been reported before. Also, try to use the [nightly version](https://www.sdrpp.org/nightly) if possible in case I've already fixed the bug.
**Hardware**

View File

@ -1,7 +1,4 @@
# Important
Only minor bug fixes and bandplans are accepted.
Pull requests adding features or any bug fix that requires significant code changes will be automatically rejected.
Only bandplan, colormaps and themes are accepted. Code pull requests are **NOT welcome**.
Open an issue requesting a feature or discussing a possible bugfix instead.

View File

@ -18,7 +18,7 @@ jobs:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -34,13 +34,13 @@ jobs:
- name: Patch Pothos with earlier libusb version
working-directory: ${{runner.workspace}}
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/"
run: 7z x libusb.7z -olibusb_old ; rm "C:/Program Files/PothosSDR/bin/libusb-1.0.dll" ; cp "libusb_old/MS64/dll/libusb-1.0.dll" "C:/Program Files/PothosSDR/bin/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/"
- name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu" -OutFile ${{runner.workspace}}/SDRPlay.zip
run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
- name: Install SDRPlay API
run: 7z x ${{runner.workspace}}/SDRPlay.zip -o"C:/Program Files/"
run: 7z x ${{runner.workspace}}/SDRplay.zip -o"C:/Program Files/"
- name: Download codec2
run: git clone https://github.com/AlexandreRouma/codec2
@ -58,14 +58,17 @@ jobs:
run: mkdir "C:/Program Files/codec2" ; mkdir "C:/Program Files/codec2/include" ; mkdir "C:/Program Files/codec2/include/codec2" ; mkdir "C:/Program Files/codec2/lib" ; cd "codec2" ; xcopy "src" "C:/Program Files/codec2/include" ; cd "build" ; xcopy "src" "C:/Program Files/codec2/lib" ; xcopy "codec2" "C:/Program Files/codec2/include/codec2"
- name: Install vcpkg dependencies
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows
- name: Install rtaudio
run: git clone https://github.com/thestk/rtaudio ; cd rtaudio ; git checkout 2f2fca4502d506abc50f6d4473b2836d24cfb1e3 ; mkdir build ; cd build ; cmake .. ; cmake --build . --config Release ; cmake --install .
- name: Install libperseus-sdr
run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake "-DLIBUSB_LIBRARIES=C:/Program Files/PothosSDR/lib/libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIRS=C:/Program Files/PothosSDR/include/libusb-1.0" .. "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; mkdir "C:/Program Files/PothosSDR/include/perseus-sdr" ; cp Release/perseus-sdr.dll "C:/Program Files/PothosSDR/bin" ; cp Release/perseus-sdr.lib "C:/Program Files/PothosSDR/bin" ; cd .. ; xcopy "src" "C:/Program Files/PothosSDR/include/perseus-sdr"
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
run: cmake "$Env:GITHUB_WORKSPACE" "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
- name: Build
working-directory: ${{runner.workspace}}/build
@ -76,47 +79,47 @@ jobs:
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos:
runs-on: macos-11
build_macos_intel:
runs-on: macos-12
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Update brew repositories
run: brew update
- name: Fix stuff
run: rm -f /usr/local/bin/2to3* /usr/local/bin/idle3* /usr/local/bin/pydoc3* /usr/local/bin/python3* /usr/local/bin/python3-config* && brew reinstall gettext
- name: Install dependencies
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf rtl-sdr libbladerf codec2 zstd && pip3 install mako
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako
- name: Install volk
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install SDRplay API
run: wget https://www.sdrplay.com/software/SDRplay_RSP_API-MacOSX-3.07.3.pkg && sudo installer -pkg SDRplay_RSP_API-MacOSX-3.07.3.pkg -target /
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
- name: Install libiio
run: git clone https://github.com/analogdevicesinc/libiio && cd libiio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libperseus
run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
- name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_SOAPY_SOURCE=OFF -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
@ -127,16 +130,67 @@ jobs:
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_intel.zip SDR++.app
- name: Save Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_macos_intel
path: ${{runner.workspace}}/sdrpp_macos_intel.zip
build_macos_arm:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Install dependencies
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako --break-system-packages
- name: Install volk
run: git clone --recursive https://github.com/gnuradio/volk && cd volk && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install SDRplay API
run: wget https://www.sdrplay.com/software/SDRplayAPI-macos-installer-universal-3.14.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.14.0.pkg -target /
- name: Install libiio
run: wget https://github.com/analogdevicesinc/libiio/archive/refs/tags/v0.25.zip && 7z x v0.25.zip && cd libiio-0.25 && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libad9361
run: git clone https://github.com/analogdevicesinc/libad9361-iio && cd libad9361-iio && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite
run: git clone https://github.com/myriadrf/LimeSuite && cd LimeSuite && mkdir builddir && cd builddir && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
# - name: Install libperseus
# run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && make install && cd ..
- name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 $GITHUB_WORKSPACE -DOPT_BUILD_PLUTOSDR_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_AUDIO_SINK=OFF -DOPT_BUILD_PORTAUDIO_SINK=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=OFF -DOPT_BUILD_PERSEUS_SOURCE=OFF -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build
working-directory: ${{runner.workspace}}/build
run: make VERBOSE=1 -j3
- name: Create Archive
working-directory: ${{runner.workspace}}
run: cd $GITHUB_WORKSPACE && sh make_macos_bundle.sh ${{runner.workspace}}/build ./SDR++.app && zip -r ${{runner.workspace}}/sdrpp_macos_arm.zip SDR++.app
- name: Save Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_macos_arm
path: ${{runner.workspace}}/sdrpp_macos_arm.zip
build_debian_buster:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build
@ -149,7 +203,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_buster_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -158,7 +212,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
@ -171,16 +225,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bookworm_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
@ -193,38 +269,16 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
# build_ubuntu_bionic:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v3
# - name: Create Docker Image
# run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_bionic && docker build . --tag sdrpp_build
# - name: Run Container
# run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
# - name: Recover Deb Archive
# working-directory: ${{runner.workspace}}
# run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
# - name: Save Deb Archive
# uses: actions/upload-artifact@v3
# with:
# name: sdrpp_ubuntu_bionic_amd64
# path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
@ -237,7 +291,7 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
@ -246,7 +300,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
@ -259,23 +313,45 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_mantic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_mantic && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_mantic_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf:
runs-on: ARM
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Create Build Environment
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON
run: cmake $GITHUB_WORKSPACE -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
- name: Build
working-directory: ${{runner.workspace}}/build
@ -286,7 +362,7 @@ jobs:
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb
- name: Save Deb Archive
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_raspios_bullseye_armhf
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb
@ -295,7 +371,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Fetch container
working-directory: ${{runner.workspace}}
@ -313,33 +389,36 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
- name: Save APK
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: sdrpp_android
path: ${{runner.workspace}}/sdrpp.apk
create_full_archive:
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_raspios_bullseye_armhf', 'build_android']
needs: ['build_windows', 'build_macos_intel', 'build_macos_arm', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', 'build_debian_sid', 'build_ubuntu_focal', 'build_ubuntu_jammy', 'build_ubuntu_mantic', 'build_raspios_bullseye_armhf', 'build_android']
runs-on: ubuntu-latest
steps:
- name: Download All Builds
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Create Archive
run: >
mkdir sdrpp_all &&
mv sdrpp_windows_x64/sdrpp_windows_x64.zip sdrpp_all/ &&
mv sdrpp_macos_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_ubuntu_mantic_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_mantic_amd64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: sdrpp_all
path: sdrpp_all/
@ -351,7 +430,7 @@ jobs:
steps:
- name: Download All Builds
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
- name: Update Nightly
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
@ -360,7 +439,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install codespell
run: sudo apt update -y && sudo apt install -y codespell
@ -372,7 +451,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Run check_clang_format
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true

View File

@ -17,24 +17,26 @@ option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
option(OPT_BUILD_NETWORK_SOURCE "Build Network Source Module (no dependencies required)" ON)
option(OPT_BUILD_PERSEUS_SOURCE "Build Perseus Source Module (Dependencies: libperseus-sdr)" OFF)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON)
option(OPT_BUILD_RTL_TCP_SOURCE "Build RTL-TCP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPP_SERVER_SOURCE "Build SDR++ Server Source Module (no dependencies required)" ON)
option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Dependencies: libsdrplay)" OFF)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" ON)
option(OPT_BUILD_SOAPY_SOURCE "Build SoapySDR Source Module (Dependencies: soapysdr)" OFF)
option(OPT_BUILD_SPECTRAN_SOURCE "Build Spectran Source Module (Dependencies: Aaronia RTSA Suite)" OFF)
option(OPT_BUILD_SPECTRAN_HTTP_SOURCE "Build Spectran HTTP Source Module (no dependencies required)" ON)
option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
option(OPT_BUILD_USRP_SOURCE "Build USRP Source Module (libuhd)" OFF)
# Sinks
option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF)
option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON)
option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Dependencies: portaudio)" OFF)
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
# Decoders
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF)
@ -42,12 +44,14 @@ option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies:
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF)
option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON)
option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON)
option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON)
option(OPT_BUILD_FREQUENCY_MANAGER "Build the Frequency Manager module" ON)
option(OPT_BUILD_IQ_EXPORTER "Build the IQ Exporter module" ON)
option(OPT_BUILD_RECORDER "Audio and baseband recorder" ON)
option(OPT_BUILD_RIGCTL_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
@ -141,9 +145,17 @@ if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
if (OPT_BUILD_NETWORK_SOURCE)
add_subdirectory("source_modules/network_source")
endif (OPT_BUILD_NETWORK_SOURCE)
if (OPT_BUILD_PERSEUS_SOURCE)
add_subdirectory("source_modules/perseus_source")
endif (OPT_BUILD_PERSEUS_SOURCE)
if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("source_modules/plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_RFSPACE_SOURCE)
add_subdirectory("source_modules/rfspace_source")
@ -157,6 +169,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("source_modules/rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
if (OPT_BUILD_SDRPLAY_SOURCE)
add_subdirectory("source_modules/sdrplay_source")
endif (OPT_BUILD_SDRPLAY_SOURCE)
@ -177,10 +193,6 @@ if (OPT_BUILD_SPYSERVER_SOURCE)
add_subdirectory("source_modules/spyserver_source")
endif (OPT_BUILD_SPYSERVER_SOURCE)
if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("source_modules/plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_USRP_SOURCE)
add_subdirectory("source_modules/usrp_source")
endif (OPT_BUILD_USRP_SOURCE)
@ -229,6 +241,10 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
add_subdirectory("decoder_modules/meteor_demodulator")
endif (OPT_BUILD_METEOR_DEMODULATOR)
if (OPT_BUILD_PAGER_DECODER)
add_subdirectory("decoder_modules/pager_decoder")
endif (OPT_BUILD_PAGER_DECODER)
if (OPT_BUILD_RADIO)
add_subdirectory("decoder_modules/radio")
endif (OPT_BUILD_RADIO)
@ -247,6 +263,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
add_subdirectory("misc_modules/frequency_manager")
endif (OPT_BUILD_FREQUENCY_MANAGER)
if (OPT_BUILD_IQ_EXPORTER)
add_subdirectory("misc_modules/iq_exporter")
endif (OPT_BUILD_IQ_EXPORTER)
if (OPT_BUILD_RECORDER)
add_subdirectory("misc_modules/recorder")
endif (OPT_BUILD_RECORDER)
@ -267,7 +287,12 @@ if (OPT_BUILD_SCHEDULER)
add_subdirectory("misc_modules/scheduler")
endif (OPT_BUILD_SCHEDULER)
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
if (MSVC)
add_executable(sdrpp "src/main.cpp" "win32/resources.rc")
else ()
add_executable(sdrpp "src/main.cpp")
endif ()
target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Compiler arguments
@ -297,7 +322,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif ()
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON
# cmake .. "-DCMAKE_TOOLCHAIN_FILE=C:/dev/vcpkg/scripts/buildsystems/vcpkg.cmake" -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_SCANNER=ON -DOPT_BUILD_SCHEDULER=ON -DOPT_BUILD_USRP_SOURCE=ON -DOPT_BUILD_PAGER_DECODER=ON
# Create module cmake file
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)

View File

@ -1,64 +1,6 @@
# Pull Requests
TODO
# Code Style
## Naming Convention
- Files: `snake_case.h` `snake_case.cpp`
- Namespaces: `CamelCase`
- Classes: `CamelCase`
- Structs: `CamelCase_t`
- Members: `camelCase`
- Enum: `SNAKE_CASE`
- Macros: `SNAKE_CASE`
## Brace Style
```c++
int myFunction() {
if (shortIf) { shortFunctionName(); }
if (longIf) {
longFunction();
otherStuff();
myLongFunction();
}
}
```
Note: If it makes the code cleaner, remember to use the `?` keyword instead of a `if else` statement.
## Pointers
Please use `type* name` for pointers.
## Structure
Headers and their associated C++ files shall be in the same directory. All headers must use `#pragma once` instead of other include guards. Only include files in a header that are being used in that header. Include the rest in the associated C++ file.
# Modules
## Module Naming Convention
All modules names must be `snake_case`. If the module is a source, it must end with `_source`. If it is a sink, it must end with `_sink`.
For example, lets take the module named `cool_source`:
- Directory: `cool_source`
- Class: `CoolSourceModule`
- Binary: `cool_source.<os dynlib extension>`
## Integration into main repository
If the module meets the code quality requirements, it may be added to the official repository. A module that doesn't require any external dependencies that the core doesn't already use may be enabled for build by default. Otherwise, they must be disabled for build by default with a `OPT_BUILD_MODULE_NAME` variable set to `OFF`.
# JSON Formatting
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**
Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
## Band Frequency Allocation
@ -118,8 +60,8 @@ Please follow this guide to properly format the JSON files for custom color maps
}
```
# Best Practices
# JSON Formatting
* All additions and/or bug fixes to the core must not add additional dependencies.
* Use VSCode for development, VS seems to cause issues.
* DO NOT use libboost for any code meant for this repository
The ability to add new radio band allocation identifiers and color maps relies on JSON files. Proper formatting of these JSON files is important for reference and readability. The following guides will show you how to properly format the JSON files for their respective uses.
**IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**

View File

@ -108,7 +108,6 @@ elseif (ANDROID)
)
target_link_libraries(sdrpp_core PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcpu_features.a
/sdr-kit/${ANDROID_ABI}/lib/libvolk.so
/sdr-kit/${ANDROID_ABI}/lib/libfftw3f.so
/sdr-kit/${ANDROID_ABI}/lib/libzstd.so

View File

@ -45,7 +45,7 @@ uint8_t *history_buffer_get_slice(history_buffer *buf) { return buf->history[buf
shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
unsigned int search_every) {
shift_register_t bestpath;
shift_register_t bestpath = 0;
distance_t leasterror = USHRT_MAX;
// search for a state with the least error
for (shift_register_t state = 0; state < buf->num_states; state += search_every) {

View File

@ -88,7 +88,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try {
carg.ival = std::stoi(arg);
}
catch (std::exception e) {
catch (const std::exception& e) {
printf("Invalid argument, failed to parse integer\n");
showHelp();
return -1;
@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try {
carg.fval = std::stod(arg);
}
catch (std::exception e) {
catch (const std::exception& e) {
printf("Invalid argument, failed to parse float\n");
showHelp();
return -1;

View File

@ -36,8 +36,8 @@ void ConfigManager::load(json def, bool lock) {
file >> conf;
file.close();
}
catch (std::exception e) {
flog::error("Config file '{0}' is corrupted, resetting it", path);
catch (const std::exception& e) {
flog::error("Config file '{}' is corrupted, resetting it: {}", path, e.what());
conf = def;
save(false);
}

View File

@ -117,6 +117,10 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["colorMap"] = "Classic";
defConfig["fftHold"] = false;
defConfig["fftHoldSpeed"] = 60;
defConfig["fftSmoothing"] = false;
defConfig["fftSmoothingSpeed"] = 100;
defConfig["snrSmoothing"] = false;
defConfig["snrSmoothingSpeed"] = 20;
defConfig["fastFFT"] = false;
defConfig["fftHeight"] = 300;
defConfig["fftRate"] = 20;
@ -177,6 +181,8 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
@ -187,8 +193,6 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source";
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;

View File

@ -12,6 +12,7 @@ namespace sdrpp_credits {
"Howard0su",
"John Donkersley",
"Joshua Kimsey",
"Manawyrm",
"Martin Hauke",
"Marvin Sinister",
"Maxime Biette",
@ -21,7 +22,6 @@ namespace sdrpp_credits {
"Shuyuan Liu",
"Syne Ardwin (WI9SYN)",
"Szymon Zakrent",
"Tobias Mädel",
"Youssef Touil",
"Zimm"
};
@ -41,6 +41,7 @@ namespace sdrpp_credits {
"CaribouLabs",
"Ettus Research",
"Howard Su",
"MicroPhase",
"MyriadRF",
"Nuand",
"RFspace",
@ -54,6 +55,7 @@ namespace sdrpp_credits {
"Croccydile",
"Dale L Puckett (K0HYD)",
"Daniele D'Agnelli",
"David Taylor (GM8ARV)",
"D. Jones",
"Dexruus",
"EB3FRN",
@ -81,6 +83,7 @@ namespace sdrpp_credits {
"Syne Ardwin (WI9SYN)",
"W4IPA",
"William Arcand (W1WRA)",
"William Pitchford",
"Yves Rougy",
"Zipper"
};

View File

@ -93,7 +93,7 @@ namespace dsp {
void disableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain
if (!blockExists(block)) {
throw std::runtime_error("[chain] Tried to enable a block that isn't part of the chain");
throw std::runtime_error("[chain] Tried to disable a block that isn't part of the chain");
}
// If already disabled, don't do anything
@ -163,10 +163,12 @@ namespace dsp {
private:
Processor<T, T>* blockBefore(Processor<T, T>* block) {
// TODO: This is wrong and must be fixed when I get more time
for (auto& ln : links) {
if (ln == block) { return NULL; }
if (states[ln]) { return ln; }
}
return NULL;
}
Processor<T, T>* blockAfter(Processor<T, T>* block) {

View File

@ -41,7 +41,11 @@ namespace dsp::channel {
}
inline int process(int count, const complex_t* in, complex_t* out) {
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, &phaseDelta, &phase, count);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
#endif
return count;
}

View File

@ -12,6 +12,10 @@ namespace dsp::compression {
void init(stream<complex_t>* in, PCMType pcmType) {
_pcmType = pcmType;
// Set the output buffer size to the max size of a complex buffer + 8 bytes for the header
out.setBufferSize(STREAM_BUFFER_SIZE*sizeof(complex_t) + 8);
base_type::init(in);
}

View File

@ -9,7 +9,7 @@ namespace dsp::convert {
StereoToMono(stream<stereo_t>* in) { base_type::init(in); }
static inline int process(int count, const stereo_t* in, float* out) {
inline int process(int count, const stereo_t* in, float* out) {
for (int i = 0; i < count; i++) {
out[i] = (in[i].l + in[i].r) / 2.0f;
}

View File

@ -49,6 +49,7 @@ namespace dsp::demod {
audioFirTaps = taps::lowPass(15000.0, 4000.0, _samplerate);
alFir.init(NULL, audioFirTaps);
arFir.init(NULL, audioFirTaps);
xlator.init(NULL, -57000.0, samplerate);
rdsResamp.init(NULL, samplerate, 5000.0);
lmr = buffer::alloc<float>(STREAM_BUFFER_SIZE);
@ -56,9 +57,9 @@ namespace dsp::demod {
r = buffer::alloc<float>(STREAM_BUFFER_SIZE);
lprDelay.out.free();
lmrDelay.out.free();
arFir.out.free();
alFir.out.free();
xlator.out.free();
rdsResamp.out.free();
base_type::init(in);
@ -92,6 +93,7 @@ namespace dsp::demod {
alFir.setTaps(audioFirTaps);
arFir.setTaps(audioFirTaps);
xlator.setOffset(-57000.0, samplerate);
rdsResamp.setInSamplerate(samplerate);
reset();
@ -139,7 +141,7 @@ namespace dsp::demod {
base_type::tempStart();
}
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, float* rdsout = NULL) {
inline int process(int count, complex_t* in, stereo_t* out, int& rdsOutCount, complex_t* rdsout = NULL) {
// Demodulate
demod.process(count, in, demod.out.writeBuf);
if (_stereo) {
@ -152,24 +154,24 @@ namespace dsp::demod {
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, lmrDelay.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, lmrDelay.out.writeBuf, pilotPLL.out.writeBuf, lmrDelay.out.writeBuf);
// Do RDS demod
if (_rdsOut) {
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Convert output back to real for further processing
convert::ComplexToReal::process(count, rtoc.out.writeBuf, lmr);
convert::ComplexToReal::process(count, lmrDelay.out.writeBuf, lmr);
// Amplify by 2x
volk_32f_s32f_multiply_32f(lmr, lmr, 2.0f, count);
@ -193,24 +195,11 @@ namespace dsp::demod {
// Convert to complex
rtoc.process(count, demod.out.writeBuf, rtoc.out.writeBuf);
// Filter out pilot and run through PLL
pilotFir.process(count, rtoc.out.writeBuf, pilotFir.out.writeBuf);
pilotPLL.process(count, pilotFir.out.writeBuf, pilotPLL.out.writeBuf);
// Translate to 0Hz
xlator.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// Delay
lprDelay.process(count, demod.out.writeBuf, demod.out.writeBuf);
lmrDelay.process(count, rtoc.out.writeBuf, rtoc.out.writeBuf);
// conjugate PLL output to down convert twice the L-R signal
math::Conjugate::process(count, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, rtoc.out.writeBuf);
// Since the PLL output is no longer needed after this, use it as the output
math::Multiply<dsp::complex_t>::process(count, rtoc.out.writeBuf, pilotPLL.out.writeBuf, pilotPLL.out.writeBuf);
convert::ComplexToReal::process(count, pilotPLL.out.writeBuf, rdsout);
volk_32f_s32f_multiply_32f(rdsout, rdsout, 100.0, count);
rdsOutCount = rdsResamp.process(count, rdsout, rdsout);
// Resample to the output samplerate
rdsOutCount = rdsResamp.process(count, rtoc.out.writeBuf, rdsout);
}
// Filter if needed
@ -240,7 +229,7 @@ namespace dsp::demod {
return count;
}
stream<float> rdsOut;
stream<complex_t> rdsOut;
protected:
double _deviation;
@ -253,13 +242,14 @@ namespace dsp::demod {
tap<complex_t> pilotFirTaps;
filter::FIR<complex_t, complex_t> pilotFir;
convert::RealToComplex rtoc;
channel::FrequencyXlator xlator;
loop::PLL pilotPLL;
math::Delay<float> lprDelay;
math::Delay<complex_t> lmrDelay;
tap<float> audioFirTaps;
filter::FIR<float, float> arFir;
filter::FIR<float, float> alFir;
multirate::RationalResampler<float> rdsResamp;
multirate::RationalResampler<dsp::complex_t> rdsResamp;
float* lmr;
float* l;

View File

@ -110,7 +110,7 @@ namespace dsp::demod {
else if (_mode == Mode::LSB) {
return -_bandwidth / 2.0;
}
else if (_mode == Mode::DSB) {
else {
return 0.0;
}
}

View File

@ -41,10 +41,10 @@ namespace dsp::filter {
// Move existing data to make transition seemless
if (_taps.size < oldTC) {
memcpy(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
memmove(buffer, &buffer[oldTC - _taps.size], (_taps.size - 1) * sizeof(D));
}
else if (_taps.size > oldTC) {
memcpy(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
memmove(&buffer[_taps.size - oldTC], buffer, (oldTC - 1) * sizeof(D));
buffer::clear<D>(buffer, _taps.size - oldTC);
}

View File

@ -65,6 +65,11 @@ namespace dsp::loop {
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
inline void advancePhase() {
phase += freq;
if constexpr(CLAMP_PHASE) { clampPhase(); }
}
T freq;
T phase;

View File

@ -1,183 +0,0 @@
#pragma once
#include "../processor.h"
#include "../window/nuttall.h"
#include <fftw3.h>
#include "../convert/stereo_to_mono.h"
namespace dsp::noise_reduction {
class Audio : public Processor<stereo_t, stereo_t> {
using base_type = Processor<stereo_t, stereo_t>;
public:
Audio() {}
Audio(stream<stereo_t>* in, int bins) { init(in, bins); }
~Audio() {
if (!base_type::_block_init) { return; }
base_type::stop();
destroyBuffers();
}
void init(stream<stereo_t>* in, int bins) {
_bins = bins;
complexBins = (bins / 2) + 1;
normFactor = 1.0f / (float)_bins;
initBuffers();
base_type::init(in);
}
void setBins(int bins) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_bins = bins;
complexBins = (bins / 2) + 1;
normFactor = 1.0f / (float)_bins;
destroyBuffers();
initBuffers();
base_type::tempStart();
}
void setLevel(float level) {
_level = powf(10.0f, level * 0.1f);
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
buffer::clear(buffer, _bins - 1);
buffer::clear(backFFTIn, _bins);
buffer::clear(noisePrint, _bins);
base_type::tempStart();
}
int process(int count, const stereo_t* in, stereo_t* out) {
// Write new input data to buffer
convert::StereoToMono::process(count, in, bufferStart);
// Iterate the FFT
for (int i = 0; i < count; i++) {
// Apply windows
volk_32f_x2_multiply_32f(forwFFTIn, &buffer[i], fftWin, _bins);
// Do forward FFT
fftwf_execute(forwardPlan);
// Get bin amplitude and square to get power
volk_32fc_magnitude_32f(ampBuf, (lv_32fc_t*)forwFFTOut, complexBins);
// Update noise print using a running average
volk_32f_s32f_multiply_32f(scaledAmps, ampBuf, alpha, complexBins);
volk_32f_s32f_multiply_32f(noisePrint, noisePrint, beta, complexBins);
volk_32f_x2_add_32f(noisePrint, noisePrint, scaledAmps, complexBins);
// Clamp amplitudes
volk_32f_x2_max_32f(ampBuf, ampBuf, noisePrint, complexBins);
// Compute Wiener (funny) filter
volk_32f_x2_subtract_32f(scaledAmps, ampBuf, noisePrint, complexBins);
volk_32f_x2_divide_32f(scaledAmps, scaledAmps, ampBuf, complexBins);
// Apply wiener filter to bins
volk_32fc_32f_multiply_32fc((lv_32fc_t*)backFFTIn, (lv_32fc_t*)forwFFTOut, scaledAmps, complexBins);
// Do reverse FFT and get first element
fftwf_execute(backwardPlan);
out[i].l = backFFTOut[_bins / 2];
out[i].r = backFFTOut[_bins / 2];
}
// Correct amplitude
volk_32f_s32f_multiply_32f((float*)out, (float*)out, normFactor, count*2);
// Move buffer buffer
memmove(buffer, &buffer[count], (_bins - 1) * sizeof(float));
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
// Swap if some data was generated
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
protected:
void initBuffers() {
// Allocate FFT buffers
forwFFTIn = (float*)fftwf_malloc(_bins * sizeof(float));
forwFFTOut = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
backFFTIn = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t));
backFFTOut = (float*)fftwf_malloc(_bins * sizeof(float));
// Allocate and clear delay buffer
buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + 64000);
bufferStart = &buffer[_bins - 1];
buffer::clear(buffer, _bins - 1);
// Clear backward FFT input
buffer::clear(backFFTIn, _bins);
// Allocate amplitude buffer
ampBuf = buffer::alloc<float>(_bins);
scaledAmps = buffer::alloc<float>(_bins);
noisePrint = buffer::alloc<float>(_bins);
buffer::clear(noisePrint, _bins);
// Allocate and generate Window
fftWin = buffer::alloc<float>(_bins);
for (int i = 0; i < _bins; i++) { fftWin[i] = window::nuttall(i, _bins - 1); }
// Plan FFTs
forwardPlan = fftwf_plan_dft_r2c_1d(_bins, forwFFTIn, (fftwf_complex*)forwFFTOut, FFTW_ESTIMATE);
backwardPlan = fftwf_plan_dft_c2r_1d(_bins, (fftwf_complex*)backFFTIn, backFFTOut, FFTW_ESTIMATE);
}
void destroyBuffers() {
fftwf_destroy_plan(forwardPlan);
fftwf_destroy_plan(backwardPlan);
fftwf_free(forwFFTIn);
fftwf_free(forwFFTOut);
fftwf_free(backFFTIn);
fftwf_free(backFFTOut);
buffer::free(buffer);
buffer::free(ampBuf);
buffer::free(scaledAmps);
buffer::free(noisePrint);
buffer::free(fftWin);
}
float _level = 0.0f;
float* forwFFTIn;
complex_t* forwFFTOut;
complex_t* backFFTIn;
float* backFFTOut;
fftwf_plan forwardPlan;
fftwf_plan backwardPlan;
float* buffer;
float* bufferStart;
float* fftWin;
float* ampBuf;
float* scaledAmps;
float* noisePrint;
int _bins;
int complexBins;
float normFactor = 1.0f;
float alpha = 0.0001f;
float beta = 0.9999f;
};
}

View File

@ -37,17 +37,21 @@ namespace dsp::noise_reduction {
inline int process(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
// Get signal amplitude and pass value if null
// Get signal amplitude
float inAmp = in[i].amplitude();
if (!inAmp) {
out[i] = in[i];
// Update average amplitude
float gain = 1.0f;
if (inAmp != 0.0f) {
amp = (amp * _invRate) + (inAmp * _rate);
float excess = inAmp / amp;
if (excess > _level) {
gain = 1.0f / excess;
}
}
// Update running average of amplitude
amp = (_rate*inAmp) + (_invRate*amp);
// Null out if spike (Note: ideally, it should try to guess the real data)
out[i] = (inAmp > _level*amp) ? complex_t{0.0f,0.0f} : in[i];
// Scale output by gain
out[i] = in[i] * gain;
}
return count;
}

View File

@ -82,7 +82,7 @@ namespace dsp {
inline float fastAmplitude() {
float re_abs = fabsf(re);
float im_abs = fabsf(re);
float im_abs = fabsf(im);
if (re_abs > im_abs) { return re_abs + 0.4f * im_abs; }
return im_abs + 0.4f * re_abs;
}

View File

@ -16,6 +16,7 @@ namespace icons {
ImTextureID UNMUTED;
ImTextureID NORMAL_TUNING;
ImTextureID CENTER_TUNING;
ImTextureID ALIGN_CENTER;
GLuint loadTexture(std::string path) {
int w, h, n;
@ -45,6 +46,7 @@ namespace icons {
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");
ALIGN_CENTER = (ImTextureID)(uintptr_t)loadTexture(resDir + "/icons/align_center.png");
return true;
}

View File

@ -13,6 +13,7 @@ namespace icons {
extern ImTextureID UNMUTED;
extern ImTextureID NORMAL_TUNING;
extern ImTextureID CENTER_TUNING;
extern ImTextureID ALIGN_CENTER;
GLuint loadTexture(std::string path);
bool load(std::string resDir);

View File

@ -17,6 +17,7 @@
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/sink.h>
#include <gui/menus/streams.h>
#include <gui/menus/vfo_color.h>
#include <gui/menus/module_manager.h>
#include <gui/menus/theme.h>
@ -72,6 +73,7 @@ void MainWindow::init() {
gui::menu.registerEntry("Source", sourcemenu::draw, NULL);
gui::menu.registerEntry("Sinks", sinkmenu::draw, NULL);
gui::menu.registerEntry("Streams", streamsmenu::draw, NULL);
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
gui::menu.registerEntry("Theme", thememenu::draw, NULL);
@ -165,6 +167,7 @@ void MainWindow::init() {
sourcemenu::init();
sinkmenu::init();
streamsmenu::init();
bandplanmenu::init();
displaymenu::init();
vfo_color_menu::init();
@ -433,6 +436,9 @@ void MainWindow::draw() {
showCredits = false;
}
// Reset waterfall lock
lockWaterfallControls = showCredits;
// Handle menu resize
ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos();
@ -463,9 +469,10 @@ void MainWindow::draw() {
}
}
// Process menu keybinds
displaymenu::checkKeybinds();
// Left Column
lockWaterfallControls = false;
if (showMenu) {
ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth);
@ -576,8 +583,20 @@ void MainWindow::draw() {
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
double nfreq;
if (vfo != NULL) {
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (vfo->snapInterval * wheel);
nfreq = roundl(nfreq / vfo->snapInterval) * vfo->snapInterval;
// Select factor depending on modifier keys
double interval;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
interval = vfo->snapInterval * 10.0;
}
else if (ImGui::IsKeyDown(ImGuiKey_LeftAlt)) {
interval = vfo->snapInterval * 0.1;
}
else {
interval = vfo->snapInterval;
}
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel);
nfreq = roundl(nfreq / interval) * interval;
}
else {
nfreq = gui::waterfall.getCenterFrequency() - (gui::waterfall.getViewBandwidth() * wheel / 20.0);

View File

@ -8,6 +8,7 @@
#include <signal_path/signal_path.h>
#include <gui/style.h>
#include <utils/optionlist.h>
#include <algorithm>
namespace displaymenu {
bool showWaterfall;
@ -22,6 +23,10 @@ namespace displaymenu {
bool restartRequired = false;
bool fftHold = false;
int fftHoldSpeed = 60;
bool fftSmoothing = false;
int fftSmoothingSpeed = 100;
bool snrSmoothing = false;
int snrSmoothingSpeed = 20;
OptionList<float, float> uiScales;
@ -57,8 +62,10 @@ namespace displaymenu {
IQFrontEnd::FFTWindow::NUTTALL
};
void updateFFTHoldSpeed() {
gui::waterfall.setFFTHoldSpeed(fftHoldSpeed / (fftRate * 10.0f));
void updateFFTSpeeds() {
gui::waterfall.setFFTHoldSpeed((float)fftHoldSpeed / ((float)fftRate * 10.0f));
gui::waterfall.setFFTSmoothingSpeed(std::min<float>((float)fftSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
gui::waterfall.setSNRSmoothingSpeed(std::min<float>((float)snrSmoothingSpeed / (float)(fftRate * 10.0f), 1.0f));
}
void init() {
@ -104,7 +111,13 @@ namespace displaymenu {
fftHold = core::configManager.conf["fftHold"];
fftHoldSpeed = core::configManager.conf["fftHoldSpeed"];
gui::waterfall.setFFTHold(fftHold);
updateFFTHoldSpeed();
fftSmoothing = core::configManager.conf["fftSmoothing"];
fftSmoothingSpeed = core::configManager.conf["fftSmoothingSpeed"];
gui::waterfall.setFFTSmoothing(fftSmoothing);
snrSmoothing = core::configManager.conf["snrSmoothing"];
snrSmoothingSpeed = core::configManager.conf["snrSmoothingSpeed"];
gui::waterfall.setSNRSmoothing(snrSmoothing);
updateFFTSpeeds();
// Define and load UI scales
uiScales.define(1.0f, "100%", 1.0f);
@ -114,15 +127,24 @@ namespace displaymenu {
uiScaleId = uiScales.valueId(style::uiScale);
}
void setWaterfallShown(bool shown) {
showWaterfall = shown;
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
}
void checkKeybinds() {
if (ImGui::IsKeyPressed(ImGuiKey_Home, false)) {
setWaterfallShown(!showWaterfall);
}
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvail().x;
bool homePressed = ImGui::IsKeyPressed(ImGuiKey_Home, false);
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall) || homePressed) {
if (homePressed) { showWaterfall = !showWaterfall; }
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true);
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall)) {
setWaterfallShown(showWaterfall);
}
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
@ -144,16 +166,47 @@ namespace displaymenu {
core::configManager.conf["fftHold"] = fftHold;
core::configManager.release(true);
}
ImGui::LeftLabel("FFT Hold Speed");
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_fft_hold_speed", &fftHoldSpeed)) {
updateFFTHoldSpeed();
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["fftHoldSpeed"] = fftHoldSpeed;
core::configManager.release(true);
}
if (ImGui::Checkbox("FFT Smoothing##_sdrpp", &fftSmoothing)) {
gui::waterfall.setFFTSmoothing(fftSmoothing);
core::configManager.acquire();
core::configManager.conf["fftSmoothing"] = fftSmoothing;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_fft_smoothing_speed", &fftSmoothingSpeed)) {
fftSmoothingSpeed = std::max<int>(fftSmoothingSpeed, 1);
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["fftSmoothingSpeed"] = fftSmoothingSpeed;
core::configManager.release(true);
}
if (ImGui::Checkbox("SNR Smoothing##_sdrpp", &snrSmoothing)) {
gui::waterfall.setSNRSmoothing(snrSmoothing);
core::configManager.acquire();
core::configManager.conf["snrSmoothing"] = snrSmoothing;
core::configManager.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt("##sdrpp_snr_smoothing_speed", &snrSmoothingSpeed)) {
snrSmoothingSpeed = std::max<int>(snrSmoothingSpeed, 1);
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["snrSmoothingSpeed"] = snrSmoothingSpeed;
core::configManager.release(true);
}
ImGui::LeftLabel("High-DPI Scaling");
ImGui::FillWidth();
if (ImGui::Combo("##sdrpp_ui_scale", &uiScaleId, uiScales.txt)) {
@ -168,7 +221,7 @@ namespace displaymenu {
if (ImGui::InputInt("##sdrpp_fft_rate", &fftRate, 1, 10)) {
fftRate = std::max<int>(1, fftRate);
sigpath::iqFrontEnd.setFFTRate(fftRate);
updateFFTHoldSpeed();
updateFFTSpeeds();
core::configManager.acquire();
core::configManager.conf["fftRate"] = fftRate;
core::configManager.release(true);

View File

@ -2,5 +2,6 @@
namespace displaymenu {
void init();
void checkKeybinds();
void draw(void* ctx);
}

View File

@ -39,7 +39,7 @@ namespace module_manager_menu {
ImVec2 btnSize = ImVec2(lheight, lheight - 1);
ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale);
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
if (ImGui::BeginTable("Module Manager Table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);

View File

@ -0,0 +1,177 @@
#include "streams.h"
#include <signal_path/signal_path.h>
#include <imgui.h>
#include <utils/flog.h>
#include <gui/style.h>
#include <gui/icons.h>
#include <utils/optionlist.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
namespace streamsmenu {
std::vector<SinkID> sinksToBeRemoved;
std::recursive_mutex sinkTypesMtx;
OptionList<std::string, std::string> sinkTypes;
std::map<std::string, int> selectedSinkTypeId;
std::map<std::string, int> addSinkTypeId;
int addType = 0;
void updateSinkTypeList(const std::string& removed = "") {
std::lock_guard<std::recursive_mutex> lck1(sinkTypesMtx);
auto lck2 = sigpath::streamManager.getSinkTypesLock();
const auto& types = sigpath::streamManager.getSinkTypes();
sinkTypes.clear();
for (const auto& type : types) {
if (type == removed) { continue; }
sinkTypes.define(type, type, type);
}
}
void onSinkProviderRegistered(const std::string& type) {
// Update the list
updateSinkTypeList();
// Update the ID of the Add dropdown
// TODO
// Update the selected ID of each drop down
// TODO
}
void onSinkProviderUnregister(const std::string& type) {
// Update the list
updateSinkTypeList(type);
// Update the ID of the Add dropdown
// TODO
// Update the selected ID of each drop down
// TODO
}
void init() {
sigpath::streamManager.onSinkProviderRegistered.bind(onSinkProviderRegistered);
sigpath::streamManager.onSinkProviderUnregister.bind(onSinkProviderUnregister);
updateSinkTypeList();
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvail().x;
auto lck = sigpath::streamManager.getStreamsLock();
const auto& streams = sigpath::streamManager.getStreams();
int count = 0;
int maxCount = streams.size();
for (auto& [name, stream] : streams) {
// Stream name
ImGui::SetCursorPosX((menuWidth / 2.0f) - (ImGui::CalcTextSize(name.c_str()).x / 2.0f));
ImGui::Text("%s", name.c_str());
// Display ever sink
if (ImGui::BeginTable(CONCAT("sdrpp_streams_tbl_", name), 1, ImGuiTableFlags_Borders)) {
auto lck2 = stream->getSinksLock();
auto sinks = stream->getSinks();
for (auto& [id, sink] : sinks) {
std::string sid = sink->getStringID();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
float tableWidth = ImGui::GetContentRegionAvail().x;
ImGui::Spacing();
// Sink type
int ttttt = 0;
ImGui::FillWidth();
if (ImGui::Combo(CONCAT("##sdrpp_streams_type_", sid), &ttttt, sinkTypes.txt)) {
}
sink->showMenu();
float vol = sink->getVolume();
bool muted = sink->getMuted();
float pan = sink->getPanning();
bool linked = true;
float height = ImGui::GetTextLineHeightWithSpacing() + 2;
ImGui::PushID(ImGui::GetID(("sdrpp_streams_center_btn_" + sid).c_str()));
if (ImGui::ImageButton(icons::ALIGN_CENTER, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
sink->setPanning(0.0f);
}
ImGui::PopID();
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_pan_", sid), &pan, -1, 1, "")) {
sink->setPanning(pan);
}
if (muted) {
ImGui::PushID(ImGui::GetID(("sdrpp_unmute_btn_" + sid).c_str()));
if (ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
sink->setMuted(false);
}
ImGui::PopID();
}
else {
ImGui::PushID(ImGui::GetID(("sdrpp_mute_btn_" + sid).c_str()));
if (ImGui::ImageButton(icons::UNMUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
sink->setMuted(true);
}
ImGui::PopID();
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::SliderFloat(CONCAT("##sdrpp_streams_vol_", sid), &vol, 0, 1, "")) {
sink->setVolume(vol);
}
int startCur = ImGui::GetCursorPosX();
if (ImGui::Checkbox(CONCAT("Link volume##sdrpp_streams_vol_", sid), &linked)) {
// TODO
}
ImGui::SameLine();
if (ImGui::Button(CONCAT("Remove##sdrpp_streams_remove_type_", sid), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
sinksToBeRemoved.push_back(id);
}
ImGui::Spacing();
}
lck2.unlock();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
float tableWidth = ImGui::GetContentRegionAvail().x;
ImGui::Spacing();
int startCur = ImGui::GetCursorPosX();
{
std::lock_guard<std::recursive_mutex> lck(sinkTypesMtx);
ImGui::Combo(CONCAT("##sdrpp_streams_add_type_", name), &addType, sinkTypes.txt);
ImGui::SameLine();
if (ImGui::Button(CONCAT("Add##sdrpp_streams_add_btn_", name), ImVec2(tableWidth - (ImGui::GetCursorPosX() - startCur), 0))) {
stream->addSink(sinkTypes.value(addType));
}
ImGui::Spacing();
}
ImGui::EndTable();
// Remove sinks that need to be removed
if (!sinksToBeRemoved.empty()) {
for (auto& id : sinksToBeRemoved) {
stream->removeSink(id);
}
sinksToBeRemoved.clear();
}
}
count++;
if (count < maxCount) {
ImGui::Spacing();
}
ImGui::Spacing();
}
}
};

View File

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

View File

@ -1,4 +1,5 @@
#pragma once
#include <stdint.h>
#include <imgui.h>
#include <string>
#include <vector>

View File

@ -62,6 +62,33 @@ inline void printAndScale(double freq, char* buf) {
}
}
inline void doZoom(int offset, int width, int inSize, int outSize, float* in, float* out) {
// NOTE: REMOVE THAT SHIT, IT'S JUST A HACKY FIX
if (offset < 0) {
offset = 0;
}
if (width > 524288) {
width = 524288;
}
float factor = (float)width / (float)outSize;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outSize; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > inSize) ? sFactor - ((sId + sFactor) - inSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (in[sId + j] > maxVal) { maxVal = in[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
namespace ImGui {
WaterFall::WaterFall() {
fftMin = -70.0;
@ -586,7 +613,7 @@ namespace ImGui {
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);
doZoom(drawDataStart, drawDataSize, rawFFTSize, 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))];
@ -689,6 +716,7 @@ namespace ImGui {
void WaterFall::onResize() {
std::lock_guard<std::recursive_mutex> lck(latestFFTMtx);
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
// return if widget is too small
if (widgetSize.x < 100 || widgetSize.y < 100) {
return;
@ -740,14 +768,23 @@ namespace ImGui {
}
latestFFTHold = new float[dataWidth];
// Reallocate smoothing buffer
if (fftSmoothing) {
if (smoothingBuf) { delete[] smoothingBuf; }
smoothingBuf = new float[dataWidth];
for (int i = 0; i < dataWidth; i++) {
smoothingBuf[i] = -1000.0f;
}
}
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
latestFFTHold[i] = -1000.0;
latestFFT[i] = -1000.0f; // Hide everything
latestFFTHold[i] = -1000.0f;
}
fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale));
@ -857,7 +894,7 @@ namespace ImGui {
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
if (waterfallVisible) {
doZoom(drawDataStart, drawDataSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, &rawFFTs[currentFFTLine * rawFFTSize], latestFFT);
memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel;
float dataRange = waterfallMax - waterfallMin;
@ -869,13 +906,29 @@ namespace ImGui {
waterfallUpdate = true;
}
else {
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT);
doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
fftLines = 1;
}
// Apply smoothing if enabled
if (fftSmoothing && latestFFT != NULL && smoothingBuf != NULL && fftLines != 0) {
std::lock_guard<std::mutex> lck2(smoothingBufMtx);
volk_32f_s32f_multiply_32f(latestFFT, latestFFT, fftSmoothingAlpha, dataWidth);
volk_32f_s32f_multiply_32f(smoothingBuf, smoothingBuf, fftSmoothingBeta, dataWidth);
volk_32f_x2_add_32f(smoothingBuf, latestFFT, smoothingBuf, dataWidth);
memcpy(latestFFT, smoothingBuf, dataWidth * sizeof(float));
}
if (selectedVFO != "" && vfos.size() > 0) {
float dummy;
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
if (snrSmoothing) {
float newSNR = 0.0f;
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, newSNR);
selectedVFOSNR = (snrSmoothingBeta*selectedVFOSNR) + (snrSmoothingAlpha*newSNR);
}
else {
calculateVFOSignalInfo(waterfallVisible ? &rawFFTs[currentFFTLine * rawFFTSize] : rawFFTs, vfos[selectedVFO], dummy, selectedVFOSNR);
}
}
// If FFT hold is enabled, update it
@ -1110,6 +1163,45 @@ namespace ImGui {
fftHoldSpeed = speed;
}
void WaterFall::setFFTSmoothing(bool enabled) {
std::lock_guard<std::mutex> lck(smoothingBufMtx);
fftSmoothing = enabled;
// Free buffer if not null
if (smoothingBuf) {delete[] smoothingBuf; }
// If disabled, stop here
if (!enabled) {
smoothingBuf = NULL;
return;
}
// Allocate and copy existing FFT into it
smoothingBuf = new float[dataWidth];
if (latestFFT) {
std::lock_guard<std::recursive_mutex> lck2(latestFFTMtx);
memcpy(smoothingBuf, latestFFT, dataWidth * sizeof(float));
}
else {
memset(smoothingBuf, 0, dataWidth * sizeof(float));
}
}
void WaterFall::setFFTSmoothingSpeed(float speed) {
std::lock_guard<std::mutex> lck(smoothingBufMtx);
fftSmoothingAlpha = speed;
fftSmoothingBeta = 1.0f - speed;
}
void WaterFall::setSNRSmoothing(bool enabled) {
snrSmoothing = enabled;
}
void WaterFall::setSNRSmoothingSpeed(float speed) {
snrSmoothingAlpha = speed;
snrSmoothingBeta = 1.0f - speed;
}
float* WaterFall::acquireLatestFFT(int& width) {
latestFFTMtx.lock();
if (!latestFFT) {

View File

@ -90,33 +90,6 @@ namespace ImGui {
float* getFFTBuffer();
void pushFFT();
inline 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 > 524288) {
width = 524288;
}
float factor = (float)width / (float)outWidth;
float sFactor = ceilf(factor);
float uFactor;
float id = offset;
float maxVal;
int sId;
for (int i = 0; i < outWidth; i++) {
maxVal = -INFINITY;
sId = (int)id;
uFactor = (sId + sFactor > rawFFTSize) ? sFactor - ((sId + sFactor) - rawFFTSize) : sFactor;
for (int j = 0; j < uFactor; j++) {
if (data[sId + j] > maxVal) { maxVal = data[sId + j]; }
}
out[i] = maxVal;
id += factor;
}
}
void updatePallette(float colors[][3], int colorCount);
void updatePalletteFromArray(float* colors, int colorCount);
@ -169,6 +142,12 @@ namespace ImGui {
void setFFTHold(bool hold);
void setFFTHoldSpeed(float speed);
void setFFTSmoothing(bool enabled);
void setFFTSmoothingSpeed(float speed);
void setSNRSmoothing(bool enabled);
void setSNRSmoothingSpeed(float speed);
float* acquireLatestFFT(int& width);
void releaseLatestFFT();
@ -182,7 +161,7 @@ namespace ImGui {
bool mouseInFFT = false;
bool mouseInWaterfall = false;
float selectedVFOSNR = NAN;
float selectedVFOSNR = 0.0f;
bool centerFrequencyLocked = false;
@ -270,6 +249,7 @@ namespace ImGui {
std::recursive_mutex buf_mtx;
std::recursive_mutex latestFFTMtx;
std::mutex texMtx;
std::mutex smoothingBufMtx;
float vRange;
@ -304,8 +284,9 @@ namespace ImGui {
//std::vector<std::vector<float>> rawFFTs;
int rawFFTSize;
float* rawFFTs = NULL;
float* latestFFT;
float* latestFFTHold;
float* latestFFT = NULL;
float* latestFFTHold = NULL;
float* smoothingBuf = NULL;
int currentFFTLine = 0;
int fftLines = 0;
@ -325,6 +306,14 @@ namespace ImGui {
bool fftHold = false;
float fftHoldSpeed = 0.3f;
bool fftSmoothing = false;
float fftSmoothingAlpha = 0.5;
float fftSmoothingBeta = 0.5;
bool snrSmoothing = false;
float snrSmoothingAlpha = 0.5;
float snrSmoothingBeta = 0.5;
// UI Select elements
bool fftResizeSelect = false;
bool freqScaleSelect = false;

View File

@ -33,7 +33,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) {
#else
mod.handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (mod.handle == NULL) {
flog::error("Couldn't load {0}.", path);
flog::error("Couldn't load {0}: {1}", path, dlerror());
mod.handle = NULL;
return mod;
}

View File

@ -2,7 +2,7 @@
#include <utils/flog.h>
bool ModuleComManager::registerInterface(std::string moduleName, std::string name, void (*handler)(int code, void* in, void* out, void* ctx), void* ctx) {
std::lock_guard<std::mutex> lck(mtx);
std::lock_guard<std::recursive_mutex> lck(mtx);
if (interfaces.find(name) != interfaces.end()) {
flog::error("Tried creating module interface with an existing name: {0}", name);
return false;
@ -16,7 +16,7 @@ bool ModuleComManager::registerInterface(std::string moduleName, std::string nam
}
bool ModuleComManager::unregisterInterface(std::string name) {
std::lock_guard<std::mutex> lck(mtx);
std::lock_guard<std::recursive_mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to erase module interface with unknown name: {0}", name);
return false;
@ -26,13 +26,13 @@ bool ModuleComManager::unregisterInterface(std::string name) {
}
bool ModuleComManager::interfaceExists(std::string name) {
std::lock_guard<std::mutex> lck(mtx);
std::lock_guard<std::recursive_mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) { return false; }
return true;
}
std::string ModuleComManager::getModuleName(std::string name) {
std::lock_guard<std::mutex> lck(mtx);
std::lock_guard<std::recursive_mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to call unknown module interface: {0}", name);
return "";
@ -41,7 +41,7 @@ std::string ModuleComManager::getModuleName(std::string name) {
}
bool ModuleComManager::callInterface(std::string name, int code, void* in, void* out) {
std::lock_guard<std::mutex> lck(mtx);
std::lock_guard<std::recursive_mutex> lck(mtx);
if (interfaces.find(name) == interfaces.end()) {
flog::error("Tried to call unknown module interface: {0}", name);
return false;

View File

@ -18,6 +18,6 @@ public:
bool callInterface(std::string name, int code, void* in, void* out);
private:
std::mutex mtx;
std::recursive_mutex mtx;
std::map<std::string, ModuleComInterface> interfaces;
};

View File

@ -230,7 +230,7 @@ namespace server {
// Compress data if needed and fill out header fields
if (compression) {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED;
bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE, data, count, 1);
bb_pkt_hdr->size = sizeof(PacketHeader) + (uint32_t)ZSTD_compressCCtx(cctx, &bbuf[sizeof(PacketHeader)], SERVER_MAX_PACKET_SIZE-sizeof(PacketHeader), data, count, 1);
}
else {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;

View File

@ -5,4 +5,5 @@ namespace sigpath {
VFOManager vfoManager;
SourceManager sourceManager;
SinkManager sinkManager;
StreamManager streamManager;
};

View File

@ -3,6 +3,7 @@
#include "vfo_manager.h"
#include "source.h"
#include "sink.h"
#include "stream.h"
#include <module.h>
namespace sigpath {
@ -10,4 +11,5 @@ namespace sigpath {
SDRPP_EXPORT VFOManager vfoManager;
SDRPP_EXPORT SourceManager sourceManager;
SDRPP_EXPORT SinkManager sinkManager;
SDRPP_EXPORT StreamManager streamManager;
};

View File

@ -84,7 +84,7 @@ void SourceManager::tune(double freq) {
if (selectedHandler == NULL) {
return;
}
// TODO: No need to always retune the hardware in panadpter mode
// TODO: No need to always retune the hardware in Panadapter mode
selectedHandler->tuneHandler(((tuneMode == TuningMode::NORMAL) ? freq : ifFreq) + tuneOffset, selectedHandler->ctx);
onRetune.emit(freq);
currentFreq = freq;
@ -100,7 +100,7 @@ void SourceManager::setTuningMode(TuningMode mode) {
tune(currentFreq);
}
void SourceManager::setPanadpterIF(double freq) {
void SourceManager::setPanadapterIF(double freq) {
ifFreq = freq;
tune(currentFreq);
}

View File

@ -35,7 +35,7 @@ public:
void tune(double freq);
void setTuningOffset(double offset);
void setTuningMode(TuningMode mode);
void setPanadpterIF(double freq);
void setPanadapterIF(double freq);
std::vector<std::string> getSourceNames();

View File

@ -0,0 +1,519 @@
#include "stream.h"
#include <utils/flog.h>
Sink::Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) :
entry(entry),
stream(stream),
streamName(name),
id(id),
stringId(stringId)
{}
void Sink::showMenu() {}
SinkEntry::SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate) :
manager(manager),
parentStream(parentStream),
id(id)
{
this->type = type;
this->inputSamplerate = inputSamplerate;
// Generate string ID
stringId = parentStream->getName();
char buf[16];
sprintf(buf, "%d", (int)id);
stringId += buf;
// Initialize DSP
resamp.init(&input, inputSamplerate, inputSamplerate);
volumeAdjust.init(&resamp.out, 1.0f, false);
// Initialize the sink
setType(type);
}
std::string SinkEntry::getType() const {
std::lock_guard<std::recursive_mutex> lck(mtx);
return type;
}
void SinkEntry::setType(const std::string& type) {
// Get unique lock on the entry
std::lock_guard<std::recursive_mutex> lck(mtx);
// Delete existing sink
if (sink) {
provider->destroySink(std::move(sink));
}
// Get shared lock on sink types
auto lck2 = manager->getSinkTypesLock();
// Get the provider or throw error
const auto& types = manager->getSinkTypes();
if (std::find(types.begin(), types.end(), type) == types.end()) {
this->type.clear();
throw SinkEntryCreateException("Invalid sink type");
}
// Create sink
this->type = type;
provider = manager->providers[type];
sink = provider->createSink(this, &volumeAdjust.out, parentStream->getName(), id, stringId);
}
SinkID SinkEntry::getID() const {
return id;
}
float SinkEntry::getVolume() const {
std::lock_guard<std::recursive_mutex> lck(mtx);
return volume;
}
void SinkEntry::setVolume(float volume) {
std::lock_guard<std::recursive_mutex> lck(mtx);
this->volume = volume;
volumeAdjust.setVolume(volume);
onVolumeChanged(volume);
}
bool SinkEntry::getMuted() const {
std::lock_guard<std::recursive_mutex> lck(mtx);
return muted;
}
void SinkEntry::setMuted(bool muted) {
std::lock_guard<std::recursive_mutex> lck(mtx);
this->muted = muted;
volumeAdjust.setMuted(muted);
onMutedChanged(muted);
}
float SinkEntry::getPanning() const {
std::lock_guard<std::recursive_mutex> lck(mtx);
return panning;
}
void SinkEntry::setPanning(float panning) {
std::lock_guard<std::recursive_mutex> lck(mtx);
this->panning = panning;
// TODO
onPanningChanged(panning);
}
void SinkEntry::showMenu() {
std::lock_guard<std::recursive_mutex> lck(mtx);
sink->showMenu();
}
void SinkEntry::startSink() {
std::lock_guard<std::recursive_mutex> lck(mtx);
sink->start();
}
void SinkEntry::stopSink() {
std::lock_guard<std::recursive_mutex> lck(mtx);
sink->stop();
}
std::lock_guard<std::recursive_mutex> SinkEntry::getLock() const {
return std::lock_guard<std::recursive_mutex>(mtx);
}
void SinkEntry::setSamplerate(double samplerate) {
std::lock_guard<std::recursive_mutex> lck(mtx);
resamp.setOutSamplerate(samplerate);
}
void SinkEntry::startDSP() {
std::lock_guard<std::recursive_mutex> lck(mtx);
resamp.start();
volumeAdjust.start();
}
void SinkEntry::stopDSP() {
std::lock_guard<std::recursive_mutex> lck(mtx);
resamp.stop();
volumeAdjust.stop();
}
void SinkEntry::destroy(bool forgetSettings) {
std::lock_guard<std::recursive_mutex> lck(mtx);
if (sink) {
provider->destroySink(std::move(sink));
}
type.clear();
}
void SinkEntry::setInputSamplerate(double samplerate) {
std::lock_guard<std::recursive_mutex> lck(mtx);
resamp.setInSamplerate(samplerate);
}
std::string SinkEntry::getStringID() const {
return stringId;
}
Stream::Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
manager(manager),
name(name)
{
this->samplerate = samplerate;
// Initialize DSP
split.init(stream);
}
Stream::~Stream() {
// Copy sink IDs
std::vector<SinkID> ids;
for (auto& [id, sink] : sinks) {
ids.push_back(id);
}
// Remove them all
for (auto& id : ids) {
removeSink(id, false);
}
}
const std::string& Stream::getName() const {
return name;
}
SinkID Stream::addSink(const std::string& type, SinkID id) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Find a free ID if not provided
if (id < 0) {
for (id = 0; sinks.find(id) != sinks.end(); id++);
}
else {
// Check that the provided ID is valid
if (sinks.find(id) != sinks.end()) {
flog::error("Tried to create sink for stream '{}' with existing ID: {}", name, id);
return -1;
}
}
// Create sink entry
std::shared_ptr<SinkEntry> sink;
try {
sink = std::make_shared<SinkEntry>(manager, this, type, id, samplerate);
}
catch (SinkEntryCreateException e) {
flog::error("Tried to create sink for stream '{}' with ID '{}': {}", name, id, e.what());
return -1;
}
// Start the sink and DSP
sink->startSink();
if (running) { sink->startDSP(); }
// Bind the sinks's input
split.bindStream(&sink->input);
// Add sink to list
sinks[id] = sink;
// Release lock and emit event
lck.unlock();
onSinkAdded(sink);
return id;
}
void Stream::removeSink(SinkID id, bool forgetSettings) {
// Acquire shared lock
std::shared_ptr<SinkEntry> sink;
{
std::shared_lock<std::shared_mutex> lck(sinksMtx);
// Check that the ID exists
if (sinks.find(id) == sinks.end()) {
flog::error("Tried to remove sink with unknown ID: {}", id);
return;
}
// Get sink
sink = sinks[id];
}
// Emit event
onSinkRemove(sink);
// Acquire unique lock
{
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Check that it's still in the list
if (sinks.find(id) == sinks.end()) {
flog::error("Tried to remove sink with unknown ID: {}", id);
return;
}
// Remove from list
sinks.erase(id);
// Unbind the sink's steam
split.unbindStream(&sink->input);
// Stop the sink and DSP
sink->stopDSP();
sink->stopSink();
// Delete instance
sink->destroy(forgetSettings);
}
}
std::shared_lock<std::shared_mutex> Stream::getSinksLock() {
return std::shared_lock<std::shared_mutex>(sinksMtx);
}
const std::map<SinkID, std::shared_ptr<SinkEntry>>& Stream::getSinks() const {
return sinks;
}
MasterStream::MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) :
Stream(manager, name, stream, samplerate)
{}
void MasterStream::setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// If all that's needed is to set the input, do it and return
if (samplerate == 0.0) {
split.setInput(stream);
return;
}
// Update samplerate
this->samplerate = samplerate;
// Stop DSP
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set input and samplerate
split.setInput(stream);
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
void MasterStream::setSamplerate(double samplerate) {
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Update samplerate
this->samplerate = samplerate;
// TODO: Maybe simply disallow while running?
// Stop DSP if it was running
if (running) {
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
}
// Set samplerate
for (auto& [id, sink] : sinks) {
sink->setInputSamplerate(samplerate);
}
// Start DSP if it was running
if (running) {
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
split.start();
}
}
void MasterStream::startDSP() {
// TODO: Maybe add a different mutex for the stream?
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Check if already running
if (running) { return; }
// Start all DSP
split.start();
for (auto& [id, sink] : sinks) {
sink->startDSP();
}
running = true;
}
void MasterStream::stopDSP() {
// TODO: Maybe add a different mutex for the stream?
std::unique_lock<std::shared_mutex> lck(sinksMtx);
// Check if already running
if (!running) { return; }
// Start all DSP
split.stop();
for (auto& [id, sink] : sinks) {
sink->stopDSP();
}
running = false;
}
std::shared_ptr<MasterStream> StreamManager::createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate) {
std::unique_lock<std::shared_mutex> lck(streamsMtx);
// Check that no stream with that name already exists
if (streams.find(name) != streams.end()) {
flog::error("Tried to created stream with an existing name: {}", name);
return NULL;
}
// Create and save stream
std::shared_ptr<MasterStream> newStream(new MasterStream(this, name, stream, samplerate));
streams[name] = newStream;
// Release lock and emit event
lck.unlock();
onStreamCreated(newStream);
return newStream;
}
void StreamManager::destroyStream(std::shared_ptr<MasterStream>& stream) {
// Emit event
onStreamDestroy(stream);
// Aquire complete lock on the stream list
{
std::unique_lock<std::shared_mutex> lck(streamsMtx);
// Get iterator of the stream
auto it = std::find_if(streams.begin(), streams.end(), [&stream](std::pair<const std::string, std::shared_ptr<Stream>> e) {
return e.second == stream;
});
if (it == streams.end()) {
flog::error("Tried to delete a stream using an invalid pointer. Stream not found in list");
return;
}
// Delete entry from list
flog::debug("Stream pointer uses, should be 2 and is {}", (int)stream.use_count());
streams.erase(it);
}
// Reset passed pointer
stream.reset();
}
std::shared_lock<std::shared_mutex> StreamManager::getStreamsLock() {
return std::shared_lock<std::shared_mutex>(streamsMtx);
}
const std::map<std::string, std::shared_ptr<Stream>>& StreamManager::getStreams() const {
return streams;
}
void StreamManager::registerSinkProvider(const std::string& name, SinkProvider* provider) {
std::unique_lock<std::shared_mutex> lck(providersMtx);
// Check that a provider with that name doesn't already exist
if (providers.find(name) != providers.end()) {
flog::error("Tried to register a sink provider with an existing name: {}", name);
return;
}
// Add provider to the list and sort name list
providers[name] = provider;
sinkTypes.push_back(name);
std::sort(sinkTypes.begin(), sinkTypes.end());
// Release lock and emit event
lck.unlock();
onSinkProviderRegistered(name);
}
void StreamManager::unregisterSinkProvider(SinkProvider* provider) {
// Get provider name for event
std::string type;
{
std::shared_lock<std::shared_mutex> lck(providersMtx);
auto it = std::find_if(providers.begin(), providers.end(), [&provider](std::pair<const std::string, SinkProvider *> e) {
return e.second == provider;
});
if (it == providers.end()) {
flog::error("Tried to unregister sink provider using invalid pointer");
return;
}
type = (*it).first;
}
// Emit event
onSinkProviderUnregister(type);
// Acquire shared lock on streams
{
std::unique_lock<std::shared_mutex> lck1(providersMtx);
std::shared_lock<std::shared_mutex> lck2(streamsMtx);
for (auto& [name, stream] : streams) {
// Aquire lock on sink list
auto sLock = stream->getSinksLock();
const auto& sinks = stream->getSinks();
// Find all sinks with the type that is about to be removed
std::vector<SinkID> toRemove;
for (auto& [id, sink] : sinks) {
if (sink->getType() != type) { continue; }
toRemove.push_back(id);
}
// Remove them all (TODO: THERE IS RACE CONDITION IF A SINK IS CHANGED AFTER LISTING)
sLock.unlock();
for (auto& id : toRemove) {
stream->removeSink(id);
}
}
// Remove from the lists
if (providers.find(type) != providers.end()) {
providers.erase(type);
}
else {
flog::error("Could not remove sink provider from list");
}
auto it = std::find(sinkTypes.begin(), sinkTypes.end(), type);
if (it != sinkTypes.end()) {
sinkTypes.erase(it);
}
else {
flog::error("Could not remove sink provider from sink type list");
}
}
}
std::shared_lock<std::shared_mutex> StreamManager::getSinkTypesLock() {
return std::shared_lock<std::shared_mutex>(providersMtx);
}
const std::vector<std::string>& StreamManager::getSinkTypes() const {
// TODO: This allows code to modify the names...
return sinkTypes;
}

View File

@ -0,0 +1,334 @@
#pragma once
#include <memory>
#include <vector>
#include <map>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/routing/splitter.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/audio/volume.h>
#include <utils/new_event.h>
#include <shared_mutex>
#include <stdexcept>
class SinkEntry;
class Stream;
class MasterStream;
class StreamManager;
using SinkID = int;
class Sink {
public:
Sink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId);
virtual ~Sink() {}
virtual void start() = 0;
virtual void stop() = 0;
virtual void showMenu();
protected:
SinkEntry* const entry;
dsp::stream<dsp::stereo_t>* const stream;
const std::string streamName;
const SinkID id;
const std::string stringId;
};
class SinkProvider {
friend Sink;
public:
/**
* Create a sink instance.
* @param name Name of the audio stream.
* @param index Index of the sink in the menu. Should be use to keep settings.
*/
virtual std::unique_ptr<Sink> createSink(SinkEntry* entry, dsp::stream<dsp::stereo_t>* stream, const std::string& name, SinkID id, const std::string& stringId) = 0;
/**
* Destroy a sink instance. This function is so that the provide knows at all times how many instances there are.
* @param sink Instance of the sink.
*/
virtual void destroySink(std::unique_ptr<Sink> sink) {
sink.reset();
}
};
class SinkEntryCreateException : public std::runtime_error {
public:
SinkEntryCreateException(const char* what) : std::runtime_error(what) {}
};
// TODO: Would be cool to have data and audio sinks instead of just audio.
class SinkEntry {
friend Sink;
friend Stream;
friend MasterStream;
public:
SinkEntry(StreamManager* manager, Stream* parentStream, const std::string& type, SinkID id, double inputSamplerate);
/**
* Get the type of the sink.
* @return Type of the sink.
*/
std::string getType() const;
/**
* Change the type of the sink.
* @param type New sink type.
*/
void setType(const std::string& type);
/**
* Get the ID of the sink.
* @return ID of the sink.
*/
SinkID getID() const;
/**
* Get sink volume.
* @return Volume as value between 0.0 and 1.0.
*/
float getVolume() const;
/**
* Set sink volume.
* @param volume Volume as value between 0.0 and 1.0.
*/
void setVolume(float volume);
/**
* Check if the sink is muted.
* @return True if muted, false if not.
*/
bool getMuted() const;
/**
* Set wether or not the sink is muted
* @param muted True to mute, false to unmute.
*/
void setMuted(bool muted);
/**
* Get sink panning.
* @return Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
*/
float getPanning() const;
/**
* Set sink panning.
* @param panning Panning as value between -1.0 and 1.0 meaning panning to the left and right respectively.
*/
void setPanning(float panning);
/**
* Show the sink type-specific menu.
*/
void showMenu();
/**
* Get the string form ID unique to both the sink and stream. Be used to reference settings.
* @return Unique string ID.
*/
std::string getStringID() const;
// Emitted when the type of the sink was changed
NewEvent<const std::string&> onTypeChanged;
// Emmited when volume of the sink was changed
NewEvent<float> onVolumeChanged;
// Emitted when the muted state of the sink was changed
NewEvent<bool> onMutedChanged;
// Emitted when the panning of the sink was changed
NewEvent<float> onPanningChanged;
// TODO: Need to allow the sink to change the entry samplerate and start/stop the DSP
// This will also require allowing it to get a lock on the sink so others don't attempt to mess with it.
std::lock_guard<std::recursive_mutex> getLock() const;
void startDSP();
void stopDSP();
void setSamplerate(double samplerate);
private:
void startSink();
void stopSink();
void destroy(bool forgetSettings);
void setInputSamplerate(double samplerate);
mutable std::recursive_mutex mtx;
dsp::stream<dsp::stereo_t> input;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::audio::Volume volumeAdjust;
SinkProvider* provider = NULL;
std::unique_ptr<Sink> sink;
std::string type;
const SinkID id;
double inputSamplerate;
Stream* const parentStream;
StreamManager* const manager;
std::string stringId;
float volume = 1.0f;
bool muted = false;
float panning = 0.0f;
};
class Stream {
protected:
Stream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
public:
~Stream();
/**
* Get the name of the stream.
* @return Name of the stream.
*/
const std::string& getName() const;
/**
* Add a sink to the stream.
* @param type Type of the sink.
* @param id ID of the sink. Optional, -1 if automatic.
* @return ID of the new sink or -1 on error.
*/
SinkID addSink(const std::string& type, SinkID id = -1);
/**
* Remove a sink from a stream.
* @param id ID of the sink.
* @param forgetSettings Forget the settings for the sink.
*/
void removeSink(SinkID id, bool forgetSettings = true);
/**
* Aquire a lock for the sink list.
* @return Shared lock for the sink list.
*/
std::shared_lock<std::shared_mutex> getSinksLock();
/**
* Get the list of all sinks belonging to this stream.
* @return Sink list.
*/
const std::map<SinkID, std::shared_ptr<SinkEntry>>& getSinks() const;
// Emitted when the samplerate of the stream was changed
NewEvent<double> onSamplerateChanged;
// Emitted when a sink was added
NewEvent<std::shared_ptr<SinkEntry>> onSinkAdded;
// Emitted when a sink is being removed
NewEvent<std::shared_ptr<SinkEntry>> onSinkRemove;
protected:
StreamManager* const manager;
const std::string name;
double samplerate;
dsp::routing::Splitter<dsp::stereo_t> split;
bool running = false;
std::map<SinkID, std::shared_ptr<SinkEntry>> sinks;
std::shared_mutex sinksMtx;
};
class MasterStream : public Stream {
friend StreamManager;
MasterStream(StreamManager* manager, const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
public:
/**
* Set DSP stream input.
* @param stream DSP stream.
* @param samplerate New samplerate (optional, 0.0 if not used).
*/
void setInput(dsp::stream<dsp::stereo_t>* stream, double samplerate = 0.0);
/**
* Set the samplerate of the input stream.
* @param samplerate Samplerate in Hz.
*/
void setSamplerate(double samplerate);
/**
* Start the DSP.
*/
void startDSP();
/**
* Stop the DSP.
*/
void stopDSP();
};
class StreamManager {
friend SinkEntry;
public:
/**
* Create an audio stream.
* @param name Name of the stream.
* @param stream DSP stream that outputs the audio.
* @param samplerate Samplerate of the audio data.
* @return Audio stream instance.
*/
std::shared_ptr<MasterStream> createStream(const std::string& name, dsp::stream<dsp::stereo_t>* stream, double samplerate);
/**
* Destroy an audio stream.
* @param stream Stream to destroy. The passed shared pointer will be automatically reset.
*/
void destroyStream(std::shared_ptr<MasterStream>& stream);
/**
* Aquire a lock for the stream list.
* @return Shared lock for the stream list.
*/
std::shared_lock<std::shared_mutex> getStreamsLock();
/**
* Get a list of streams and their associated names.
* @return Map of names to stream instance.
*/
const std::map<std::string, std::shared_ptr<Stream>>& getStreams() const;
/**
* Register a sink provider.
* @param name Name of the sink type.
* @param provider Sink provider instance.
*/
void registerSinkProvider(const std::string& name, SinkProvider* provider);
/**
* Unregister a sink provider.
* @param name Name of the sink type.
*/
void unregisterSinkProvider(SinkProvider* provider);
/**
* Aquire a lock for the sink type list.
* @return Shared lock for the sink type list.
*/
std::shared_lock<std::shared_mutex> getSinkTypesLock();
/**
* Get a list of sink types.
* @return List of sink type names in alphabetical order.
*/
const std::vector<std::string>& getSinkTypes() const;
// Emitted when a stream was created
NewEvent<std::shared_ptr<Stream>> onStreamCreated;
// Emitted when a stream is about to be destroyed
NewEvent<std::shared_ptr<Stream>> onStreamDestroy;
// Emitted when a sink provider was registered
NewEvent<const std::string&> onSinkProviderRegistered;
// Emitted when a sink provider is about to be unregistered
NewEvent<const std::string&> onSinkProviderUnregister;
private:
std::map<std::string, std::shared_ptr<Stream>> streams;
std::shared_mutex streamsMtx;
std::map<std::string, SinkProvider*> providers;
std::vector<std::string> sinkTypes;
std::shared_mutex providersMtx;
};

View File

@ -1,6 +1,7 @@
#include "net.h"
#include <string.h>
#include <codecvt>
#include <stdexcept>
#ifdef _WIN32
#define WOULD_BLOCK (WSAGetLastError() == WSAEWOULDBLOCK)
@ -85,14 +86,14 @@ namespace net {
addr.sin_port = htons(port);
}
std::string Address::getIPStr() {
std::string Address::getIPStr() const {
char buf[128];
IP_t ip = getIP();
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
return buf;
}
IP_t Address::getIP() {
IP_t Address::getIP() const {
return htonl(addr.sin_addr.s_addr);
}
@ -100,7 +101,7 @@ namespace net {
addr.sin_addr.s_addr = htonl(ip);
}
int Address::getPort() {
int Address::getPort() const {
return htons(addr.sin_port);
}
@ -137,7 +138,16 @@ namespace net {
}
int Socket::send(const uint8_t* data, size_t len, const Address* dest) {
return sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
// Send data
int err = sendto(sock, (const char*)data, len, 0, (sockaddr*)(dest ? &dest->addr : (raddr ? &raddr->addr : NULL)), sizeof(sockaddr_in));
// On error, close socket
if (err <= 0 && !WOULD_BLOCK) {
close();
return err;
}
return err;
}
int Socket::sendstr(const std::string& str, const Address* dest) {
@ -159,8 +169,8 @@ namespace net {
// Set timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
@ -224,8 +234,8 @@ namespace net {
// Define timeout
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data or error
if (timeout != NONBLOCKING) {
@ -374,13 +384,25 @@ namespace net {
return connect(Address(host, port));
}
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr) {
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast) {
// Init library if needed
init();
// Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// If the remote address is multicast, allow multicast connections
#ifdef _WIN32
const char enable = allowBroadcast;
#else
int enable = allowBroadcast;
#endif
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int)) < 0) {
closeSocket(s);
throw std::runtime_error("Could not enable broadcast on socket");
return NULL;
}
// Bind socket to local port
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
closeSocket(s);
@ -392,15 +414,15 @@ namespace net {
return std::make_shared<Socket>(s, &raddr);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr) {
return openudp(Address(rhost, rport), laddr);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast) {
return openudp(Address(rhost, rport), laddr, allowBroadcast);
}
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) {
return openudp(raddr, Address(lhost, lport));
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
return openudp(raddr, Address(lhost, lport), allowBroadcast);
}
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport) {
return openudp(Address(rhost, rport), Address(lhost, lport));
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int lport, bool allowBroadcast) {
return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
}
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <stdint.h>
#include <string>
#include <mutex>
#include <memory>
#include <map>
@ -66,13 +67,13 @@ namespace net {
* Get the IP address.
* @return IP address in standard string format.
*/
std::string getIPStr();
std::string getIPStr() const;
/**
* Get the IP address.
* @return IP address in host byte order.
*/
IP_t getIP();
IP_t getIP() const;
/**
* Set the IP address.
@ -84,7 +85,7 @@ namespace net {
* Get the TCP/UDP port.
* @return TCP/UDP port number.
*/
int getPort();
int getPort() const;
/**
* Set the TCP/UDP port.
@ -245,37 +246,37 @@ namespace net {
/**
* Create UDP socket.
* @param raddr Remote address.
* @param raddr Remote address. Set to a multicast address to allow multicast.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr);
std::shared_ptr<Socket> openudp(const Address& raddr, const Address& laddr, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rhost Remote hostname or IP address. Set to a multicast address to allow multicast.
* @param rport Remote port.
* @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, const Address& laddr, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param raddr Remote address.
* @param raddr Remote address. Set to a multicast or broadcast address to allow multicast.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0);
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
/**
* Create UDP socket.
* @param rhost Remote hostname or IP address.
* @param rhost Remote hostname or IP address. Set to a multicast or broadcast address to allow multicast.
* @param rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any).
* @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise.
*/
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0);
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost = "0.0.0.0", int lport = 0, bool allowBroadcast = false);
}

View File

@ -320,7 +320,7 @@ namespace net {
}
entry.handler(std::move(client), entry.ctx);
}
catch (std::exception e) {
catch (const std::exception& e) {
listening = false;
return;
}

View File

@ -0,0 +1,52 @@
#pragma once
#include <functional>
#include <stdexcept>
#include <mutex>
#include <map>
typedef int HandlerID;
template <typename... Args>
class NewEvent {
public:
using Handler = std::function<void(Args...)>;
HandlerID bind(const Handler& handler) {
std::lock_guard<std::mutex> lck(mtx);
HandlerID id = genID();
handlers[id] = handler;
return id;
}
template<typename MHandler, class T>
HandlerID bind(MHandler handler, T* ctx) {
return bind([=](Args... args){
(ctx->*handler)(args...);
});
}
void unbind(HandlerID id) {
std::lock_guard<std::mutex> lck(mtx);
if (handlers.find(id) == handlers.end()) {
throw std::runtime_error("Could not unbind handler, unknown ID");
}
handlers.erase(id);
}
void operator()(Args... args) {
std::lock_guard<std::mutex> lck(mtx);
for (const auto& [desc, handler] : handlers) {
handler(args...);
}
}
private:
HandlerID genID() {
int id;
for (id = 1; handlers.find(id) != handlers.end(); id++);
return id;
}
std::map<HandlerID, Handler> handlers;
std::mutex mtx;
};

View File

@ -63,6 +63,7 @@ namespace net::http {
std::string MessageHeader::getField(const std::string name) {
// TODO: Check if exists
// TODO: Maybe declare the set/get field functions to do type conversions automatically?
return fields[name];
}
@ -257,6 +258,7 @@ namespace net::http {
// Deserialize
req.deserialize(respData);
return 0; // Might wanna return size instead
}
int Client::sendResponseHeader(ResponseHeader& resp) {
@ -274,6 +276,7 @@ namespace net::http {
// Deserialize
resp.deserialize(respData);
return 0; // Might wanna return size instead
}
int Client::sendChunkHeader(ChunkHeader& chdr) {

View File

@ -7,6 +7,14 @@ namespace riff {
const char* LIST_SIGNATURE = "LIST";
const size_t RIFF_LABEL_SIZE = 4;
// Writer::Writer(const Writer&& b) {
// //file = std::move(b.file);
// }
Writer::~Writer() {
close();
}
bool Writer::open(std::string path, const char form[4]) {
std::lock_guard<std::recursive_mutex> lck(mtx);
@ -91,9 +99,9 @@ namespace riff {
file.write((char*)&desc.hdr.size, sizeof(desc.hdr.size));
file.seekp(pos);
// If parent chunk, increment its size
// If parent chunk, increment its size by the size of the sub-chunk plus the size of its header)
if (!chunks.empty()) {
chunks.top().hdr.size += desc.hdr.size;
chunks.top().hdr.size += desc.hdr.size + sizeof(ChunkHeader);
}
}

View File

@ -20,6 +20,10 @@ namespace riff {
class Writer {
public:
Writer() {}
// Writer(const Writer&& b);
~Writer();
bool open(std::string path, const char form[4]);
bool isOpen();
void close();
@ -40,4 +44,23 @@ namespace riff {
std::ofstream file;
std::stack<ChunkDesc> chunks;
};
// class Reader {
// public:
// Reader();
// Reader(const Reader&& b);
// ~Reader();
// bool open(std::string path);
// bool isOpen();
// void close();
// const std::string& form();
// private:
// std::string _form;
// std::recursive_mutex mtx;
// std::ofstream file;
// };
}

View File

@ -0,0 +1,63 @@
#pragma once
#include <dsp/loop/pll.h>
#include "chrominance_filter.h"
// TODO: Should be 60 but had to try something
#define BURST_START (63+CHROMA_FIR_DELAY)
#define BURST_END (BURST_START+28)
#define A_PHASE ((135.0/180.0)*FL_M_PI)
#define B_PHASE ((-135.0/180.0)*FL_M_PI)
namespace dsp::loop {
class ChromaPLL : public PLL {
using base_type = PLL;
public:
ChromaPLL() {}
ChromaPLL(stream<complex_t>* in, double bandwidth, double initPhase = 0.0, double initFreq = 0.0, double minFreq = -FL_M_PI, double maxFreq = FL_M_PI) {
base_type::init(in, bandwidth, initFreq, initPhase, minFreq, maxFreq);
}
inline int process(int count, complex_t* in, complex_t* out, bool aphase = false) {
// Process the pre-burst section
for (int i = 0; i < BURST_START; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
// Process the burst itself
if (aphase) {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - A_PHASE));
}
}
else {
for (int i = BURST_START; i < BURST_END; i++) {
complex_t outVal = in[i] * math::phasor(-pcl.phase);
out[i] = outVal;
pcl.advance(math::normalizePhase(outVal.phase() - B_PHASE));
}
}
// Process the post-burst section
for (int i = BURST_END; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
inline int processBlank(int count, complex_t* in, complex_t* out) {
for (int i = 0; i < count; i++) {
out[i] = in[i] * math::phasor(-pcl.phase);
pcl.advancePhase();
}
return count;
}
};
}

View File

@ -0,0 +1,239 @@
#pragma once
#include <dsp/types.h>
inline const dsp::complex_t CHROMA_FIR[] = {
{-0.000005461290583903, -0.000011336784355655},
{ 0.000020060944485414, 0.000009851315045203},
{-0.000034177222729438, 0.000007245841504981},
{ 0.000027694034878705, -0.000033114740542635},
{-0.000001217597841648, 0.000039141482370942},
{-0.000008324593371228, -0.000011315001355976},
{-0.000038085228233509, -0.000010585909953738},
{ 0.000114833396071141, -0.000047778708840608},
{-0.000115428390169113, 0.000205816198882814},
{-0.000055467806072871, -0.000356692479491626},
{ 0.000349316846854190, 0.000326162940234916},
{-0.000558465829929114, -0.000048001521408724},
{ 0.000488176200631416, -0.000319593757302922},
{-0.000169437838021935, 0.000501610900725908},
{-0.000131793335799502, -0.000373003580727547},
{ 0.000166817395492786, 0.000105930895534474},
{ 0.000030499908326112, -0.000003048682668943},
{-0.000174999505027919, 0.000168008090089458},
{ 0.000054431163395030, -0.000385174790951272},
{ 0.000215876012859739, 0.000372695852521209},
{-0.000325534912280750, -0.000130173041693966},
{ 0.000154951430569290, -0.000045395998708328},
{ 0.000054324657659002, -0.000076028700470037},
{ 0.000015664427565764, 0.000348002612845696},
{-0.000345943017888332, -0.000402175417043307},
{ 0.000568731727879741, 0.000112347863435682},
{-0.000416485880859085, 0.000211750352828909},
{ 0.000087462353623011, -0.000188197153014309},
{-0.000032082305030264, -0.000136804226080664},
{ 0.000379089999045955, 0.000303466839685362},
{-0.000726760198519770, -0.000007022279302816},
{ 0.000619888661818195, -0.000476871323359809},
{-0.000151885493742993, 0.000595641190573181},
{-0.000100626407015494, -0.000227947144491108},
{-0.000201935458823941, -0.000107628631934340},
{ 0.000680260922139900, -0.000120771182888852},
{-0.000666108629277491, 0.000744775901128973},
{ 0.000067236591919755, -0.001044125966364420},
{ 0.000447037274751822, 0.000651912509450913},
{-0.000262675893448686, -0.000082499729563337},
{-0.000349821460486320, 0.000132102793530818},
{ 0.000507024815168287, -0.000837598610490618},
{ 0.000163814255478652, 0.001346530693477834},
{-0.000970457632383793, -0.000968411010101160},
{ 0.000974834882891140, 0.000116507082762032},
{-0.000225464280571542, 0.000137131865995708},
{-0.000211542240694642, 0.000563783548428947},
{-0.000414412310798766, -0.001309793399193736},
{ 0.001497010004594478, 0.001021907858926259},
{-0.001752019159639658, 0.000116536066154131},
{ 0.000872822027879430, -0.000783952720205569},
{-0.000032439446797970, 0.000184988059956734},
{ 0.000446259382722895, 0.000833040920509238},
{-0.001741577737284306, -0.000764423771425237},
{ 0.002306569133792772, -0.000593352416441601},
{-0.001336084746214192, 0.001744394557524181},
{-0.000015810020735495, -0.001342809547658260},
{ 0.000007636494885364, 0.000009498318627546},
{ 0.001403876768349702, 0.000326101441888391},
{-0.002351020828600226, 0.001098649819278302},
{ 0.001389314639579544, -0.002746943712072884},
{ 0.000526319899588909, 0.002635084366837732},
{-0.001109526585744687, -0.000950323796527721},
{-0.000307792427984886, -0.000013203419520794},
{ 0.001737955094951111, -0.001247368808692850},
{-0.000974502437588420, 0.003352512117661680},
{-0.001462571137390936, -0.003635296917435679},
{ 0.002783459090201693, 0.001604420226187745},
{-0.001471518558760170, 0.000211117948702137},
{-0.000575340825070194, 0.000601820846100026},
{ 0.000302090333345692, -0.003088058972305493},
{ 0.002496092353182990, 0.003912508340989065},
{-0.004645661091012423, -0.001630427298020200},
{ 0.003556824805628799, -0.001209822327859352},
{-0.000744999556260706, 0.001143238699138109},
{ 0.000144278726929409, 0.001638049051599065},
{-0.003025291044450178, -0.003226370992887968},
{ 0.006047866290490120, 0.000927406808799887},
{-0.005338456415106141, 0.003008811999350399},
{ 0.001642959659014839, -0.003972384205231079},
{ 0.000273874932822212, 0.000977326273749033},
{ 0.002315022846573390, 0.001695671268241410},
{-0.006240953957978884, 0.000207330368698293},
{ 0.006164252120861735, -0.005177351717451013},
{-0.001560310257561104, 0.007437030759707700},
{-0.002131333814462852, -0.004317129694157112},
{ 0.000280518918541908, 0.000134405998842553},
{ 0.004612116481180659, -0.001024468120657814},
{-0.005599300279638699, 0.006828277067771868},
{ 0.000228879728552504, -0.010675998154712657},
{ 0.005692081512980654, 0.007582243186569848},
{-0.005100500569859509, -0.001364751685737153},
{-0.000902490398043454, 0.000385770160220703},
{ 0.003673858819546609, -0.006701685283451640},
{ 0.002079056046131593, 0.012568579063417429},
{-0.010730008156911677, -0.009826454574016218},
{ 0.012092401380903161, 0.000921764172237851},
{-0.004714530989129091, 0.003151948807627123},
{-0.001055930168838909, 0.003228576712467020},
{-0.004343270165991213, -0.011924332879354394},
{ 0.016499994418955999, 0.010255324919126899},
{-0.021047239750251585, 0.002309419513135448},
{ 0.011855513874047341, -0.011604071033866310},
{-0.000777842281358575, 0.005916341648175263},
{ 0.004380939277688377, 0.007397670455730446},
{-0.021891594662401131, -0.008509480947490166},
{ 0.032787638290674201, -0.009950745850861956},
{-0.021022579272463194, 0.030030850567389102},
{-0.001508145650189953, -0.027571914870304640},
{ 0.004056649693022923, 0.004624901687718579},
{ 0.025728742586666287, 0.004824671348397606},
{-0.058002700931665603, 0.030198618296813803},
{ 0.043631619628438784, -0.096308304333327280},
{ 0.033451363423624300, 0.136687079396426990},
{-0.129387018420204200, -0.101540513046619400},
{ 0.172881344826560730, -0.000000000000005297},
{-0.129387018420198010, 0.101540513046627330},
{ 0.033451363423615862, -0.136687079396429050},
{ 0.043631619628444723, 0.096308304333324601},
{-0.058002700931667456, -0.030198618296810247},
{ 0.025728742586665992, -0.004824671348399184},
{ 0.004056649693022639, -0.004624901687718827},
{-0.001508145650188251, 0.027571914870304734},
{-0.021022579272465047, -0.030030850567387805},
{ 0.032787638290674812, 0.009950745850859947},
{-0.021891594662400610, 0.008509480947491507},
{ 0.004380939277687923, -0.007397670455730714},
{-0.000777842281358940, -0.005916341648175215},
{ 0.011855513874048058, 0.011604071033865578},
{-0.021047239750251731, -0.002309419513134139},
{ 0.016499994418955360, -0.010255324919127926},
{-0.004343270165990471, 0.011924332879354665},
{-0.001055930168839110, -0.003228576712466955},
{-0.004714530989129287, -0.003151948807626830},
{ 0.012092401380903103, -0.000921764172238603},
{-0.010730008156911072, 0.009826454574016881},
{ 0.002079056046130817, -0.012568579063417559},
{ 0.003673858819547020, 0.006701685283451416},
{-0.000902490398043478, -0.000385770160220647},
{-0.005100500569859424, 0.001364751685737466},
{ 0.005692081512980187, -0.007582243186570198},
{ 0.000228879728553163, 0.010675998154712643},
{-0.005599300279639117, -0.006828277067771524},
{ 0.004612116481180722, 0.001024468120657532},
{ 0.000280518918541900, -0.000134405998842571},
{-0.002131333814462586, 0.004317129694157243},
{-0.001560310257561563, -0.007437030759707604},
{ 0.006164252120862052, 0.005177351717450635},
{-0.006240953957978898, -0.000207330368697911},
{ 0.002315022846573286, -0.001695671268241552},
{ 0.000273874932822152, -0.000977326273749050},
{ 0.001642959659015084, 0.003972384205230976},
{-0.005338456415106324, -0.003008811999350072},
{ 0.006047866290490063, -0.000927406808800258},
{-0.003025291044449980, 0.003226370992888153},
{ 0.000144278726929308, -0.001638049051599074},
{-0.000744999556260777, -0.001143238699138063},
{ 0.003556824805628873, 0.001209822327859134},
{-0.004645661091012323, 0.001630427298020484},
{ 0.002496092353182751, -0.003912508340989219},
{ 0.000302090333345882, 0.003088058972305475},
{-0.000575340825070231, -0.000601820846099991},
{-0.001471518558760183, -0.000211117948702046},
{ 0.002783459090201593, -0.001604420226187919},
{-0.001462571137390710, 0.003635296917435769},
{-0.000974502437588628, -0.003352512117661619},
{ 0.001737955094951189, 0.001247368808692742},
{-0.000307792427984885, 0.000013203419520814},
{-0.001109526585744628, 0.000950323796527789},
{ 0.000526319899588746, -0.002635084366837765},
{ 0.001389314639579712, 0.002746943712072799},
{-0.002351020828600294, -0.001098649819278158},
{ 0.001403876768349682, -0.000326101441888477},
{ 0.000007636494885364, -0.000009498318627546},
{-0.000015810020735412, 0.001342809547658261},
{-0.001336084746214299, -0.001744394557524099},
{ 0.002306569133792808, 0.000593352416441460},
{-0.001741577737284259, 0.000764423771425344},
{ 0.000446259382722843, -0.000833040920509266},
{-0.000032439446797982, -0.000184988059956732},
{ 0.000872822027879478, 0.000783952720205515},
{-0.001752019159639665, -0.000116536066154024},
{ 0.001497010004594416, -0.001021907858926351},
{-0.000414412310798685, 0.001309793399193761},
{-0.000211542240694677, -0.000563783548428934},
{-0.000225464280571550, -0.000137131865995694},
{ 0.000974834882891133, -0.000116507082762092},
{-0.000970457632383734, 0.000968411010101219},
{ 0.000163814255478569, -0.001346530693477844},
{ 0.000507024815168339, 0.000837598610490586},
{-0.000349821460486328, -0.000132102793530797},
{-0.000262675893448681, 0.000082499729563353},
{ 0.000447037274751782, -0.000651912509450940},
{ 0.000067236591919819, 0.001044125966364416},
{-0.000666108629277537, -0.000744775901128932},
{ 0.000680260922139908, 0.000120771182888810},
{-0.000201935458823935, 0.000107628631934352},
{-0.000100626407015480, 0.000227947144491114},
{-0.000151885493743030, -0.000595641190573172},
{ 0.000619888661818225, 0.000476871323359771},
{-0.000726760198519770, 0.000007022279302861},
{ 0.000379089999045936, -0.000303466839685386},
{-0.000032082305030256, 0.000136804226080666},
{ 0.000087462353623023, 0.000188197153014303},
{-0.000416485880859098, -0.000211750352828883},
{ 0.000568731727879734, -0.000112347863435717},
{-0.000345943017888307, 0.000402175417043329},
{ 0.000015664427565742, -0.000348002612845697},
{ 0.000054324657659007, 0.000076028700470034},
{ 0.000154951430569292, 0.000045395998708319},
{-0.000325534912280742, 0.000130173041693986},
{ 0.000215876012859716, -0.000372695852521222},
{ 0.000054431163395054, 0.000385174790951269},
{-0.000174999505027930, -0.000168008090089447},
{ 0.000030499908326113, 0.000003048682668941},
{ 0.000166817395492779, -0.000105930895534485},
{-0.000131793335799479, 0.000373003580727555},
{-0.000169437838021966, -0.000501610900725898},
{ 0.000488176200631435, 0.000319593757302892},
{-0.000558465829929111, 0.000048001521408758},
{ 0.000349316846854170, -0.000326162940234938},
{-0.000055467806072849, 0.000356692479491629},
{-0.000115428390169126, -0.000205816198882806},
{ 0.000114833396071144, 0.000047778708840601},
{-0.000038085228233508, 0.000010585909953741},
{-0.000008324593371228, 0.000011315001355977},
{-0.000001217597841650, -0.000039141482370942},
{ 0.000027694034878707, 0.000033114740542633},
{-0.000034177222729439, -0.000007245841504979},
{ 0.000020060944485413, -0.000009851315045204},
{-0.000005461290583903, 0.000011336784355656},
};
#define CHROMA_FIR_SIZE (sizeof(CHROMA_FIR)/sizeof(dsp::complex_t))
#define CHROMA_FIR_DELAY ((CHROMA_FIR_SIZE-1)/2)

View File

@ -0,0 +1,193 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/phase_control_loop.h>
#include <dsp/taps/windowed_sinc.h>
#include <dsp/multirate/polyphase_bank.h>
#include <dsp/math/step.h>
class LineSync : public dsp::Processor<float, float> {
using base_type = dsp::Processor<float, float>;
public:
LineSync() {}
LineSync(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) { init(in, omega, omegaGain, muGain, omegaRelLimit, interpPhaseCount, interpTapCount); }
~LineSync() {
if (!base_type::_block_init) { return; }
base_type::stop();
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
}
void init(dsp::stream<float>* in, double omega, double omegaGain, double muGain, double omegaRelLimit, int interpPhaseCount = 128, int interpTapCount = 8) {
_omega = omega;
_omegaGain = omegaGain;
_muGain = muGain;
_omegaRelLimit = omegaRelLimit;
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
pcl.init(_muGain, _omegaGain, 0.0, 0.0, 1.0, _omega, _omega * (1.0 - omegaRelLimit), _omega * (1.0 + omegaRelLimit));
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::init(in);
}
void setOmegaGain(double omegaGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaGain = omegaGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setMuGain(double muGain) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_muGain = muGain;
pcl.setCoefficients(_muGain, _omegaGain);
}
void setOmegaRelLimit(double omegaRelLimit) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_omegaRelLimit = omegaRelLimit;
pcl.setFreqLimits(_omega * (1.0 - _omegaRelLimit), _omega * (1.0 + _omegaRelLimit));
}
void setSyncLevel(float level) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
syncLevel = level;
}
void setInterpParams(int interpPhaseCount, int interpTapCount) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_interpPhaseCount = interpPhaseCount;
_interpTapCount = interpTapCount;
dsp::multirate::freePolyphaseBank(interpBank);
dsp::buffer::free(buffer);
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
offset = 0;
pcl.phase = 0.0f;
pcl.freq = _omega;
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
// Copy data to work buffer
memcpy(bufStart, base_type::_in->readBuf, count * sizeof(float));
if (test2) {
test2 = false;
offset += 5;
}
// Process all samples
while (offset < count) {
// Calculate new output value
int phase = std::clamp<int>(floorf(pcl.phase * (float)_interpPhaseCount), 0, _interpPhaseCount - 1);
float outVal;
volk_32f_x2_dot_prod_32f(&outVal, &buffer[offset], interpBank.phases[phase], _interpTapCount);
base_type::out.writeBuf[outCount++] = outVal;
// If the end of the line is reached, process it and determin error
float error = 0;
if (outCount >= 720) {
// Compute averages.
float left = 0.0f, right = 0.0f;
for (int i = (720-17); i < 720; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 0; i < 27; i++) {
left += base_type::out.writeBuf[i];
}
for (int i = 27; i < (54+17); i++) {
right += base_type::out.writeBuf[i];
}
left *= (1.0f/44.0f);
right *= (1.0f/44.0f);
// If the sync is present, compute error
if ((left < syncLevel && right < syncLevel) && !forceLock) {
error = (left + syncBias - right);
locked = true;
}
else {
locked = false;
}
if (++counter >= 100) {
counter = 0;
//flog::warn("Left: {}, Right: {}, Error: {}, Freq: {}, Phase: {}", left, right, error, pcl.freq, pcl.phase);
}
// Output line
if (!base_type::out.swap(outCount)) { break; }
outCount = 0;
}
// Advance symbol offset and phase
pcl.advance(error);
float delta = floorf(pcl.phase);
offset += delta;
pcl.phase -= delta;
}
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
// Swap if some data was generated
base_type::_in->flush();
return outCount;
}
bool locked = false;
bool test2 = false;
float syncBias = 0.0f;
bool forceLock = false;
int counter = 0;
protected:
void generateInterpTaps() {
double bw = 0.5 / (double)_interpPhaseCount;
dsp::tap<float> lp = dsp::taps::windowedSinc<float>(_interpPhaseCount * _interpTapCount, dsp::math::hzToRads(bw, 1.0), dsp::window::nuttall, _interpPhaseCount);
interpBank = dsp::multirate::buildPolyphaseBank<float>(_interpPhaseCount, lp);
dsp::taps::free(lp);
}
dsp::multirate::PolyphaseBank<float> interpBank;
dsp::loop::PhaseControlLoop<double, false> pcl;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
int offset = 0;
int outCount = 0;
float* buffer;
float* bufStart;
float syncLevel = -0.03f;
};

View File

@ -10,6 +10,15 @@
#include <dsp/demod/quadrature.h>
#include <dsp/sink/handler_sink.h>
#include "linesync.h"
#include <dsp/loop/pll.h>
#include <dsp/convert/real_to_complex.h>
#include <dsp/filter/fir.h>
#include <dsp/taps/from_array.h>
#include "chrominance_filter.h"
#include "chroma_pll.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -17,7 +26,8 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Description: */ "ATV decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1};
/* Max instances */ -1
};
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f)
@ -29,9 +39,16 @@ class ATVDecoderModule : public ModuleManager::Instance {
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sink.init(&demod.out, handler, this);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
sink.init(&sync.out, handler, this);
r2c.init(NULL);
chromaTaps = dsp::taps::fromArray(CHROMA_FIR_SIZE, CHROMA_FIR);
fir.init(NULL, chromaTaps);
pll.init(NULL, 0.01, 0.0, dsp::math::hzToRads(4433618.75, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*0.90, SAMPLE_RATE), dsp::math::hzToRads(4433618.75*1.1, SAMPLE_RATE));
demod.start();
sync.start();
sink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
@ -47,9 +64,13 @@ class ATVDecoderModule : public ModuleManager::Instance {
void postInit() {}
void enable() { enabled = true; }
void enable() {
enabled = true;
}
void disable() { enabled = false; }
void disable() {
enabled = false;
}
bool isEnabled() { return enabled; }
@ -61,6 +82,8 @@ class ATVDecoderModule : public ModuleManager::Instance {
style::beginDisabled();
}
// Ideal width for testing: 750pixels
ImGui::FillWidth();
_this->img.draw();
@ -76,6 +99,28 @@ class ATVDecoderModule : public ModuleManager::Instance {
ImGui::FillWidth();
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
ImGui::LeftLabel("Sync Bias");
ImGui::FillWidth();
ImGui::SliderFloat("##syncBias", &_this->sync.syncBias,-0.1, 0.1);
if (ImGui::Button("Test2")) {
_this->sync.test2 = true;
}
if (ImGui::Button("Switch frame")) {
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
if (_this->sync.locked) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::Checkbox("Force Lock", &_this->sync.forceLock);
if (!_this->enabled) {
style::endDisabled();
}
@ -84,70 +129,67 @@ class ATVDecoderModule : public ModuleManager::Instance {
static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
uint8_t *buf = (uint8_t *)_this->img.buffer;
float val;
float imval;
int pos = 0;
// Convert line to complex
_this->r2c.process(720, data, _this->r2c.out.writeBuf);
// Isolate the chroma subcarrier
_this->fir.process(720, _this->r2c.out.writeBuf, _this->fir.out.writeBuf);
// Run chroma carrier through the PLL
_this->pll.process(720, _this->fir.out.writeBuf, _this->pll.out.writeBuf, ((_this->ypos%2)==1) ^ _this->evenFrame);
// Render line to the image without color
int lypos = _this->ypos - 1;
if (lypos < 0) { lypos = 624; }
uint32_t* lastLine = &((uint32_t *)_this->img.buffer)[(lypos < 313) ? (lypos*720*2) : ((((lypos - 313)*2)+1)*720) ];
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos < 313) ? (_this->ypos*720*2) : ((((_this->ypos - 313)*2)+1)*720) ];
//uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[_this->ypos*720];
for (int i = 0; i < count; i++) {
val = data[i];
// Sync
if (val < _this->sync_level) {
_this->sync_count++;
}
else {
if (_this->sync_count >= 300) {
_this->short_sync = 0;
}
else if (_this->sync_count >= 33) {
if (_this->short_sync == 5) {
_this->even_field = false;
_this->ypos = 0;
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
else if (_this->short_sync == 4) {
_this->even_field = true;
_this->ypos = 0;
}
_this->xpos = 0;
_this->short_sync = 0;
}
else if (_this->sync_count >= 15) {
_this->short_sync++;
}
_this->sync_count = 0;
}
int imval = std::clamp<float>((data[i] - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// uint32_t re = std::clamp<float>((_this->pll.out.writeBuf[i].re - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// uint32_t im = std::clamp<float>((_this->pll.out.writeBuf[i].im - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
// currentLine[i] = 0xFF000000 | (im << 8) | re;
currentLine[i] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
}
// Vertical scan logic
_this->ypos++;
bool rollover = _this->ypos >= 625;
if (rollover) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
// Draw
imval = std::clamp<float>((val - _this->minLvl) * 255.0 / _this->spanLvl, 0, 255);
if (_this->even_field) {
pos = ((720 * _this->ypos * 2) + _this->xpos) * 4;
}
else {
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4;
}
// Measure vsync levels
float sync0 = 0.0f, sync1 = 0.0f;
for (int i = 0; i < 306; i++) {
sync0 += data[i];
}
for (int i = (720/2); i < ((720/2)+306); i++) {
sync1 += data[i];
}
sync0 *= (1.0f/305.0f);
sync1 *= (1.0f/305.0f);
buf[pos] = imval;
buf[pos + 1] = imval;
buf[pos + 2] = imval;
buf[pos + 3] = imval;
// Save sync detection to history
_this->syncHistory >>= 2;
_this->syncHistory |= (((uint16_t)(sync1 < _this->sync_level)) << 9) | (((uint16_t)(sync0 < _this->sync_level)) << 8);
// Image logic
_this->xpos++;
if (_this->xpos >= 720) {
_this->ypos++;
_this->xpos = 0;
}
if (_this->ypos >= 312) {
_this->ypos = 0;
_this->xpos = 0;
_this->even_field = !_this->even_field;
if (_this->even_field) {
_this->img.swap();
buf = (uint8_t *)_this->img.buffer;
}
// Trigger vsync in case one is detected
// TODO: Also sync with odd field
if (!rollover && _this->syncHistory == 0b0000011111) {
{
std::lock_guard<std::mutex> lck(_this->evenFrameMtx);
_this->evenFrame = !_this->evenFrame;
}
_this->ypos = 0;
_this->img.swap();
}
}
@ -156,19 +198,27 @@ class ATVDecoderModule : public ModuleManager::Instance {
VFOManager::VFO *vfo = NULL;
dsp::demod::Quadrature demod;
LineSync sync;
dsp::sink::Handler<float> sink;
int xpos = 0;
dsp::convert::RealToComplex r2c;
dsp::tap<dsp::complex_t> chromaTaps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::ChromaPLL pll;
int ypos = 0;
bool even_field = false;
float sync_level = -0.3f;
bool evenFrame = false;
std::mutex evenFrameMtx;
float sync_level = -0.06f;
int sync_count = 0;
int short_sync = 0;
float minLvl = 0.0f;
float spanLvl = 1.0f;
bool lockedLines = 0;
uint16_t syncHistory = 0;
ImGui::ImageDisplay img;
};

View File

@ -57,10 +57,13 @@ public:
if (config.conf[name].contains("brokenModulation")) {
brokenModulation = config.conf[name]["brokenModulation"];
}
if (config.conf[name].contains("oqpsk")) {
oqpsk = config.conf[name]["oqpsk"];
}
config.release();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, 1e-6, 0.01);
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, INPUT_SAMPLE_RATE, true);
demod.init(vfo->output, 72000.0f, INPUT_SAMPLE_RATE, 33, 0.6f, 0.1f, 0.005f, brokenModulation, oqpsk, 1e-6, 0.01);
split.init(&demod.out);
split.bindStream(&symSinkStream);
split.bindStream(&sinkStream);
@ -99,6 +102,7 @@ public:
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 150000, INPUT_SAMPLE_RATE, 150000, 150000, true);
demod.setBrokenModulation(brokenModulation);
demod.setInput(vfo->output);
demod.start();
@ -151,6 +155,13 @@ private:
config.release(true);
}
if (ImGui::Checkbox(CONCAT("OQPSK##oqpsk", _this->name), &_this->oqpsk)) {
_this->demod.setOQPSK(_this->oqpsk);
config.acquire();
config.conf[_this->name]["oqpsk"] = _this->oqpsk;
config.release(true);
}
if (!_this->folderSelect.pathIsValid() && _this->enabled) { style::beginDisabled(); }
if (_this->recording) {
@ -245,7 +256,7 @@ private:
uint64_t dataWritten = 0;
std::ofstream recFile;
bool brokenModulation = false;
bool oqpsk = false;
int8_t* writeBuffer;
};

View File

@ -11,8 +11,8 @@ namespace dsp::demod {
public:
Meteor() {}
Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, omegaGain, muGain);
Meteor(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
init(in, symbolrate, samplerate, rrcTapCount, rrcBeta, agcRate, costasBandwidth, brokenModulation, oqpsk, omegaGain, muGain);
}
~Meteor() {
@ -21,11 +21,12 @@ namespace dsp::demod {
taps::free(rrcTaps);
}
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
void init(stream<complex_t>* in, double symbolrate, double samplerate, int rrcTapCount, double rrcBeta, double agcRate, double costasBandwidth, bool brokenModulation, bool oqpsk, double omegaGain, double muGain, double omegaRelLimit = 0.01) {
_symbolrate = symbolrate;
_samplerate = samplerate;
_rrcTapCount = rrcTapCount;
_rrcBeta = rrcBeta;
_oqpsk = oqpsk;
rrcTaps = taps::rootRaisedCosine<float>(_rrcTapCount, _rrcBeta, _symbolrate, _samplerate);
rrc.init(NULL, rrcTaps);
@ -129,6 +130,12 @@ namespace dsp::demod {
costas.setBrokenModulation(enabled);
}
void setOQPSK(bool enabled) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
_oqpsk = enabled;
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
@ -144,6 +151,18 @@ namespace dsp::demod {
rrc.process(count, in, out);
agc.process(count, out, out);
costas.process(count, out, out);
if (_oqpsk) {
// Single sample delay + deinterleave
for (int i = 0; i < count; i++) {
float tmp = out[i].im;
out[i].im = lastI;
lastI = tmp;
}
// TODO: Additional 1/24th sample delay
}
return recov.process(count, out, out);
}
@ -166,6 +185,8 @@ namespace dsp::demod {
double _samplerate;
int _rrcTapCount;
double _rrcBeta;
float lastI = 0.0f;
bool _oqpsk = false;
tap<float> rrcTaps;
filter::FIR<complex_t, float> rrc;

View File

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.13)
project(pager_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(pager_decoder PRIVATE "src/")

View File

@ -0,0 +1,11 @@
#pragma once
#include <signal_path/vfo_manager.h>
class Decoder {
public:
virtual ~Decoder() {}
virtual void showMenu() {};
virtual void setVFO(VFOManager::VFO* vfo) = 0;
virtual void start() = 0;
virtual void stop() = 0;
};

View File

@ -0,0 +1,96 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "flex.h"
class FLEXDecoder : public Decoder {
dsp::stream<float> dummy1;
dsp::stream<uint8_t> dummy2;
public:
FLEXDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, 1600) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(1600, "1600 Baud", 1600);
baudrates.define(3200, "3200 Baud", 3200);
baudrates.define(6400, "6400 Baud", 6400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(16000, 12500);
reshape.init(&dummy1, 1600.0, (1600 / 30.0) - 1600.0);
dataHandler.init(&dummy2, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
}
~FLEXDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_flex_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
// dsp.setInput(vfo->output);
}
void start() {
flog::debug("FLEX start");
// dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
flog::debug("FLEX stop");
// dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
// _this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
FLEXDecoder* _this = (FLEXDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
std::string name;
VFOManager::VFO* vfo;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
flex::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 0;
OptionList<int, int> baudrates;
};

View File

@ -0,0 +1,5 @@
#include "flex.h"
namespace flex {
// TODO
}

View File

@ -0,0 +1,11 @@
#pragma once
namespace flex {
class Decoder {
public:
// TODO
private:
// TODO
};
}

View File

@ -0,0 +1,172 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <gui/widgets/folder_select.h>
#include <utils/optionlist.h>
#include "decoder.h"
#include "pocsag/decoder.h"
#include "flex/decoder.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "pager_decoder",
/* Description: */ "POCSAG and Flex Pager Decoder"
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
enum Protocol {
PROTOCOL_INVALID = -1,
PROTOCOL_POCSAG,
PROTOCOL_FLEX
};
class PagerDecoderModule : public ModuleManager::Instance {
public:
PagerDecoderModule(std::string name) {
this->name = name;
// Define protocols
protocols.define("POCSAG", PROTOCOL_POCSAG);
//protocols.define("FLEX", PROTOCOL_FLEX);
// Initialize VFO with default values
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
// Select the protocol
selectProtocol(PROTOCOL_POCSAG);
gui::menu.registerEntry(name, menuHandler, this, this);
}
~PagerDecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP
if (enabled) {
decoder->stop();
decoder.reset();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 12500, 24000, 12500, 12500, true);
vfo->setSnapInterval(1);
decoder->setVFO(vfo);
decoder->start();
enabled = true;
}
void disable() {
decoder->stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
void selectProtocol(Protocol newProto) {
// Cannot change while disabled
if (!enabled) { return; }
// If the protocol hasn't changed, no need to do anything
if (newProto == proto) { return; }
// Delete current decoder
decoder.reset();
// Create a new decoder
switch (newProto) {
case PROTOCOL_POCSAG:
decoder = std::make_unique<POCSAGDecoder>(name, vfo);
break;
case PROTOCOL_FLEX:
decoder = std::make_unique<FLEXDecoder>(name, vfo);
break;
default:
flog::error("Tried to select unknown pager protocol");
return;
}
// Start the new decoder
decoder->start();
// Save selected protocol
proto = newProto;
}
private:
static void menuHandler(void* ctx) {
PagerDecoderModule* _this = (PagerDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->selectProtocol(_this->protocols.value(_this->protoId));
}
if (_this->decoder) { _this->decoder->showMenu(); }
ImGui::Button(("Record##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
ImGui::Button(("Show Messages##pager_decoder_show_" + _this->name).c_str(), ImVec2(menuWidth, 0));
if (!_this->enabled) { style::endDisabled(); }
}
std::string name;
bool enabled = true;
Protocol proto = PROTOCOL_INVALID;
int protoId = 0;
OptionList<std::string, Protocol> protocols;
// DSP Chain
VFOManager::VFO* vfo;
std::unique_ptr<Decoder> decoder;
bool showLines = false;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/pager_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new PagerDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (PagerDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -0,0 +1,105 @@
#pragma once
#include "../decoder.h"
#include <signal_path/vfo_manager.h>
#include <utils/optionlist.h>
#include <gui/widgets/symbol_diagram.h>
#include <gui/style.h>
#include <dsp/sink/handler_sink.h>
#include "dsp.h"
#include "pocsag.h"
#define BAUDRATE 2400
#define SAMPLERATE (BAUDRATE*10)
class POCSAGDecoder : public Decoder {
public:
POCSAGDecoder(const std::string& name, VFOManager::VFO* vfo) : diag(0.6, BAUDRATE) {
this->name = name;
this->vfo = vfo;
// Define baudrate options
baudrates.define(512, "512 Baud", 512);
baudrates.define(1200, "1200 Baud", 1200);
baudrates.define(2400, "2400 Baud", 2400);
// Init DSP
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(SAMPLERATE, 12500);
dsp.init(vfo->output, SAMPLERATE, BAUDRATE);
reshape.init(&dsp.soft, BAUDRATE, (BAUDRATE / 30.0) - BAUDRATE);
dataHandler.init(&dsp.out, _dataHandler, this);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init decoder
decoder.onMessage.bind(&POCSAGDecoder::messageHandler, this);
}
~POCSAGDecoder() {
stop();
}
void showMenu() {
ImGui::LeftLabel("Baudrate");
ImGui::FillWidth();
if (ImGui::Combo(("##pager_decoder_pocsag_br_" + name).c_str(), &brId, baudrates.txt)) {
// TODO
}
ImGui::FillWidth();
diag.draw();
}
void setVFO(VFOManager::VFO* vfo) {
this->vfo = vfo;
vfo->setBandwidthLimits(12500, 12500, true);
vfo->setSampleRate(24000, 12500);
dsp.setInput(vfo->output);
}
void start() {
dsp.start();
reshape.start();
dataHandler.start();
diagHandler.start();
}
void stop() {
dsp.stop();
reshape.stop();
dataHandler.stop();
diagHandler.stop();
}
private:
static void _dataHandler(uint8_t* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
_this->decoder.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
POCSAGDecoder* _this = (POCSAGDecoder*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
void messageHandler(pocsag::Address addr, pocsag::MessageType type, const std::string& msg) {
flog::debug("[{}]: '{}'", (uint32_t)addr, msg);
}
std::string name;
VFOManager::VFO* vfo;
POCSAGDSP dsp;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<uint8_t> dataHandler;
dsp::sink::Handler<float> diagHandler;
pocsag::Decoder decoder;
ImGui::SymbolDiagram diag;
int brId = 2;
OptionList<int, int> baudrates;
};

View File

@ -0,0 +1,76 @@
#pragma once
#include <dsp/stream.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/sink/handler_sink.h>
#include <dsp/demod/quadrature.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/correction/dc_blocker.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/routing/doubler.h>
class POCSAGDSP : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
POCSAGDSP() {}
POCSAGDSP(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) { init(in, samplerate, baudrate); }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double baudrate) {
// Save settings
_samplerate = samplerate;
// Configure blocks
demod.init(NULL, -4500.0, samplerate);
float taps[] = { 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f };
shape = dsp::taps::fromArray<float>(10, taps);
fir.init(NULL, shape);
recov.init(NULL, samplerate/baudrate, 1e-4, 1.0, 0.05);
// Free useless buffers
fir.out.free();
recov.out.free();
// Init base
base_type::init(in);
}
int process(int count, dsp::complex_t* in, float* softOut, uint8_t* out) {
count = demod.process(count, in, demod.out.readBuf);
count = fir.process(count, demod.out.readBuf, demod.out.readBuf);
count = recov.process(count, demod.out.readBuf, softOut);
dsp::digital::BinarySlicer::process(count, softOut, out);
return count;
}
void setBaudrate(double baudrate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
base_type::tempStart();
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (count) { if (!soft.swap(count)) { return -1; } }
return count;
}
dsp::stream<float> soft;
private:
dsp::demod::Quadrature demod;
dsp::tap<float> shape;
dsp::filter::FIR<float, float> fir;
dsp::clock_recovery::MM<float> recov;
double _samplerate;
};

View File

@ -0,0 +1,173 @@
#include "pocsag.h"
#include <string.h>
#include <utils/flog.h>
#define POCSAG_FRAME_SYNC_CODEWORD ((uint32_t)(0b01111100110100100001010111011000))
#define POCSAG_IDLE_CODEWORD_DATA ((uint32_t)(0b011110101100100111000))
#define POCSAG_BATCH_BIT_COUNT (POCSAG_BATCH_CODEWORD_COUNT*32)
#define POCSAG_DATA_BITS_PER_CW 20
#define POCSAG_GEN_POLY ((uint32_t)(0b11101101001))
namespace pocsag {
const char NUMERIC_CHARSET[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'*',
'U',
' ',
'-',
']',
'['
};
Decoder::Decoder() {
// Zero out batch
memset(batch, 0, sizeof(batch));
}
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Get symbol
uint32_t s = symbols[i];
// If not sync, try to acquire sync (TODO: sync confidence)
if (!synced) {
// Append new symbol to sync shift register
syncSR = (syncSR << 1) | s;
// Test for sync
synced = (distance(syncSR, POCSAG_FRAME_SYNC_CODEWORD) <= POCSAG_SYNC_DIST);
// Go to next symbol
continue;
}
// TODO: Flush message on desync
// Append bit to batch
batch[batchOffset >> 5] |= (s << (31 - (batchOffset & 0b11111)));
batchOffset++;
// On end of batch, decode and reset
if (batchOffset >= POCSAG_BATCH_BIT_COUNT) {
decodeBatch();
batchOffset = 0;
synced = false;
memset(batch, 0, sizeof(batch));
}
}
}
int Decoder::distance(uint32_t a, uint32_t b) {
uint32_t diff = a ^ b;
int dist = 0;
for (int i = 0; i < 32; i++) {
dist += (diff >> i ) & 1;
}
return dist;
}
bool Decoder::correctCodeword(Codeword in, Codeword& out) {
return true; // TODO
}
void Decoder::flushMessage() {
if (!msg.empty()) {
// Send out message
onMessage(addr, msgType, msg);
// Reset state
msg.clear();
currChar = 0;
currOffset = 0;
}
}
void printbin(uint32_t cw) {
for (int i = 31; i >= 0; i--) {
printf("%c", ((cw >> i) & 1) ? '1':'0');
}
}
void bitswapChar(char in, char& out) {
out = 0;
for (int i = 0; i < 7; i++) {
out |= ((in >> (6-i)) & 1) << i;
}
}
void Decoder::decodeBatch() {
for (int i = 0; i < POCSAG_BATCH_CODEWORD_COUNT; i++) {
// Get codeword
Codeword cw = batch[i];
// Correct errors. If corrupted, skip
if (!correctCodeword(cw, cw)) { continue; }
// TODO: End message if two consecutive are corrupt
// Get codeword type
CodewordType type = (CodewordType)((cw >> 31) & 1);
if (type == CODEWORD_TYPE_ADDRESS && (cw >> 11) == POCSAG_IDLE_CODEWORD_DATA) {
type = CODEWORD_TYPE_IDLE;
}
// Decode codeword
if (type == CODEWORD_TYPE_IDLE) {
// If a non-empty message is available, send it out and clear
flushMessage();
}
else if (type == CODEWORD_TYPE_ADDRESS) {
// If a non-empty message is available, send it out and clear
flushMessage();
// Decode message type
msgType = MESSAGE_TYPE_ALPHANUMERIC;
// msgType = (MessageType)((cw >> 11) & 0b11);
// Decode address and append lower 8 bits from position
addr = ((cw >> 13) & 0b111111111111111111) << 3;
addr |= (i >> 1);
}
else if (type == CODEWORD_TYPE_MESSAGE) {
// Extract the 20 data bits
uint32_t data = (cw >> 11) & 0b11111111111111111111;
// Decode data depending on message type
if (msgType == MESSAGE_TYPE_NUMERIC) {
// Numeric messages pack 5 characters per message codeword
msg += NUMERIC_CHARSET[(data >> 16) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 12) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 8) & 0b1111];
msg += NUMERIC_CHARSET[(data >> 4) & 0b1111];
msg += NUMERIC_CHARSET[data & 0b1111];
}
else if (msgType == MESSAGE_TYPE_ALPHANUMERIC) {
// Unpack ascii bits 7 at a time (TODO: could be more efficient)
for (int i = 19; i >= 0; i--) {
// Append bit to char
currChar |= ((data >> i) & 1) << (currOffset++);
// When the char is full, append to message
if (currOffset >= 7) {
// TODO: maybe replace with std::isprint
if (currChar) { msg += currChar; }
currChar = 0;
currOffset = 0;
}
}
}
}
}
}
}

View File

@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <stdint.h>
#include <utils/new_event.h>
#define POCSAG_SYNC_DIST 4
#define POCSAG_BATCH_CODEWORD_COUNT 16
namespace pocsag {
enum CodewordType {
CODEWORD_TYPE_IDLE = -1,
CODEWORD_TYPE_ADDRESS = 0,
CODEWORD_TYPE_MESSAGE = 1
};
enum MessageType {
MESSAGE_TYPE_NUMERIC = 0b00,
MESSAGE_TYPE_ALPHANUMERIC = 0b11
};
using Codeword = uint32_t;
using Address = uint32_t;
class Decoder {
public:
Decoder();
void process(uint8_t* symbols, int count);
NewEvent<Address, MessageType, const std::string&> onMessage;
private:
static int distance(uint32_t a, uint32_t b);
bool correctCodeword(Codeword in, Codeword& out);
void flushMessage();
void decodeBatch();
uint32_t syncSR = 0;
bool synced = false;
int batchOffset = 0;
Codeword batch[POCSAG_BATCH_CODEWORD_COUNT];
Address addr;
MessageType msgType;
std::string msg;
char currChar = 0;
int currOffset = 0;
};
}

View File

@ -24,13 +24,12 @@ namespace demod {
class Demodulator {
public:
virtual ~Demodulator() {}
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) = 0;
virtual void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void showMenu() = 0;
virtual void setBandwidth(double bandwidth) = 0;
virtual void setInput(dsp::stream<dsp::complex_t>* input) = 0;
virtual void AFSampRateChanged(double newSR) = 0;
virtual const char* getName() = 0;
virtual double getIFSampleRate() = 0;
virtual double getAFSampleRate() = 0;
@ -45,7 +44,6 @@ namespace demod {
virtual int getDefaultDeemphasisMode() = 0;
virtual bool getFMIFNRAllowed() = 0;
virtual bool getNBAllowed() = 0;
virtual bool getAFNRAllowed() = 0;
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
};
}

View File

@ -7,13 +7,13 @@ namespace demod {
public:
AM() {}
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
AM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~AM() { stop(); }
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
_config = config;
@ -68,8 +68,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "AM"; }
@ -86,7 +84,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -7,15 +7,15 @@ namespace demod {
public:
CW() {}
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
CW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~CW() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
this->_config = config;
this->afbwChangeHandler = afbwChangeHandler;
@ -74,8 +74,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "CW"; }
@ -92,7 +90,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -7,15 +7,15 @@ namespace demod {
public:
DSB() {}
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
DSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~DSB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
_config = config;
@ -61,8 +61,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "DSB"; }
@ -79,7 +77,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -7,15 +7,15 @@ namespace demod {
public:
LSB() {}
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
LSB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~LSB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
_config = config;
@ -61,8 +61,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "LSB"; }
@ -79,7 +77,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -7,13 +7,13 @@ namespace demod {
public:
NFM() {}
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
NFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~NFM() { stop(); }
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
this->_config = config;
@ -57,8 +57,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "FM"; }
@ -75,7 +73,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -7,17 +7,18 @@ namespace demod {
public:
RAW() {}
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
RAW(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~RAW() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
audioSampleRate = audioSR;
audioSampleRate = 48000;
// TODO: This needs to be selectable
// Define structure
c2s.init(input);
@ -39,10 +40,6 @@ namespace demod {
c2s.setInput(input);
}
void AFSampRateChanged(double newSR) {
audioSampleRate = newSR;
}
// ============= INFO =============
const char* getName() { return "RAW"; }
@ -59,7 +56,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
private:

View File

@ -8,15 +8,15 @@ namespace demod {
public:
USB() {}
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
USB(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
init(name, config, input, bandwidth);
}
~USB() {
stop();
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
_config = config;
@ -62,8 +62,6 @@ namespace demod {
void setInput(dsp::stream<dsp::complex_t>* input) { demod.setInput(input); }
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "USB"; }
@ -80,7 +78,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; }
bool getAFNRAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private:

View File

@ -1,23 +1,23 @@
#pragma once
#include "../demod.h"
#include <dsp/demod/broadcast_fm.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/clock_recovery/fd.h>
#include <dsp/taps/root_raised_cosine.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/manchester_decoder.h>
#include <dsp/digital/differential_decoder.h>
#include "../rds_demod.h"
#include <gui/widgets/symbol_diagram.h>
#include <fstream>
#include <rds.h>
namespace demod {
enum RDSRegion {
RDS_REGION_EUROPE,
RDS_REGION_NORTH_AMERICA
};
class WFM : public Demodulator {
public:
WFM() {}
WFM() : diag(0.5, 4096) {}
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
init(name, config, input, bandwidth, audioSR);
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) : diag(0.5, 4096) {
init(name, config, input, bandwidth);
}
~WFM() {
@ -25,14 +25,22 @@ namespace demod {
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
}
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) {
void init(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth) {
this->name = name;
_config = config;
// Define RDS regions
rdsRegions.define("eu", "Europe", RDS_REGION_EUROPE);
rdsRegions.define("na", "North America", RDS_REGION_NORTH_AMERICA);
// Register FFT draw handler
fftRedrawHandler.handler = fftRedraw;
fftRedrawHandler.ctx = this;
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
// Default
std::string rdsRegionStr = "eu";
// Load config
_config->acquire();
bool modified = false;
@ -45,33 +53,50 @@ namespace demod {
if (config->conf[name][getName()].contains("rds")) {
_rds = config->conf[name][getName()]["rds"];
}
if (config->conf[name][getName()].contains("rdsInfo")) {
_rdsInfo = config->conf[name][getName()]["rdsInfo"];
}
if (config->conf[name][getName()].contains("rdsRegion")) {
rdsRegionStr = config->conf[name][getName()]["rdsRegion"];
}
_config->release(modified);
// Define structure
// Load RDS region
if (rdsRegions.keyExists(rdsRegionStr)) {
rdsRegionId = rdsRegions.keyId(rdsRegionStr);
rdsRegion = rdsRegions.value(rdsRegionId);
}
else {
rdsRegion = RDS_REGION_EUROPE;
rdsRegionId = rdsRegions.valueId(rdsRegion);
}
// Init DSP
demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01);
slice.init(&recov.out);
manch.init(&slice.out);
diff.init(&manch.out, 2);
hs.init(&diff.out, rdsHandler, this);
rdsDemod.init(&demod.rdsOut, _rdsInfo);
hs.init(&rdsDemod.out, rdsHandler, this);
reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
diagHandler.init(&reshape.out, _diagHandler, this);
// Init RDS display
diag.lines.push_back(-0.8);
diag.lines.push_back(0.8);
}
void start() {
demod.start();
recov.start();
slice.start();
manch.start();
diff.start();
rdsDemod.start();
hs.start();
reshape.start();
diagHandler.start();
}
void stop() {
demod.stop();
recov.stop();
slice.stop();
manch.stop();
diff.stop();
rdsDemod.stop();
hs.stop();
reshape.stop();
diagHandler.stop();
}
void showMenu() {
@ -94,14 +119,129 @@ namespace demod {
_config->release(true);
}
// if (_rds) {
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); }
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); }
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); }
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); }
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); }
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); }
// }
// TODO: This might break when the entire radio module is disabled
if (!_rds) { ImGui::BeginDisabled(); }
if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
setAdvancedRds(_rdsInfo);
_config->acquire();
_config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
_config->release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_wfm_rds_region_" + name).c_str(), &rdsRegionId, rdsRegions.txt)) {
rdsRegion = rdsRegions.value(rdsRegionId);
_config->acquire();
_config->conf[name][getName()]["rdsRegion"] = rdsRegions.key(rdsRegionId);
_config->release(true);
}
if (!_rds) { ImGui::EndDisabled(); }
float menuWidth = ImGui::GetContentRegionAvail().x;
if (_rds && _rdsInfo) {
ImGui::BeginTable(("##radio_wfm_rds_info_tbl_" + name).c_str(), 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders);
if (rdsDecode.piCodeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("0x%04X (%s)", rdsDecode.getPICode(), rdsDecode.getCallsign().c_str());
}
else {
ImGui::Text("0x%04X", rdsDecode.getPICode());
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getCountryCode());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s (%d)", rds::AREA_COVERAGE_TO_STR[rdsDecode.getProgramCoverage()], rdsDecode.getProgramCoverage());
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%d", rdsDecode.getProgramRefNumber());
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("PI Code");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::TextUnformatted("0x---- (----)");
}
else {
ImGui::TextUnformatted("0x----");
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Country Code");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--"); // TODO: String
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Coverage");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Reference Number");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("--");
}
if (rdsDecode.programTypeValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
if (rdsRegion == RDS_REGION_NORTH_AMERICA) {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_US_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
else {
ImGui::Text("%s (%d)", rds::PROGRAM_TYPE_EU_TO_STR[rdsDecode.getProgramType()], rdsDecode.getProgramType());
}
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Program Type");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("------- (--)"); // TODO: String
}
if (rdsDecode.musicValid()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s", rdsDecode.getMusic() ? "Yes":"No");
}
else {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Music");
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("---");
}
ImGui::EndTable();
ImGui::SetNextItemWidth(menuWidth);
diag.draw();
}
}
void setBandwidth(double bandwidth) {
@ -112,8 +252,6 @@ namespace demod {
demod.setInput(input);
}
void AFSampRateChanged(double newSR) {}
// ============= INFO =============
const char* getName() { return "WFM"; }
@ -130,7 +268,6 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; }
bool getAFNRAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
// ============= DEDICATED FUNCTIONS =============
@ -140,12 +277,24 @@ namespace demod {
demod.setStereo(_stereo);
}
void setAdvancedRds(bool enabled) {
rdsDemod.setSoftEnabled(enabled);
_rdsInfo = enabled;
}
private:
static void rdsHandler(uint8_t* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
_this->rdsDecode.process(data, count);
}
static void _diagHandler(float* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
WFM* _this = (WFM*)ctx;
if (!_this->_rds) { return; }
@ -187,23 +336,31 @@ namespace demod {
}
dsp::demod::BroadcastFM demod;
dsp::clock_recovery::FD recov;
dsp::digital::BinarySlicer slice;
dsp::digital::ManchesterDecoder manch;
dsp::digital::DifferentialDecoder diff;
RDSDemod rdsDemod;
dsp::sink::Handler<uint8_t> hs;
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
rds::RDSDecoder rdsDecode;
dsp::buffer::Reshaper<float> reshape;
dsp::sink::Handler<float> diagHandler;
ImGui::SymbolDiagram diag;
rds::Decoder rdsDecode;
ConfigManager* _config = NULL;
bool _stereo = false;
bool _lowPass = true;
bool _rds = false;
bool _rdsInfo = false;
float muGain = 0.01;
float omegaGain = (0.01*0.01)/4.0;
int rdsRegionId = 0;
RDSRegion rdsRegion = RDS_REGION_EUROPE;
OptionList<std::string, RDSRegion> rdsRegions;
std::string name;
};
}

View File

@ -9,7 +9,6 @@
#include <dsp/noise_reduction/noise_blanker.h>
#include <dsp/noise_reduction/fm_if.h>
#include <dsp/noise_reduction/squelch.h>
#include <dsp/noise_reduction/audio.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/filter/deephasis.h>
#include <core.h>
@ -82,32 +81,16 @@ public:
// Initialize audio DSP chain
afChain.init(&dummyAudioStream);
resamp.init(NULL, 250000.0, 48000.0);
deemp.init(NULL, 50e-6, 48000.0);
afNR.init(NULL, 1024);
afChain.addBlock(&resamp, true);
afChain.addBlock(&deemp, false);
afChain.addBlock(&afNR, false);
// Initialize the sink
srChangeHandler.ctx = this;
srChangeHandler.handler = sampleRateChangeHandler;
stream.init(afChain.out, &srChangeHandler, audioSampleRate);
sigpath::sinkManager.registerStream(name, &stream);
stream = sigpath::streamManager.createStream(name, afChain.out, 48000);
// Select the demodulator
selectDemodByID((DemodID)selectedDemodID);
// Start IF chain
ifChain.start();
// Start AF chain
afChain.start();
// Start stream, the rest was started when selecting the demodulator
stream.start();
// Register the menu
gui::menu.registerEntry(name, menuHandler, this, this);
@ -118,11 +101,10 @@ public:
~RadioModule() {
core::modComManager.unregisterInterface(name);
gui::menu.removeEntry(name);
stream.stop();
if (enabled) {
disable();
}
sigpath::sinkManager.unregisterStream(name);
sigpath::streamManager.destroyStream(stream);
}
void postInit() {}
@ -134,9 +116,7 @@ public:
vfo->wtfVFO->onUserChangedBandwidth.bindHandler(&onUserChangedBandwidthHandler);
}
ifChain.setInput(vfo->output, [=](dsp::stream<dsp::complex_t>* out){ ifChainOutputChangeHandler(out, this); });
ifChain.start();
selectDemodByID((DemodID)selectedDemodID);
afChain.start();
}
void disable() {
@ -144,6 +124,7 @@ public:
ifChain.stop();
if (selectedDemod) { selectedDemod->stop(); }
afChain.stop();
stream->stopDSP();
if (vfo) { sigpath::vfoManager.deleteVFO(vfo); }
vfo = NULL;
}
@ -250,12 +231,6 @@ private:
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
}
// Noise reduction
if (_this->afNRAllowed) {
if (ImGui::Checkbox(("Audio Noise Reduction##_radio_afnr_ena_" + _this->name).c_str(), &_this->afNREnabled)) {
_this->setAFNREnabled(_this->afNREnabled);
}
}
// Squelch
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
@ -322,7 +297,7 @@ private:
bw = std::clamp<double>(bw, demod->getMinBandwidth(), demod->getMaxBandwidth());
// Initialize
demod->init(name, &config, ifChain.out, bw, stream.getSampleRate());
demod->init(name, &config, ifChain.out, bw);
return demod;
}
@ -346,22 +321,34 @@ private:
}
void selectDemod(demod::Demodulator* demod) {
// Stopcurrently selected demodulator and select new
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Stop the IF chain
ifChain.stop();
// Stop the current demodulator
if (selectedDemod) {
selectedDemod->stop();
}
// Stop AF chain
afChain.stop();
// Stop audio stream's DSP
stream->stopDSP();
// Destroy the old demodulator
afChain.setInput(&dummyAudioStream, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
if (selectedDemod) {
delete selectedDemod;
}
selectedDemod = demod;
// Give the demodulator the most recent audio SR
selectedDemod->AFSampRateChanged(audioSampleRate);
// Select the new demodulator
selectedDemod = demod;
// Set the demodulator's input
selectedDemod->setInput(ifChain.out);
// Set AF chain's input
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
afChain.setInput(selectedDemod->getOutput(), [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
// Load config
bandwidth = selectedDemod->getDefaultBandwidth();
@ -379,8 +366,6 @@ private:
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
nbAllowed = selectedDemod->getNBAllowed();
nbEnabled = false;
afNRAllowed = selectedDemod->getAFNRAllowed();
afNREnabled = false;
nbLevel = 0.0f;
double ifSamplerate = selectedDemod->getIFSampleRate();
config.acquire();
@ -422,9 +407,6 @@ private:
if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) {
nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"];
}
if (config.conf[name][selectedDemod->getName()].contains("audioNoiseReductionEnabled")) {
nbEnabled = config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"];
}
config.release();
// Configure VFO
@ -454,24 +436,30 @@ private:
// Configure AF chain
if (postProcEnabled) {
// Configure resampler
afChain.stop();
resamp.setInSamplerate(selectedDemod->getAFSampleRate());
setAudioSampleRate(audioSampleRate);
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
deemp.setSamplerate(selectedDemod->getAFSampleRate());
// Configure deemphasis
setDeemphasisMode(deempAllowed ? deempModes[deempId] : DEEMP_MODE_NONE);
// Configure AF NR
setAFNREnabled(afNRAllowed && afNREnabled);
setDeemphasisMode(deempModes[deempId]);
}
else {
// Disable everything if post processing is disabled
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
afChain.disableAllBlocks([=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
}
// Update audo samplerate
stream->setSamplerate(selectedDemod->getAFSampleRate());
// Start the IF chain
ifChain.start();
// Start new demodulator
selectedDemod->start();
// Start the AF chain
afChain.start();
// Start the audio stream
stream->startDSP();
}
@ -487,37 +475,12 @@ private:
config.release(true);
}
void setAudioSampleRate(double sr) {
audioSampleRate = sr;
if (!selectedDemod) { return; }
selectedDemod->AFSampRateChanged(audioSampleRate);
if (!postProcEnabled && vfo) {
// If postproc is disabled, IF SR = AF SR
minBandwidth = selectedDemod->getMinBandwidth();
maxBandwidth = selectedDemod->getMaxBandwidth();
bandwidth = selectedDemod->getIFSampleRate();
vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked());
vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth);
return;
}
afChain.stop();
// Configure resampler
resamp.setOutSamplerate(audioSampleRate);
// Configure deemphasis sample rate
deemp.setSamplerate(audioSampleRate);
afChain.start();
}
void setDeemphasisMode(DeemphasisMode mode) {
deempId = deempModes.valueId(mode);
if (!postProcEnabled || !selectedDemod) { return; }
bool deempEnabled = (mode != DEEMP_MODE_NONE);
if (deempEnabled) { deemp.setTau(deempTaus[mode]); }
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
afChain.setBlockEnabled(&deemp, deempEnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream->setInput(out); });
// Save config
config.acquire();
@ -525,17 +488,6 @@ private:
config.release(true);
}
void setAFNREnabled(bool enable) {
afNREnabled = enable;
if (!postProcEnabled || !selectedDemod) { return; }
afChain.setBlockEnabled(&afNR, afNREnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"] = nbEnabled;
config.release(true);
}
void setNBEnabled(bool enable) {
nbEnabled = enable;
if (!selectedDemod) { return; }
@ -612,11 +564,6 @@ private:
_this->setBandwidth(newBw);
}
static void sampleRateChangeHandler(float sampleRate, void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
_this->setAudioSampleRate(sampleRate);
}
static void ifChainOutputChangeHandler(dsp::stream<dsp::complex_t>* output, void* ctx) {
RadioModule* _this = (RadioModule*)ctx;
if (!_this->selectedDemod) { return; }
@ -671,7 +618,6 @@ private:
// Handlers
EventHandler<double> onUserChangedBandwidthHandler;
EventHandler<float> srChangeHandler;
EventHandler<dsp::stream<dsp::complex_t>*> ifChainOutputChanged;
EventHandler<dsp::stream<dsp::stereo_t>*> afChainOutputChanged;
@ -686,11 +632,9 @@ private:
// Audio chain
dsp::stream<dsp::stereo_t> dummyAudioStream;
dsp::chain<dsp::stereo_t> afChain;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::filter::Deemphasis<dsp::stereo_t> deemp;
dsp::noise_reduction::Audio afNR;
SinkManager::Stream stream;
std::shared_ptr<MasterStream> stream;
demod::Demodulator* selectedDemod = NULL;
@ -712,9 +656,6 @@ private:
int deempId = 0;
bool deempAllowed;
bool afNREnabled = false;
bool afNRAllowed;
bool FMIFNRAllowed;
bool FMIFNREnabled = false;
int fmIFPresetId;

View File

@ -3,6 +3,8 @@
#include <map>
#include <algorithm>
#include <utils/flog.h>
namespace rds {
std::map<uint16_t, BlockType> SYNDROMES = {
{ 0b1111011000, BLOCK_TYPE_A },
@ -20,6 +22,98 @@ namespace rds {
{ BLOCK_TYPE_D, 0b0110110100 }
};
std::map<uint16_t, const char*> THREE_LETTER_CALLS = {
{ 0x99A5, "KBW" },
{ 0x99A6, "KCY" },
{ 0x9990, "KDB" },
{ 0x99A7, "KDF" },
{ 0x9950, "KEX" },
{ 0x9951, "KFH" },
{ 0x9952, "KFI" },
{ 0x9953, "KGA" },
{ 0x9991, "KGB" },
{ 0x9954, "KGO" },
{ 0x9955, "KGU" },
{ 0x9956, "KGW" },
{ 0x9957, "KGY" },
{ 0x99AA, "KHQ" },
{ 0x9958, "KID" },
{ 0x9959, "KIT" },
{ 0x995A, "KJR" },
{ 0x995B, "KLO" },
{ 0x995C, "KLZ" },
{ 0x995D, "KMA" },
{ 0x995E, "KMJ" },
{ 0x995F, "KNX" },
{ 0x9960, "KOA" },
{ 0x99AB, "KOB" },
{ 0x9992, "KOY" },
{ 0x9993, "KPQ" },
{ 0x9964, "KQV" },
{ 0x9994, "KSD" },
{ 0x9965, "KSL" },
{ 0x9966, "KUJ" },
{ 0x9995, "KUT" },
{ 0x9967, "KVI" },
{ 0x9968, "KWG" },
{ 0x9996, "KXL" },
{ 0x9997, "KXO" },
{ 0x996B, "KYW" },
{ 0x9999, "WBT" },
{ 0x996D, "WBZ" },
{ 0x996E, "WDZ" },
{ 0x996F, "WEW" },
{ 0x999A, "WGH" },
{ 0x9971, "WGL" },
{ 0x9972, "WGN" },
{ 0x9973, "WGR" },
{ 0x999B, "WGY" },
{ 0x9975, "WHA" },
{ 0x9976, "WHB" },
{ 0x9977, "WHK" },
{ 0x9978, "WHO" },
{ 0x999C, "WHP" },
{ 0x999D, "WIL" },
{ 0x997A, "WIP" },
{ 0x99B3, "WIS" },
{ 0x997B, "WJR" },
{ 0x99B4, "WJW" },
{ 0x99B5, "WJZ" },
{ 0x997C, "WKY" },
{ 0x997D, "WLS" },
{ 0x997E, "WLW" },
{ 0x999E, "WMC" },
{ 0x999F, "WMT" },
{ 0x9981, "WOC" },
{ 0x99A0, "WOI" },
{ 0x9983, "WOL" },
{ 0x9984, "WOR" },
{ 0x99A1, "WOW" },
{ 0x99B9, "WRC" },
{ 0x99A2, "WRR" },
{ 0x99A3, "WSB" },
{ 0x99A4, "WSM" },
{ 0x9988, "WWJ" },
{ 0x9989, "WWL" }
};
std::map<uint16_t, const char*> NAT_LOC_LINKED_STATIONS = {
{ 0xB01, "NPR-1" },
{ 0xB02, "CBC - Radio One" },
{ 0xB03, "CBC - Radio Two" },
{ 0xB04, "Radio-Canada - Première Chaîne" },
{ 0xB05, "Radio-Canada - Espace Musique" },
{ 0xB06, "CBC" },
{ 0xB07, "CBC" },
{ 0xB08, "CBC" },
{ 0xB09, "CBC" },
{ 0xB0A, "NPR-2" },
{ 0xB0B, "NPR-3" },
{ 0xB0C, "NPR-4" },
{ 0xB0D, "NPR-5" },
{ 0xB0E, "NPR-6" }
};
// 9876543210
const uint16_t LFSR_POLY = 0b0110111001;
const uint16_t IN_POLY = 0b1100011011;
@ -28,7 +122,7 @@ namespace rds {
const int DATA_LEN = 16;
const int POLY_LEN = 10;
void RDSDecoder::process(uint8_t* symbols, int count) {
void Decoder::process(uint8_t* symbols, int count) {
for (int i = 0; i < count; i++) {
// Shift in the bit
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
@ -54,18 +148,26 @@ namespace rds {
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT);
}
// Save block while correcting errors (NOT YET)
// Save block while correcting errors (NOT YET) <- idk why the "not yet is here", TODO: find why
blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
// Update continous group count
if (type == BLOCK_TYPE_A) { contGroup = 1; }
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; }
// If block type is A, decode it directly, otherwise, update continous count
if (type == BLOCK_TYPE_A) {
decodeBlockA();
}
else if (type == BLOCK_TYPE_B) { contGroup = 1; }
else if ((type == BLOCK_TYPE_C || type == BLOCK_TYPE_CP) && lastType == BLOCK_TYPE_B) { contGroup++; }
else if (type == BLOCK_TYPE_D && (lastType == BLOCK_TYPE_C || lastType == BLOCK_TYPE_CP)) { contGroup++; }
else { contGroup = 0; }
else {
// If block B is available, decode it alone.
if (contGroup == 1) {
decodeBlockB();
}
contGroup = 0;
}
// If we've got an entire group, process it
if (contGroup >= 4) {
if (contGroup >= 3) {
contGroup = 0;
decodeGroup();
}
@ -76,7 +178,7 @@ namespace rds {
}
}
uint16_t RDSDecoder::calcSyndrome(uint32_t block) {
uint16_t Decoder::calcSyndrome(uint32_t block) {
uint16_t syn = 0;
// Calculate the syndrome using a LFSR
@ -95,7 +197,7 @@ namespace rds {
return syn;
}
uint32_t RDSDecoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
uint32_t Decoder::correctErrors(uint32_t block, BlockType type, bool& recovered) {
// Subtract the offset from block
block ^= (uint32_t)OFFSETS[type];
uint32_t out = block;
@ -124,96 +226,264 @@ namespace rds {
return out;
}
void RDSDecoder::decodeGroup() {
std::lock_guard<std::mutex> lck(groupMtx);
auto now = std::chrono::high_resolution_clock::now();
anyGroupLastUpdate = now;
void Decoder::decodeBlockA() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockAMtx);
// Make sure blocks A and B are available
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; }
// If it didn't decode properly return
if (!blockAvail[BLOCK_TYPE_A]) { return; }
// Decode PI code
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF;
callsign = decodeCallsign(piCode);
// Update timeout
blockALastUpdate = std::chrono::high_resolution_clock::now();;
}
void Decoder::decodeBlockB() {
// Acquire lock
std::lock_guard<std::mutex> lck(blockBMtx);
// If it didn't decode properly return (TODO: Make sure this is not needed)
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode group type and version
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
// Decode traffic program and program type
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
if (groupType == 0) {
group0LastUpdate = now;
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Update timeout
blockBLastUpdate = std::chrono::high_resolution_clock::now();
}
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
void Decoder::decodeGroup0() {
// Acquire lock
std::lock_guard<std::mutex> lck(group0Mtx);
// Decode Block B data
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
uint8_t offset = ((blocks[BLOCK_TYPE_B] >> 10) & 0b11);
uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2;
// Decode Block C data
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group0LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup2() {
// Acquire lock
std::lock_guard<std::mutex> lck(group2Mtx);
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
}
rtAB = nAB;
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
// Write DI bit to the decoder identification
decoderIdent &= ~(1 << diOffset);
decoderIdent |= (diBit << diOffset);
// Write chars at offset the PSName
if (blockAvail[BLOCK_TYPE_D]) {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else if (groupType == 2) {
group2LastUpdate = now;
// Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
// Clear text field if the A/B flag changed
if (nAB != rtAB) {
radioText = " ";
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
rtAB = nAB;
}
// Write char at offset in Radiotext
if (groupVer == GROUP_VER_A) {
uint8_t rtOffset = offset * 4;
if (blockAvail[BLOCK_TYPE_C]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset + 2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
// Update timeout
group2LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup10() {
// Acquire lock
std::lock_guard<std::mutex> lck(group10Mtx);
// Check if the text needs to be cleared
bool ab = (blocks[BLOCK_TYPE_B] >> 14) & 1;
if (ab != ptnAB) {
programTypeName = " ";
}
ptnAB = ab;
// Decode segment address
bool addr = (blocks[BLOCK_TYPE_B] >> 10) & 1;
// Save text depending on address
if (addr) {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[4] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[5] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[6] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[7] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
else {
if (blockAvail[BLOCK_TYPE_C]) {
programTypeName[0] = (blocks[BLOCK_TYPE_C] >> 18) & 0xFF;
programTypeName[1] = (blocks[BLOCK_TYPE_C] >> 10) & 0xFF;
}
if (blockAvail[BLOCK_TYPE_D]) {
programTypeName[2] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programTypeName[3] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
}
// Update timeout
group10LastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup() {
// Make sure blocks B is available
if (!blockAvail[BLOCK_TYPE_B]) { return; }
// Decode block B
decodeBlockB();
// Decode depending on group type
switch (groupType) {
case 0:
decodeGroup0();
break;
case 2:
decodeGroup2();
break;
case 10:
decodeGroup10();
break;
default:
break;
}
}
std::string Decoder::base26ToCall(uint16_t pi) {
// Determin first better based on offset
bool w = (pi >= 21672);
std::string callsign(w ? "W" : "K");
// Base25 decode the rest
std::string restStr;
int rest = pi - (w ? 21672 : 4096);
while (rest) {
restStr += 'A' + (rest % 26);
rest /= 26;
}
// Pad with As
while (restStr.size() < 3) {
restStr += 'A';
}
// Reorder chars
for (int i = restStr.size() - 1; i >= 0; i--) {
callsign += restStr[i];
}
return callsign;
}
std::string Decoder::decodeCallsign(uint16_t pi) {
if ((pi >> 8) == 0xAF) {
// AFXY -> XY00
return base26ToCall((pi & 0xFF) << 8);
}
else if ((pi >> 12) == 0xA) {
// AXYZ -> X0YZ
return base26ToCall((((pi >> 8) & 0xF) << 12) | (pi & 0xFF));
}
else if (pi >= 0x9950 && pi <= 0x9EFF) {
// 3 letter callsigns
if (THREE_LETTER_CALLS.find(pi) != THREE_LETTER_CALLS.end()) {
return THREE_LETTER_CALLS[pi];
}
else {
uint8_t rtOffset = offset * 2;
if (blockAvail[BLOCK_TYPE_D]) {
radioText[rtOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
}
return "Not Assigned";
}
}
else if (pi >= 0x1000 && pi <= 0x994F) {
// Normal encoding
if ((pi & 0xFF) == 0 || ((pi >> 8) & 0xF) == 0) {
return "Not Assigned";
}
else {
return base26ToCall(pi);
}
}
else if (pi >= 0xB000 && pi <= 0xEFFF) {
uint16_t _pi = ((pi >> 12) << 8) | (pi & 0xFF);
if (NAT_LOC_LINKED_STATIONS.find(_pi) != NAT_LOC_LINKED_STATIONS.end()) {
return NAT_LOC_LINKED_STATIONS[_pi];
}
else {
return "Not Assigned";
}
}
else {
return "Not Assigned";
}
}
bool RDSDecoder::anyGroupValid() {
bool Decoder::blockAValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS;
}
bool RDSDecoder::group0Valid() {
bool Decoder::blockBValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS;
}
bool RDSDecoder::group2Valid() {
bool Decoder::group0Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < 5000.0;
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < RDS_GROUP_0_TIMEOUT_MS;
}
bool Decoder::group2Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group2LastUpdate)).count() < RDS_GROUP_2_TIMEOUT_MS;
}
bool Decoder::group10Valid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group10LastUpdate)).count() < RDS_GROUP_10_TIMEOUT_MS;
}
}

View File

@ -4,6 +4,12 @@
#include <chrono>
#include <mutex>
#define RDS_BLOCK_A_TIMEOUT_MS 5000.0
#define RDS_BLOCK_B_TIMEOUT_MS 5000.0
#define RDS_GROUP_0_TIMEOUT_MS 5000.0
#define RDS_GROUP_2_TIMEOUT_MS 5000.0
#define RDS_GROUP_10_TIMEOUT_MS 5000.0
namespace rds {
enum BlockType {
BLOCK_TYPE_A,
@ -20,22 +26,42 @@ namespace rds {
};
enum AreaCoverage {
AREA_COVERAGE_LOCAL,
AREA_COVERAGE_INTERNATIONAL,
AREA_COVERAGE_NATIONAL,
AREA_COVERAGE_SUPRA_NATIONAL,
AREA_COVERAGE_REGIONAL1,
AREA_COVERAGE_REGIONAL2,
AREA_COVERAGE_REGIONAL3,
AREA_COVERAGE_REGIONAL4,
AREA_COVERAGE_REGIONAL5,
AREA_COVERAGE_REGIONAL6,
AREA_COVERAGE_REGIONAL7,
AREA_COVERAGE_REGIONAL8,
AREA_COVERAGE_REGIONAL9,
AREA_COVERAGE_REGIONAL10,
AREA_COVERAGE_REGIONAL11,
AREA_COVERAGE_REGIONAL12
AREA_COVERAGE_INVALID = -1,
AREA_COVERAGE_LOCAL = 0,
AREA_COVERAGE_INTERNATIONAL = 1,
AREA_COVERAGE_NATIONAL = 2,
AREA_COVERAGE_SUPRA_NATIONAL = 3,
AREA_COVERAGE_REGIONAL1 = 4,
AREA_COVERAGE_REGIONAL2 = 5,
AREA_COVERAGE_REGIONAL3 = 6,
AREA_COVERAGE_REGIONAL4 = 7,
AREA_COVERAGE_REGIONAL5 = 8,
AREA_COVERAGE_REGIONAL6 = 9,
AREA_COVERAGE_REGIONAL7 = 10,
AREA_COVERAGE_REGIONAL8 = 11,
AREA_COVERAGE_REGIONAL9 = 12,
AREA_COVERAGE_REGIONAL10 = 13,
AREA_COVERAGE_REGIONAL11 = 14,
AREA_COVERAGE_REGIONAL12 = 15
};
inline const char* AREA_COVERAGE_TO_STR[] = {
"Local",
"International",
"National",
"Supra-National",
"Regional 1",
"Regional 2",
"Regional 3",
"Regional 4",
"Regional 5",
"Regional 6",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional 10",
"Regional 11",
"Regional 12",
};
enum ProgramType {
@ -108,6 +134,76 @@ namespace rds {
PROGRAM_TYPE_EU_ALARM = 31
};
inline const char* PROGRAM_TYPE_EU_TO_STR[] = {
"None",
"News",
"Current Affairs",
"Information",
"Sports",
"Education",
"Drama",
"Culture",
"Science",
"Varied",
"Pop Music",
"Rock Music",
"Easy Listening Music",
"Light Classical",
"Serious Classical",
"Other Music",
"Weather",
"Finance",
"Children Program",
"Social Affairs",
"Religion",
"Phone-in",
"Travel",
"Leisure",
"Jazz Music",
"Country Music",
"National Music",
"Oldies Music",
"Folk Music",
"Documentary",
"Alarm Test",
"Alarm",
};
inline const char* PROGRAM_TYPE_US_TO_STR[] = {
"None",
"News",
"Information",
"Sports",
"Talk",
"Rock",
"Classic Rock",
"Adult Hits",
"Soft Rock",
"Top 40",
"Country",
"Oldies",
"Soft",
"Nostalgia",
"Jazz",
"Classical",
"Rythm and Blues",
"Soft Rythm and Blues",
"Foreign Language",
"Religious Music",
"Religious Talk",
"Personality",
"Public",
"College",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Unassigned",
"Weather",
"Emergency Test",
"Emergency",
};
enum DecoderIdentification {
DECODER_IDENT_STEREO = (1 << 0),
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
@ -115,35 +211,49 @@ namespace rds {
DECODER_IDENT_VARIABLE_PTY = (1 << 0)
};
class RDSDecoder {
class Decoder {
public:
void process(uint8_t* symbols, int count);
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; }
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; }
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; }
bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(group0Mtx); return music; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(group0Mtx); return programServiceName; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(group2Mtx); return group2Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(group2Mtx); return radioText; }
bool programTypeNameValid() { std::lock_guard<std::mutex> lck(group10Mtx); return group10Valid(); }
std::string getProgramTypeName() { std::lock_guard<std::mutex> lck(group10Mtx); return programTypeName; }
private:
static uint16_t calcSyndrome(uint32_t block);
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
void decodeBlockA();
void decodeBlockB();
void decodeGroup0();
void decodeGroup2();
void decodeGroup10();
void decodeGroup();
bool anyGroupValid();
static std::string base26ToCall(uint16_t pi);
static std::string decodeCallsign(uint16_t pi);
bool blockAValid();
bool blockBValid();
bool group0Valid();
bool group2Valid();
bool group10Valid();
// State machine
uint32_t shiftReg = 0;
@ -154,17 +264,26 @@ namespace rds {
uint32_t blocks[_BLOCK_TYPE_COUNT];
bool blockAvail[_BLOCK_TYPE_COUNT];
// All groups
std::mutex groupMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate;
// Block A (All groups)
std::mutex blockAMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
uint16_t piCode;
uint8_t countryCode;
AreaCoverage programCoverage;
uint8_t programRefNumber;
std::string callsign;
// Block B (All groups)
std::mutex blockBMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> blockBLastUpdate{}; // 1970-01-01
uint8_t groupType;
GroupVersion groupVer;
bool trafficProgram;
ProgramType programType;
// Group type 0
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate;
std::mutex group0Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group0LastUpdate{}; // 1970-01-01
bool trafficAnnouncement;
bool music;
uint8_t decoderIdent;
@ -172,9 +291,16 @@ namespace rds {
std::string programServiceName = " ";
// Group type 2
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate;
std::mutex group2Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group2LastUpdate{}; // 1970-01-01
bool rtAB = false;
std::string radioText = " ";
// Group type 10
std::mutex group10Mtx;
std::chrono::time_point<std::chrono::high_resolution_clock> group10LastUpdate{}; // 1970-01-01
bool ptnAB = false;
std::string programTypeName = " ";
};
}

View File

@ -0,0 +1,102 @@
#pragma once
#include <dsp/processor.h>
#include <dsp/loop/fast_agc.h>
#include <dsp/loop/costas.h>
#include <dsp/taps/band_pass.h>
#include <dsp/filter/fir.h>
#include <dsp/convert/complex_to_real.h>
#include <dsp/clock_recovery/mm.h>
#include <dsp/digital/binary_slicer.h>
#include <dsp/digital/differential_decoder.h>
class RDSDemod : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
RDSDemod() {}
RDSDemod(dsp::stream<dsp::complex_t>* in, bool enableSoft) { init(in, enableSoft); }
~RDSDemod() {}
void init(dsp::stream<dsp::complex_t>* in, bool enableSoft) {
// Save config
this->enableSoft = enableSoft;
// Initialize the DSP
agc.init(NULL, 1.0, 1e6, 0.1);
costas.init(NULL, 0.005f);
taps = dsp::taps::bandPass<dsp::complex_t>(0, 2375, 100, 5000);
fir.init(NULL, taps);
double baudfreq = dsp::math::hzToRads(2375.0/2.0, 5000);
costas2.init(NULL, 0.01, 0.0, baudfreq, baudfreq - (baudfreq*0.1), baudfreq + (baudfreq*0.1));
recov.init(NULL, 5000.0 / (2375.0 / 2.0), 1e-6, 0.01, 0.01);
diff.init(NULL, 2);
// Free useless buffers
agc.out.free();
fir.out.free();
costas2.out.free();
recov.out.free();
// Init the rest
base_type::init(in);
}
void setSoftEnabled(bool enable) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
enableSoft = enable;
base_type::tempStart();
}
void reset() {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
agc.reset();
costas.reset();
fir.reset();
costas2.reset();
recov.reset();
diff.reset();
base_type::tempStart();
}
inline int process(int count, dsp::complex_t* in, float* softOut, uint8_t* hardOut) {
count = agc.process(count, in, costas.out.readBuf);
count = costas.process(count, costas.out.readBuf, costas.out.writeBuf);
count = fir.process(count, costas.out.writeBuf, costas.out.writeBuf);
count = costas2.process(count, costas.out.writeBuf, costas.out.readBuf);
count = dsp::convert::ComplexToReal::process(count, costas.out.readBuf, softOut);
count = recov.process(count, softOut, softOut);
count = dsp::digital::BinarySlicer::process(count, softOut, diff.out.readBuf);
count = diff.process(count, diff.out.readBuf, hardOut);
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = process(count, base_type::_in->readBuf, soft.writeBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
if (enableSoft) {
if (!soft.swap(count)) { return -1; }
}
return count;
}
dsp::stream<float> soft;
private:
bool enableSoft = false;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::loop::Costas<2> costas;
dsp::tap<dsp::complex_t> taps;
dsp::filter::FIR<dsp::complex_t, dsp::complex_t> fir;
dsp::loop::Costas<2> costas2;
dsp::clock_recovery::MM<float> recov;
dsp::digital::DifferentialDecoder diff;
};

View File

@ -0,0 +1,4 @@
FROM debian:bookworm
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev'

View File

@ -4,21 +4,31 @@ cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -4,21 +4,31 @@ cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -4,22 +4,32 @@ cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev, libzstd-dev'
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'

View File

@ -10,15 +10,15 @@ echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://ap
apt update
# Install dependencies and tools
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libsoapysdr-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk1-dev libzstd-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev libudev-dev
libcodec2-dev libudev-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install a more recent libusb version
@ -41,6 +41,16 @@ make install
ldconfig
cd ../../
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
# Fix missing .pc file for codec2
echo 'prefix=/usr/' >> /usr/share/pkgconfig/codec2.pc
echo 'libdir=/usr/include/x86_64-linux-gnu/' >> /usr/share/pkgconfig/codec2.pc
@ -56,7 +66,7 @@ echo 'Cflags: -I/usr/include/codec2' >> /usr/share/pkgconfig/codec2.pc
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_BLADERF_SOURCE=OFF -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_OVERRIDE_STD_FILESYSTEM=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
# Generate package

View File

@ -4,21 +4,31 @@ cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -4,21 +4,31 @@ cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libsoapysdr-dev libairspyhf-dev libairspy-dev \
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk2-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1.run
7z x ./SDRplay_RSP_API-Linux-3.07.1
cp x86_64/libsdrplay_api.so.3.07 /usr/lib/libsdrplay_api.so
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..

View File

@ -0,0 +1,4 @@
FROM ubuntu:mantic
ENV DEBIAN_FRONTEND=noninteractive
COPY do_build.sh /root
RUN chmod +x /root/do_build.sh

View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
cd /root
# Install dependencies and tools
apt update
apt install -y build-essential cmake git libfftw3-dev libglfw3-dev libvolk-dev libzstd-dev libairspyhf-dev libairspy-dev \
libiio-dev libad9361-dev librtaudio-dev libhackrf-dev librtlsdr-dev libbladerf-dev liblimesuite-dev p7zip-full wget portaudio19-dev \
libcodec2-dev autoconf libtool xxd
# Install SDRPlay libraries
wget https://www.sdrplay.com/software/SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0.run
7z x ./SDRplay_RSP_API-Linux-3.14.0
cp x86_64/libsdrplay_api.so.3.14 /usr/lib/libsdrplay_api.so
cp inc/* /usr/include/
# Install libperseus
git clone https://github.com/Microtelecom/libperseus-sdr
cd libperseus-sdr
autoreconf -i
./configure
make
make install
ldconfig
cd ..
cd SDRPlusPlus
mkdir build
cd build
cmake .. -DOPT_BUILD_BLADERF_SOURCE=ON -DOPT_BUILD_LIMESDR_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON -DOPT_BUILD_PERSEUS_SOURCE=ON
make VERBOSE=1 -j2
cd ..
sh make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk-dev, librtaudio-dev, libzstd-dev'

View File

@ -27,6 +27,7 @@ bundle_is_not_to_be_installed() {
if [ "$1" = "SystemConfiguration" ]; then echo 1; fi
if [ "$1" = "Security" ]; then echo 1; fi
if [ "$1" = "AppleFSCompression" ]; then echo 1; fi
if [ "$1" = "libsdrplay_api.so.3.14" ]; then echo 1; fi
}
# ========================= FOR INTERNAL USE ONLY =========================
@ -81,7 +82,7 @@ bundle_find_full_path() {
# Correct dep path
echo $RPATH/$RPATH_NEXT
return
return -1
done
# Search other common paths

View File

@ -38,13 +38,13 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hackrf_source/hackrf_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/hermes_source/hermes_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/limesdr_source/limesdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/perseus_source/perseus_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/plutosdr_source/plutosdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rfspace_source/rfspace_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_sdr_source/rtl_sdr_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/rtl_tcp_source/rtl_tcp_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrplay_source/sdrplay_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/sdrpp_server_source/sdrpp_server_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/soapy_source/soapy_source.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/spyserver_source/spyserver_source.dylib
# bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/source_modules/usrp_source/usrp_source.dylib
@ -62,6 +62,7 @@ bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/decoder_module
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/discord_integration/discord_integration.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/frequency_manager/frequency_manager.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/recorder/recorder.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_client/rigctl_client.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/rigctl_server/rigctl_server.dylib
bundle_install_binary $BUNDLE $BUNDLE/Contents/Plugins $BUILD_DIR/misc_modules/scanner/scanner.dylib

View File

@ -32,6 +32,9 @@ cp $build_dir/source_modules/hermes_source/Release/hermes_source.dll sdrpp_windo
cp $build_dir/source_modules/limesdr_source/Release/limesdr_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/LimeSuite.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/perseus_source/Release/perseus_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/perseus-sdr.dll' sdrpp_windows_x64/
cp $build_dir/source_modules/plutosdr_source/Release/plutosdr_source.dll sdrpp_windows_x64/modules/
cp 'C:/Program Files/PothosSDR/bin/libiio.dll' sdrpp_windows_x64/
cp 'C:/Program Files/PothosSDR/bin/libad9361.dll' sdrpp_windows_x64/
@ -48,8 +51,6 @@ cp 'C:/Program Files/SDRplay/API/x64/sdrplay_api.dll' sdrpp_windows_x64/ -ErrorA
cp $build_dir/source_modules/sdrpp_server_source/Release/sdrpp_server_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/soapy_source/Release/soapy_source.dll sdrpp_windows_x64/modules/
cp $build_dir/source_modules/spyserver_source/Release/spyserver_source.dll sdrpp_windows_x64/modules/
# cp $build_dir/source_modules/usrp_source/Release/usrp_source.dll sdrpp_windows_x64/modules/
@ -76,6 +77,8 @@ cp $build_dir/misc_modules/discord_integration/Release/discord_integration.dll s
cp $build_dir/misc_modules/frequency_manager/Release/frequency_manager.dll sdrpp_windows_x64/modules/
cp $build_dir/misc_modules/iq_exporter/Release/iq_exporter.dll sdrpp_windows_x64/modules/
cp $build_dir/misc_modules/recorder/Release/recorder.dll sdrpp_windows_x64/modules/
cp $build_dir/misc_modules/rigctl_client/Release/rigctl_client.dll sdrpp_windows_x64/modules/

View File

@ -485,7 +485,7 @@ private:
}
// Bookmark list
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Bookmark");
ImGui::TableSetupScrollFreeze(2, 1);
@ -531,7 +531,7 @@ private:
ImGui::TableSetColumnIndex(0);
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
_this->importOpen = true;
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, pfd::opt::multiselect);
}
ImGui::TableSetColumnIndex(1);
@ -544,7 +544,7 @@ private:
}
config.release();
_this->exportOpen = true;
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" });
}
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
ImGui::EndTable();
@ -787,7 +787,7 @@ private:
void exportBookmarks(std::string path) {
std::ofstream fs(path);
exportedBookmarks >> fs;
fs << exportedBookmarks;
fs.close();
}

View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.13)
project(iq_exporter)
file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})

View File

@ -0,0 +1,590 @@
#include <utils/net.h>
#include <imgui.h>
#include <module.h>
#include <gui/gui.h>
#include <gui/style.h>
#include <utils/optionlist.h>
#include <algorithm>
#include <dsp/sink/handler_sink.h>
#include <volk/volk.h>
#include <signal_path/signal_path.h>
#include <dsp/buffer/reshaper.h>
#include <gui/dialogs/dialog_box.h>
#include <core.h>
SDRPP_MOD_INFO{
/* Name: */ "iq_exporter",
/* Description: */ "Export raw IQ through TCP or UDP",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
enum Mode {
MODE_NONE = -1,
MODE_BASEBAND,
MODE_VFO
};
enum Protocol {
PROTOCOL_TCP_SERVER,
PROTOCOL_TCP_CLIENT,
PROTOCOL_UDP
};
enum SampleType {
SAMPLE_TYPE_INT8,
SAMPLE_TYPE_INT16,
SAMPLE_TYPE_INT32,
SAMPLE_TYPE_FLOAT32
};
class IQExporterModule : public ModuleManager::Instance {
public:
IQExporterModule(std::string name) {
this->name = name;
// Define operating modes
modes.define("Baseband", MODE_BASEBAND);
modes.define("VFO", MODE_VFO);
// Define VFO samplerates
for (int i = 3000; i <= 192000; i <<= 1) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 250000; i < 1000000; i += 250000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 1000000; i < 10000000; i += 500000) {
samplerates.define(i, getSrScaled(i), i);
}
for (int i = 10000000; i <= 100000000; i += 5000000) {
samplerates.define(i, getSrScaled(i), i);
}
// Define protocols
protocols.define("TCP (Server)", PROTOCOL_TCP_SERVER);
protocols.define("TCP (Client)", PROTOCOL_TCP_CLIENT);
protocols.define("UDP", PROTOCOL_UDP);
// Define sample types
sampleTypes.define("Int8", SAMPLE_TYPE_INT8);
sampleTypes.define("Int16", SAMPLE_TYPE_INT16);
sampleTypes.define("Int32", SAMPLE_TYPE_INT32);
sampleTypes.define("Float32", SAMPLE_TYPE_FLOAT32);
// Define packet sizes
for (int i = 8; i <= 32768; i <<= 1) {
char buf[16];
sprintf(buf, "%d Bytes", i);
packetSizes.define(i, buf, i);
}
// Load config
bool autoStart = false;
Mode nMode = MODE_BASEBAND;
config.acquire();
if (config.conf[name].contains("mode")) {
std::string modeStr = config.conf[name]["mode"];
if (modes.keyExists(modeStr)) { nMode = modes.value(modes.keyId(modeStr)); }
}
if (config.conf[name].contains("samplerate")) {
int sr = config.conf[name]["samplerate"];
if (samplerates.keyExists(sr)) { samplerate = samplerates.value(samplerates.keyId(sr)); }
}
if (config.conf[name].contains("protocol")) {
std::string protoStr = config.conf[name]["protocol"];
if (protocols.keyExists(protoStr)) { proto = protocols.value(protocols.keyId(protoStr)); }
}
if (config.conf[name].contains("sampleType")) {
std::string sampTypeStr = config.conf[name]["sampleType"];
if (sampleTypes.keyExists(sampTypeStr)) { sampType = sampleTypes.value(sampleTypes.keyId(sampTypeStr)); }
}
if (config.conf[name].contains("packetSize")) {
int size = config.conf[name]["packetSize"];
if (packetSizes.keyExists(size)) { packetSize = packetSizes.value(packetSizes.keyId(size)); }
}
if (config.conf[name].contains("host")) {
std::string hostStr = config.conf[name]["host"];
strcpy(hostname, hostStr.c_str());
}
if (config.conf[name].contains("port")) {
port = config.conf[name]["port"];
port = std::clamp<int>(port, 1, 65535);
}
if (config.conf[name].contains("running")) {
autoStart = config.conf[name]["running"];
}
config.release();
// Set menu IDs
modeId = modes.valueId(nMode);
srId = samplerates.valueId(samplerate);
protoId = protocols.valueId(proto);
sampTypeId = sampleTypes.valueId(sampType);
packetSizeId = packetSizes.valueId(packetSize);
// Allocate buffer
buffer = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE * sizeof(dsp::complex_t));
// Init DSP
reshape.init(&iqStream, packetSize/sampleSize(), 0);
handler.init(&reshape.out, dataHandler, this);
// Set operating mode
setMode(nMode);
// Start if needed
if (autoStart) { start(); }
// Register menu entry
gui::menu.registerEntry(name, menuHandler, this, this);
}
~IQExporterModule() {
// Un-register menu entry
gui::menu.removeEntry(name);
// Stop networking
stop();
// Stop DSP
setMode(MODE_NONE);
// Free buffer
dsp::buffer::free(buffer);
}
void postInit() {}
void enable() {
// Rebind streams and start DSP
setMode(mode, true);
// Restart networking if it was running
if (wasRunning) { start(); }
// Mark as running
enabled = true;
}
void disable() {
// Save running state
wasRunning = running;
// Stop networking
stop();
// Stop the DSP and unbind streams
setMode(MODE_NONE);
// Mark as disabled
enabled = false;
}
bool isEnabled() {
return enabled;
}
void start() {
if (running) { return; }
// Acquire lock on the socket
std::lock_guard lck1(sockMtx);
// Start listening or open UDP socket
try {
if (proto == PROTOCOL_TCP_SERVER) {
// Create listener
listener = net::listen(hostname, port);
// Start listen worker
listenWorkerThread = std::thread(&IQExporterModule::listenWorker, this);
}
else if (proto == PROTOCOL_TCP_CLIENT) {
// Connect to TCP server
sock = net::connect(hostname, port);
}
else {
// Open UDP socket
sock = net::openudp(hostname, port, "0.0.0.0", 0, true);
}
}
catch (const std::exception& e) {
flog::error("[IQExporter] Could not start socket: {}", e.what());
errorStr = e.what();
showError = true;
return;
}
running = true;
}
void stop() {
if (!running) { return; }
// Acquire lock on the socket
std::lock_guard lck1(sockMtx);
// Stop listening or close UDP socket
if (proto == PROTOCOL_TCP_SERVER) {
// Stop listener
if (listener) {
listener->stop();
}
// Wait for worker to stop
if (listenWorkerThread.joinable()) { listenWorkerThread.join(); }
// Free listener
listener.reset();
// Close socket and free it
if (sock) {
sock->close();
sock.reset();
}
}
else {
// Close socket and free it
if (sock) {
sock->close();
sock.reset();
}
}
running = false;
}
private:
std::string getSrScaled(double sr) {
char buf[1024];
if (sr >= 1000000.0) {
sprintf(buf, "%.1lf MS/s", sr / 1000000.0);
}
else if (sr >= 1000.0) {
sprintf(buf, "%.1lf KS/s", sr / 1000.0);
}
else {
sprintf(buf, "%.1lf S/s", sr);
}
return std::string(buf);
}
static void menuHandler(void* ctx) {
IQExporterModule* _this = (IQExporterModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
// Error message box
ImGui::GenericDialog("##iq_exporter_err_", _this->showError, GENERIC_DIALOG_BUTTONS_OK, [=](){
ImGui::Text("Error: %s", _this->errorStr.c_str());
});
if (!_this->enabled) { ImGui::BeginDisabled(); }
if (_this->running) { ImGui::BeginDisabled(); }
// Mode selector
ImGui::LeftLabel("Mode");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_mode_" + _this->name).c_str(), &_this->modeId, _this->modes.txt)) {
_this->setMode(_this->modes.value(_this->modeId));
config.acquire();
config.conf[_this->name]["mode"] = _this->modes.key(_this->modeId);
config.release(true);
}
// In VFO mode, show samplerate selector
if (_this->mode == MODE_VFO) {
ImGui::LeftLabel("Samplerate");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_sr_" + _this->name).c_str(), &_this->srId, _this->samplerates.txt)) {
_this->samplerate = _this->samplerates.value(_this->srId);
if (_this->vfo) {
_this->vfo->setBandwidthLimits(_this->samplerate, _this->samplerate, true);
_this->vfo->setSampleRate(_this->samplerate, _this->samplerate);
}
config.acquire();
config.conf[_this->name]["samplerate"] = _this->samplerates.key(_this->srId);
config.release(true);
}
}
// Mode protocol selector
ImGui::LeftLabel("Protocol");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_proto_" + _this->name).c_str(), &_this->protoId, _this->protocols.txt)) {
_this->proto = _this->protocols.value(_this->protoId);
config.acquire();
config.conf[_this->name]["protocol"] = _this->protocols.key(_this->protoId);
config.release(true);
}
// Sample type selector
ImGui::LeftLabel("Sample type");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_samp_" + _this->name).c_str(), &_this->sampTypeId, _this->sampleTypes.txt)) {
_this->sampType = _this->sampleTypes.value(_this->sampTypeId);
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
config.acquire();
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampTypeId);
config.release(true);
}
// Packet size selector
ImGui::LeftLabel("Packet size");
ImGui::FillWidth();
if (ImGui::Combo(("##iq_exporter_pkt_sz_" + _this->name).c_str(), &_this->packetSizeId, _this->packetSizes.txt)) {
_this->packetSize = _this->packetSizes.value(_this->packetSizeId);
_this->reshape.setKeep(_this->packetSize/_this->sampleSize());
config.acquire();
config.conf[_this->name]["packetSize"] = _this->packetSizes.key(_this->packetSizeId);
config.release(true);
}
// Hostname and port field
if (ImGui::InputText(("##iq_exporter_host_" + _this->name).c_str(), _this->hostname, sizeof(_this->hostname))) {
config.acquire();
config.conf[_this->name]["host"] = _this->hostname;
config.release(true);
}
ImGui::SameLine();
ImGui::FillWidth();
if (ImGui::InputInt(("##iq_exporter_port_" + _this->name).c_str(), &_this->port, 0, 0)) {
_this->port = std::clamp<int>(_this->port, 1, 65535);
config.acquire();
config.conf[_this->name]["port"] = _this->port;
config.release(true);
}
if (_this->running) { ImGui::EndDisabled(); }
// Start/Stop buttons
if (_this->running || (!_this->enabled && _this->wasRunning)) {
if (ImGui::Button(("Stop##iq_exporter_stop_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
_this->stop();
config.acquire();
config.conf[_this->name]["running"] = false;
config.release(true);
}
}
else {
if (ImGui::Button(("Start##iq_exporter_start_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
_this->start();
config.acquire();
config.conf[_this->name]["running"] = true;
config.release(true);
}
}
// Check if the socket is open by attempting a read
bool sockOpen;
{
uint8_t dummy;
sockOpen = !(!_this->sock || !_this->sock->isOpen() || (_this->proto != PROTOCOL_UDP && _this->sock->recv(&dummy, 1, false, net::NONBLOCKING) == 0));
}
// Status text
ImGui::TextUnformatted("Status:");
ImGui::SameLine();
if (sockOpen) {
ImGui::TextColored(ImVec4(0.0, 1.0, 0.0, 1.0), (_this->proto == PROTOCOL_TCP_SERVER || _this->proto == PROTOCOL_TCP_CLIENT) ? "Connected" : "Sending");
}
else if (_this->listener && _this->listener->listening()) {
ImGui::TextColored(ImVec4(1.0, 1.0, 0.0, 1.0), "Listening");
}
else if (!_this->enabled) {
ImGui::TextUnformatted("Disabled");
}
else {
// If we're idle and still supposed to be running, the server has closed the connection (TODO: kinda jank...)
if (_this->running) { _this->stop(); }
ImGui::TextUnformatted("Idle");
}
if (!_this->enabled) { ImGui::EndDisabled(); }
}
void setMode(Mode newMode, bool forceSet = false) {
// If there is no mode to change, do nothing
if (!forceSet && mode == newMode) { return; }
// Stop the DSP
reshape.stop();
handler.stop();
// Delete VFO or unbind IQ stream
if (vfo) {
sigpath::vfoManager.deleteVFO(vfo);
vfo = NULL;
}
if (streamBound) {
sigpath::iqFrontEnd.unbindIQStream(&iqStream);
streamBound = false;
}
// If the mode was none, we're done
if (newMode == MODE_NONE) { return; }
// Create VFO or bind IQ stream
if (newMode == MODE_VFO) {
// Create VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, samplerate, samplerate, samplerate, samplerate, true);
// Set its output as the input to the DSP
reshape.setInput(vfo->output);
}
else {
// Bind IQ stream
sigpath::iqFrontEnd.bindIQStream(&iqStream);
streamBound = true;
// Set its output as the input to the DSP
reshape.setInput(&iqStream);
}
// Start DSP
reshape.start();
handler.start();
// Update mode
mode = newMode;
modeId = modes.valueId(newMode);
}
void listenWorker() {
while (true) {
// Accept a client
auto newSock = listener->accept();
if (!newSock) { break; }
// Update socket
{
std::lock_guard lck(sockMtx);
sock = newSock;
}
}
}
int sampleSize() {
switch (sampType) {
case SAMPLE_TYPE_INT8:
return sizeof(int8_t)*2;
case SAMPLE_TYPE_INT16:
return sizeof(int16_t)*2;
case SAMPLE_TYPE_INT32:
return sizeof(int32_t)*2;
case SAMPLE_TYPE_FLOAT32:
return sizeof(dsp::complex_t);
default:
return -1;
}
}
static void dataHandler(dsp::complex_t* data, int count, void* ctx) {
IQExporterModule* _this = (IQExporterModule*)ctx;
// Try to cquire lock on socket
if (!_this->sockMtx.try_lock()) { return; }
// If not valid or open, give uo
if (!_this->sock || !_this->sock->isOpen()) {
// Unlock socket mutex
_this->sockMtx.unlock();
return;
}
// Convert the samples or send directory for float32
int size;
switch (_this->sampType) {
case SAMPLE_TYPE_INT8:
volk_32f_s32f_convert_8i((int8_t*)_this->buffer, (float*)data, 128.0f, count*2);
size = sizeof(int8_t)*2;
break;
case SAMPLE_TYPE_INT16:
volk_32f_s32f_convert_16i((int16_t*)_this->buffer, (float*)data, 32768.0f, count*2);
size = sizeof(int16_t)*2;
break;
case SAMPLE_TYPE_INT32:
volk_32f_s32f_convert_32i((int32_t*)_this->buffer, (float*)data, 2147483647.0f, count*2);
size = sizeof(int32_t)*2;
break;
case SAMPLE_TYPE_FLOAT32:
_this->sock->send((uint8_t*)data, count*sizeof(dsp::complex_t));
default:
// Unlock socket mutex
_this->sockMtx.unlock();
return;
}
// Send converted samples
_this->sock->send(_this->buffer, count*size);
// Unlock socket mutex
_this->sockMtx.unlock();
}
std::string name;
bool enabled = true;
Mode mode = MODE_NONE;
int modeId;
int samplerate = 1000000.0;
int srId;
Protocol proto = PROTOCOL_TCP_SERVER;
int protoId;
SampleType sampType = SAMPLE_TYPE_INT16;
int sampTypeId;
int packetSize = 1024;
int packetSizeId;
char hostname[1024] = "localhost";
int port = 1234;
bool running = false;
bool wasRunning = false;
bool showError = false;
std::string errorStr = "";
OptionList<std::string, Mode> modes;
OptionList<int, int> samplerates;
OptionList<std::string, Protocol> protocols;
OptionList<std::string, SampleType> sampleTypes;
OptionList<int, int> packetSizes;
VFOManager::VFO* vfo = NULL;
bool streamBound = false;
dsp::stream<dsp::complex_t> iqStream;
dsp::buffer::Reshaper<dsp::complex_t> reshape;
dsp::sink::Handler<dsp::complex_t> handler;
uint8_t* buffer = NULL;
std::thread listenWorkerThread;
std::mutex sockMtx;
std::shared_ptr<net::Socket> sock;
std::shared_ptr<net::Listener> listener;
};
MOD_EXPORT void _INIT_() {
json def = json({});
std::string root = (std::string)core::args["root"];
config.setPath(root + "/iq_exporter_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new IQExporterModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (IQExporterModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -6,3 +6,4 @@ file(GLOB SRC "src/*.cpp")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(recorder PRIVATE "src/")
target_include_directories(recorder PRIVATE "../../decoder_modules/radio/src")

View File

@ -21,6 +21,7 @@
#include <core.h>
#include <utils/optionlist.h>
#include <utils/wav.h>
#include <radio_interface.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
@ -168,7 +169,7 @@ public:
// Open file
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? gui::waterfall.selectedVFO : "";
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
std::string extension = ".wav";
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
if (!writer.open(expandedPath)) {
@ -437,6 +438,17 @@ private:
if (dbLvl.r > lvl.r) { lvl.r = dbLvl.r; }
}
std::map<int, const char*> radioModeToString = {
{ RADIO_IFACE_MODE_NFM, "NFM" },
{ RADIO_IFACE_MODE_WFM, "WFM" },
{ RADIO_IFACE_MODE_AM, "AM" },
{ RADIO_IFACE_MODE_DSB, "DSB" },
{ RADIO_IFACE_MODE_USB, "USB" },
{ RADIO_IFACE_MODE_CW, "CW" },
{ RADIO_IFACE_MODE_LSB, "LSB" },
{ RADIO_IFACE_MODE_RAW, "RAW" }
};
std::string genFileName(std::string templ, std::string type, std::string name) {
// Get data
time_t now = time(0);
@ -455,6 +467,7 @@ private:
char dayStr[128];
char monStr[128];
char yearStr[128];
const char* modeStr = "Unknown";
sprintf(freqStr, "%.0lfHz", freq);
sprintf(hourStr, "%02d", ltm->tm_hour);
sprintf(minStr, "%02d", ltm->tm_min);
@ -462,6 +475,11 @@ private:
sprintf(dayStr, "%02d", ltm->tm_mday);
sprintf(monStr, "%02d", ltm->tm_mon + 1);
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
if (core::modComManager.getModuleName(name) == "radio") {
int mode;
core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
modeStr = radioModeToString[mode];
}
// Replace in template
templ = std::regex_replace(templ, std::regex("\\$t"), type);
@ -472,6 +490,7 @@ private:
templ = std::regex_replace(templ, std::regex("\\$d"), dayStr);
templ = std::regex_replace(templ, std::regex("\\$M"), monStr);
templ = std::regex_replace(templ, std::regex("\\$y"), yearStr);
templ = std::regex_replace(templ, std::regex("\\$r"), modeStr);
return templ;
}

View File

@ -80,13 +80,13 @@ public:
try {
client = net::rigctl::connect(host, port);
}
catch (std::exception e) {
flog::error("Could not connect: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not connect: {}", e.what());
return;
}
// Switch source to panadapter mode
sigpath::sourceManager.setPanadpterIF(ifFreq);
sigpath::sourceManager.setPanadapterIF(ifFreq);
sigpath::sourceManager.setTuningMode(SourceManager::TuningMode::PANADAPTER);
sigpath::sourceManager.onRetune.bindHandler(&_retuneHandler);
@ -131,7 +131,7 @@ private:
ImGui::FillWidth();
if (ImGui::InputDouble(CONCAT("##_rigctl_if_freq_", _this->name), &_this->ifFreq, 100.0, 100000.0, "%.0f")) {
if (_this->running) {
sigpath::sourceManager.setPanadpterIF(_this->ifFreq);
sigpath::sourceManager.setPanadapterIF(_this->ifFreq);
}
config.acquire();
config.conf[_this->name]["ifFreq"] = _this->ifFreq;

View File

@ -200,8 +200,8 @@ private:
listener = net::listen(hostname, port);
listener->acceptAsync(clientHandler, this);
}
catch (std::exception e) {
flog::error("Could not start rigctl server: {0}", e.what());
catch (const std::exception& e) {
flog::error("Could not start rigctl server: {}", e.what());
}
}
@ -333,6 +333,17 @@ private:
_this->client->readAsync(1024, _this->dataBuf, dataHandler, _this, false);
}
std::map<int, const char*> radioModeToString = {
{ RADIO_IFACE_MODE_NFM, "FM" },
{ RADIO_IFACE_MODE_WFM, "WFM" },
{ RADIO_IFACE_MODE_AM, "AM" },
{ RADIO_IFACE_MODE_DSB, "DSB" },
{ RADIO_IFACE_MODE_USB, "USB" },
{ RADIO_IFACE_MODE_CW, "CW" },
{ RADIO_IFACE_MODE_LSB, "LSB" },
{ RADIO_IFACE_MODE_RAW, "RAW" }
};
void commandHandler(std::string cmd) {
std::string corr = "";
std::vector<std::string> parts;
@ -442,38 +453,18 @@ private:
pos++;
}
const std::string& newModeStr = parts[1];
float newBandwidth = std::atoi(parts[2].c_str());
int newMode;
if (parts[1] == "FM") {
newMode = RADIO_IFACE_MODE_NFM;
}
else if (parts[1] == "WFM") {
newMode = RADIO_IFACE_MODE_WFM;
}
else if (parts[1] == "AM") {
newMode = RADIO_IFACE_MODE_AM;
}
else if (parts[1] == "DSB") {
newMode = RADIO_IFACE_MODE_DSB;
}
else if (parts[1] == "USB") {
newMode = RADIO_IFACE_MODE_USB;
}
else if (parts[1] == "CW") {
newMode = RADIO_IFACE_MODE_CW;
}
else if (parts[1] == "LSB") {
newMode = RADIO_IFACE_MODE_LSB;
}
else if (parts[1] == "RAW") {
newMode = RADIO_IFACE_MODE_RAW;
}
else {
auto it = std::find_if(radioModeToString.begin(), radioModeToString.end(), [&newModeStr](const auto& e) {
return e.second == newModeStr;
});
if (it == radioModeToString.end()) {
resp = "RPRT 1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
return;
}
int newMode = it->first;
// If tuning is enabled, set the mode and optionally the bandwidth
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio" && tuningEnabled) {
@ -492,31 +483,9 @@ private:
if (!selectedVfo.empty() && core::modComManager.getModuleName(selectedVfo) == "radio") {
int mode;
core::modComManager.callInterface(selectedVfo, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
if (mode == RADIO_IFACE_MODE_NFM) {
resp = "FM\n";
}
else if (mode == RADIO_IFACE_MODE_WFM) {
resp = "WFM\n";
}
else if (mode == RADIO_IFACE_MODE_AM) {
resp = "AM\n";
}
else if (mode == RADIO_IFACE_MODE_DSB) {
resp = "DSB\n";
}
else if (mode == RADIO_IFACE_MODE_USB) {
resp = "USB\n";
}
else if (mode == RADIO_IFACE_MODE_CW) {
resp = "CW\n";
}
else if (mode == RADIO_IFACE_MODE_LSB) {
resp = "LSB\n";
}
resp = std::string(radioModeToString[mode]) + "\n";
}
if (!selectedVfo.empty()) {
else if (!selectedVfo.empty()) {
resp += std::to_string((int)sigpath::vfoManager.getBandwidth(selectedVfo)) + "\n";
}
else {
@ -690,6 +659,11 @@ private:
"0\n" /* RIG_PARM_NONE */;
client->write(resp.size(), (uint8_t*)resp.c_str());
}
// This get_powerstat stuff is a wordaround for WSJT-X 2.7.0
else if (parts[0] == "\\get_powerstat") {
resp = "1\n";
client->write(resp.size(), (uint8_t*)resp.c_str());
}
else {
// If command is not recognized, return error
flog::error("Rigctl client sent invalid command: '{0}'", cmd);

View File

@ -89,7 +89,7 @@ private:
}
}
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200.0f * style::uiScale))) {
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Countdown");
ImGui::TableSetupScrollFreeze(2, 1);

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