322 Commits

Author SHA1 Message Date
AlexandreRouma
a6df4d58e5 add missing argument to macos CI 2026-03-23 10:15:54 -04:00
AlexandreRouma
f817f7365b add missing argument to macos CI 2026-03-23 10:07:48 -04:00
AlexandreRouma
8eebbacb96 add missing sudo to macos CI 2026-03-23 10:03:58 -04:00
AlexandreRouma
7be83ffe65 add dragon labs source to macos CI and add to default config 2026-03-23 09:59:45 -04:00
AlexandreRouma
6fa945333c disable debugging options for dragonlabs source 2026-03-22 20:03:41 -04:00
AlexandreRouma
a3168f0365 fix wrong argument 2026-03-22 19:58:54 -04:00
AlexandreRouma
acb96dd70a add dragonlabs source to the CI 2026-03-22 19:57:38 -04:00
AlexandreRouma
e8c73f4dab just one more fix I swear 2026-03-22 00:38:30 -04:00
AlexandreRouma
8c247c5679 final fix for the hydrasdr source build 2026-03-22 00:13:39 -04:00
AlexandreRouma
ce34af01ee fix hydrasdr source compiling on windows 2026-03-21 23:48:54 -04:00
AlexandreRouma
1f1315f015 fixed a typo MSVC should absolutely have caught, fuck you microslop 2026-03-21 23:26:07 -04:00
AlexandreRouma
11ffdcfd53 fixed discord integration cmake version issue 2026-03-21 23:19:53 -04:00
AlexandreRouma
2330af01e5 fixed bundled libcorrect cmake version because I can't be arsed to wait for it to be changed upstream 2026-03-21 23:17:45 -04:00
AlexandreRouma
621c031364 Fixed API change with newer libhydrasdr 2026-03-21 23:13:51 -04:00
AlexandreRouma
2bf3faebae bugfix, new squelch system, dragon labs source 2026-03-21 23:00:10 -04:00
AlexandreRouma
65a0e11d3d fix macos CI 2025-12-12 01:55:34 -05:00
AlexandreRouma
4a48a0c09f fix support for versions of volk prior to 2.3 2025-12-12 01:53:37 -05:00
AlexandreRouma
9c32a68892 enabled the proof-of-concept ATV demodulator in nightly builds 2025-12-11 22:27:37 -05:00
AlexandreRouma
4658a1ade6 fixes to atv demod for testing only 2025-10-09 00:59:23 -04:00
AlexandreRouma
b1d94775fe another macos ci fix and removed ubuntu oracular ci 2025-10-05 18:04:22 -04:00
AlexandreRouma
2a3fb12f61 fix macos ci + fix hydrasdr vid/pid 2025-10-05 16:51:42 -04:00
AlexandreRouma
e82202ea74 switch macos nightly to macos 15 2025-10-02 18:11:09 -04:00
AlexandreRouma
981f53aa4e hopefully final hydrasdr build fix 2025-09-04 20:34:23 -04:00
AlexandreRouma
c216b88366 hydrasdr build fixes 2025-09-04 20:12:57 -04:00
AlexandreRouma
f65e4afb4a Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2025-09-04 18:46:19 -04:00
AlexandreRouma
51ae5628b3 enable hydrasdr support on all platforms 2025-09-04 18:45:45 -04:00
AlexandreRouma
9feaf02673 Merge pull request #1678 from Fale/pnrf2022
Update bands based on the PNRF 2022
2025-08-26 18:26:32 -04:00
AlexandreRouma
5d9f6dc341 statement on AI use 2025-08-22 10:20:39 -04:00
Fabio Alessandro Locati
cf7dd85f92 Update bands based on the PNRF 2022 2025-08-20 21:22:25 +02:00
AlexandreRouma
8b8eda301b add hydrasdr support to all platforms EXCEPT Windows and remote debian buster CI 2025-08-14 22:18:41 -04:00
AlexandreRouma
4558e73be5 fix dragonlabs source being included 2025-08-10 08:47:28 -04:00
AlexandreRouma
40808c60e4 remove debugging fields from hydrasdr source 2025-08-09 15:49:19 -04:00
AlexandreRouma
b5d227fecf bugfixes 2025-08-09 15:16:55 -04:00
AlexandreRouma
6c00834ddd disable armhf CI since the worker no longer exists 2025-08-07 01:01:25 -04:00
AlexandreRouma
f67fa0c66c Fix DSP source block initialization and add linux support to hydrasdr_source 2025-07-20 20:33:07 +02:00
AlexandreRouma
a94e2d6712 Show RTL-SDR dongle manufacturer and model name instead of generic name 2025-06-25 00:35:18 +02:00
AlexandreRouma
981bd1695a Added beginning of HydraSDR source 2025-06-19 18:45:42 +02:00
AlexandreRouma
dd9b8db6c9 Add new patreon 2025-05-01 01:43:13 +02:00
AlexandreRouma
543c60ccbc fix (#1594) duplicate plutosdr crash when shitty internet protocol IPv6 is enabled 2025-05-01 01:39:08 +02:00
AlexandreRouma
2dd8c6cea4 maybe final missing include? 2025-04-23 06:06:47 +02:00
AlexandreRouma
9457ad9369 fixed missing include 2025-04-23 05:35:13 +02:00
AlexandreRouma
fccd72b5f8 switch to official codec2 repo for windows build 2025-04-23 05:14:40 +02:00
AlexandreRouma
e75cc7be6f add sddc source prototype + add new ATV decoder + fix windows builds 2025-04-23 04:49:45 +02:00
AlexandreRouma
aa2b4b1c58 attempt to fix plutosdr macos issues 2025-03-12 16:50:58 +01:00
AlexandreRouma
64315ebc61 work on ATV demod 2025-03-10 17:47:56 +01:00
AlexandreRouma
553204b801 add missing include 2025-03-03 15:31:09 +01:00
AlexandreRouma
2a84ed202c Move gain clamping in rtl-sdr init 2025-03-03 14:52:29 +01:00
AlexandreRouma
f90e2d53a7 fixed audio source crashing if no device is available 2025-02-14 00:43:32 +01:00
AlexandreRouma
993bf9128c add aarch64 builds for all other debian-based distros and copy all aarch64 builds to nightly release 2025-02-14 00:34:47 +01:00
AlexandreRouma
d9ff7eaa12 fix SDRplay API version 2025-02-13 23:41:52 +01:00
AlexandreRouma
32b289953d Fix SDRplay CI build on all linux distros 2025-02-13 23:38:26 +01:00
AlexandreRouma
5178de0849 add experimental aarch64 build to CI 2025-02-13 23:25:05 +01:00
AlexandreRouma
5c355e21f5 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2025-02-06 15:46:55 +01:00
AlexandreRouma
d020640b7c add new patreon to credits + fix USRP samplerate formatting 2025-02-06 15:46:46 +01:00
AlexandreRouma
e0f9053229 Merge pull request #1559 from SrS2225a/wayland_icon
added window icon support for wayland
2025-02-02 07:10:28 +01:00
AlexandreRouma
395186ffb0 another format fix 2025-02-02 07:10:04 +01:00
AlexandreRouma
0e77a9f4ab disable VOR receiver module by default 2025-01-28 05:07:54 +01:00
AlexandreRouma
4799d0e3a8 add VOR receiver module 2025-01-28 05:07:23 +01:00
AlexandreRouma
6c6f4264b2 fix formatting 2025-01-07 21:41:22 +01:00
AlexandreRouma
ea3675da47 add new patreons to the credit 2025-01-05 21:20:43 +01:00
SrS2225a
afef9f57ab add check for correct glfw for macro 2025-01-04 22:58:53 -08:00
AlexandreRouma
e1de2daca8 Merge pull request #1556 from Oskar-Dudek/patch-4
Update ireland.json
2025-01-04 00:06:44 +01:00
AlexandreRouma
69bd6b0f3a revert changes not meant for this branch 2025-01-02 00:44:19 +01:00
AlexandreRouma
fea3fc2563 fix tuning code to allow using high-side injection downconverters 2025-01-01 23:02:16 +01:00
SrS2225a
7bccc67311 changed GLFW_WAYLAND_APP_ID, to the correct string name 2024-12-31 19:56:42 -08:00
SrS2225a
4310bbb1ea added window icon support for wayland 2024-12-29 21:17:32 -08:00
Oskar-Dudek
d811a839ff Update ireland.json
sorry for not updating for this long. I added CB, Air band VOR/ILS, Air band Voice, Polar orbiting satellites, and ADS-B. i validated this and jsonlint says its valid
2024-12-24 23:05:06 +00:00
AlexandreRouma
46bcba7594 fix some settings not applied on start for RSP1B and RSPdxR2 2024-12-19 04:26:30 +01:00
AlexandreRouma
45e4286f38 Merge pull request #1550 from AlexandreRouma/new_sdrplay_source
New sdrplay source
2024-12-18 23:32:34 +01:00
AlexandreRouma
c266a37a6b fix sdrplay configuration bug 2024-12-18 23:19:40 +01:00
AlexandreRouma
895199ae94 fix RSPdx bug 2024-12-18 23:02:49 +01:00
AlexandreRouma
d62426364a finish implementing support for RSP1B and RSPdx R2 + Improve samplerate and bandwidth code 2024-12-18 22:59:57 +01:00
AlexandreRouma
5ded73ce71 fix deprecated macos CI runner version 2024-12-18 21:18:13 +01:00
AlexandreRouma
cf3e15d285 fix workflow file 2024-12-18 20:51:02 +01:00
AlexandreRouma
59ca2cf1c0 deprecate ubuntu noble build and add ubuntu oracular build 2024-12-18 20:21:48 +01:00
AlexandreRouma
9bb4aeda14 fix package version 2024-12-18 19:47:11 +01:00
AlexandreRouma
304d5c42cc some fucking changes too lazy to name this shit 2024-11-27 21:49:46 +01:00
AlexandreRouma
b914587228 Revert "add support for new models (in a bad way) and start the rewrite process"
This reverts commit 3c1d0c7422.
2024-11-27 21:42:59 +01:00
AlexandreRouma
3c1d0c7422 add support for new models (in a bad way) and start the rewrite process 2024-11-27 21:41:46 +01:00
AlexandreRouma
11f87e0fe2 fix menu order bug 2024-11-09 19:28:34 +01:00
AlexandreRouma
e192cb963b fix MacOS ARM CI 2024-11-08 00:29:46 +01:00
AlexandreRouma
fe407a2f27 Merge pull request #1521 from AlexandreRouma/new_source_menu
New source menu
2024-11-08 00:20:26 +01:00
AlexandreRouma
6891d0bb0f final bugfixes to the new source menu 2024-11-07 23:49:15 +01:00
AlexandreRouma
b835d07573 finish custom offset definitions and fix bug in source selection 2024-11-07 17:39:52 +01:00
AlexandreRouma
f205d97b52 Merge pull request #1518 from Oskar-Dudek/patch-3
Fix ireland.json
2024-11-07 17:36:13 +01:00
Oskar-Dudek
628dcfcce0 Fix ireland.json
I used jsonlint.com and i edited it in some places and it says json valid, please can you try this version
2024-11-07 16:26:37 +00:00
AlexandreRouma
d1e7cc56b4 re-disable the M17 decoder module in MacOS ARM nightly builds due to codec2 package bug 2024-11-07 15:02:16 +01:00
AlexandreRouma
334860c963 attempt to enable M17 Decoder on MacOS ARM 2024-11-07 14:53:27 +01:00
AlexandreRouma
69161253e8 source menu upgrade 2024-11-07 14:03:32 +01:00
AlexandreRouma
5ab3428b90 update readme 2024-11-06 21:27:45 +01:00
AlexandreRouma
7f002f6276 add lower limit to network source samplerate 2024-11-06 20:54:01 +01:00
AlexandreRouma
a728403a3f Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-11-06 20:29:58 +01:00
AlexandreRouma
0f1d2da3b7 finish and enable the network source 2024-11-06 20:29:50 +01:00
AlexandreRouma
6d0b65c27f Merge pull request #1515 from Oskar-Dudek/patch-1
Create ireland.json
2024-11-06 17:51:31 +01:00
AlexandreRouma
f640cdcb6a fix formatting 2024-11-06 17:47:14 +01:00
Oskar-Dudek
80a90e13d9 Create ireland.json 2024-11-06 16:37:49 +00:00
AlexandreRouma
3982db73d3 Merge pull request #1501 from bsy0317/master
Add Republic of Korea BandPlan
2024-10-22 11:03:52 +02:00
WestKite
bd64f07a20 Update republic-of-korea.json 2024-10-22 17:57:18 +09:00
AlexandreRouma
c9950d9331 fix band plan name 2024-10-22 10:48:39 +02:00
WestKite
9bc609f4e4 Update republic-of-korea.json
Modify indentation
2024-10-22 17:39:15 +09:00
WestKite
bcc8e20e66 Update republic-of-korea.json
Corrected typos
2024-10-22 17:34:02 +09:00
WestKite
b07e828fed Update republic-of-korea.json
Correcting typos
2024-10-22 12:55:10 +09:00
WestKite
bd24a4a5eb Add Republic of Korea.json
Add Republic of Korea BandPlan.
It's not the Democratic People's Republic of Korea!
2024-10-22 12:39:06 +09:00
AlexandreRouma
fe4a7b32a7 Merge pull request #1496 from AlexandreRouma/bladerf_clock_sel
add clock selection got bladerf devices
2024-10-17 20:35:52 +02:00
AlexandreRouma
0e1ab29b5d ingore commas in pasted frequencies 2024-10-16 22:14:12 +02:00
AlexandreRouma
fbbafddd3d fix frequency formatting when copying from frequency selector 2024-10-16 20:48:35 +02:00
AlexandreRouma
1cbc8ec6f5 add copy/paste support to the frequency selector 2024-10-16 18:31:14 +02:00
AlexandreRouma
9f65e3ec71 add clock selection got bladerf devices 2024-10-12 01:48:39 +02:00
AlexandreRouma
08f3a7d201 make the modulation field of a baseband recording 'IQ' instead of 'Unknown' 2024-10-07 14:39:45 +02:00
AlexandreRouma
9ce62f8885 add whitelist for plutosdr-like devices 2024-10-07 14:10:23 +02:00
AlexandreRouma
caeaa2d46c add kcsdr_source to the readme 2024-10-02 19:15:13 +02:00
AlexandreRouma
7ae030a3a6 fix fobossdr_source module missing on MacOS as described in #1485 2024-10-02 18:58:48 +02:00
AlexandreRouma
1b27379a3d add beginning of kcsdr source 2024-10-02 18:57:05 +02:00
AlexandreRouma
e52123038e Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-09-28 03:32:14 +02:00
AlexandreRouma
ec8c60111d uncomment lines that copy the fobossdr source module for windows packaging 2024-09-28 03:32:07 +02:00
AlexandreRouma
f61799cf5f Merge pull request #1482 from AlexandreRouma/fobos_test
Enable the FobosSDR source on windows and linux
2024-09-28 02:44:04 +02:00
AlexandreRouma
17eccf5156 enabled fobos source on windows 2024-09-14 15:05:12 +02:00
AlexandreRouma
e835c8dd9a disable fobos source on windows 2024-09-14 15:04:07 +02:00
AlexandreRouma
acb1be121c enable fobossdr source 2024-09-14 14:48:37 +02:00
AlexandreRouma
0fa89614bb disabled problematic fobossdr source 2024-09-14 14:47:10 +02:00
AlexandreRouma
256affd918 fixed fobossdr CI 2024-09-14 02:06:34 +02:00
AlexandreRouma
e80cdbf248 fix CI + fix fobossdr_source cmakelist + prepare for fobos source on windows 2024-09-14 00:18:31 +02:00
AlexandreRouma
75e66226c3 finish fobossdr source + add rigexpert to hardware donor list + fix source module default instantiation 2024-09-13 23:02:30 +02:00
AlexandreRouma
c2f0e756a5 add fobossdr_source module and fix network sink crash when the given hostname is invalid 2024-09-11 21:57:12 +02:00
AlexandreRouma
79dd5bdcbb add beginning of DAB decoder and add missing hardware donors to the credits 2024-09-10 15:33:22 +02:00
AlexandreRouma
6dce28345c add missing stddef includes 2024-08-26 22:37:18 +02:00
AlexandreRouma
fe9ac6c9a1 implemented all user options for harogic devices 2024-08-22 04:36:36 +02:00
AlexandreRouma
bfdfa2b30b add debug logging to the harogic source enumeration function 2024-08-22 02:20:51 +02:00
AlexandreRouma
46e98b9b03 add harogic_source module 2024-08-22 01:52:27 +02:00
AlexandreRouma
e674a73771 removed useless include 2024-08-20 00:32:16 +02:00
AlexandreRouma
118e1fbff0 Add badgesdr source and add logging to spectran source 2024-08-05 22:32:39 +02:00
AlexandreRouma
bcadb36232 fix include issues in dsp library 2024-07-26 23:55:26 +02:00
AlexandreRouma
554ba2f596 add ryfi decoder module 2024-07-24 16:31:29 +02:00
AlexandreRouma
949fde022d fix recorder options not disabled during recording, increase rfnm source dsp frame size and fix long pause when stopping rfnm 2024-07-18 18:10:33 +02:00
AlexandreRouma
123e34d250 fix appliesCh of preferred path in rfnm_source 2024-07-18 16:53:02 +02:00
AlexandreRouma
f1c7010437 add rfnm_source path logging 2024-07-18 16:40:21 +02:00
AlexandreRouma
33a7795de1 fix RFNM source on MacOS 2024-07-18 03:12:38 +02:00
AlexandreRouma
fe7299c18a lots of work on the RFNM source 2024-07-18 02:15:20 +02:00
AlexandreRouma
8a9e0abcc2 fix RFNM source not enabled 2024-07-16 21:01:44 +02:00
AlexandreRouma
13abe4860b fix missing permissions 2024-07-16 20:59:32 +02:00
AlexandreRouma
981592fa19 how many times can someone fuck up a build script 2024-07-16 20:58:25 +02:00
AlexandreRouma
582750f79b fix CI script typo 2024-07-16 20:55:19 +02:00
AlexandreRouma
36f2a083ce More work towards working RFNM CI 2024-07-16 20:53:32 +02:00
AlexandreRouma
d753135a61 fix ARM macos CI build not having RFNM source 2024-07-16 20:45:27 +02:00
AlexandreRouma
07744e5bae add rfnm_source module to linux and MacOS CI 2024-07-16 20:43:53 +02:00
AlexandreRouma
9ec78da7ac rfnm source cleanup 2024-07-16 00:23:06 +02:00
AlexandreRouma
0066994899 add missing files for rfnm source and add it to the default instantiation config 2024-07-15 22:48:27 +02:00
AlexandreRouma
93ab51bf2f fix ci 2024-07-15 20:36:08 +02:00
AlexandreRouma
f9d7d20073 add rfnm_source to windows nightlies 2024-07-15 20:25:53 +02:00
AlexandreRouma
e81db5d85c make text bold so people won't miss it 2024-07-07 00:00:23 +02:00
AlexandreRouma
5c3a66642b Added an idiot-proof warning that the drivers for the SDR you want to use must be installed. I know... shocker... 2024-07-06 23:59:33 +02:00
AlexandreRouma
36492e799a Merge pull request #1418 from Armand31/patch-1
Update france.json
2024-06-26 17:41:53 +02:00
AlexandreRouma
0110dfbef6 Another attempt 2024-06-26 17:19:06 +02:00
AlexandreRouma
0b5a2ff786 Update librtlsdr for windows CI 2024-06-26 17:11:23 +02:00
AlexandreRouma
ce0f1f05ae Attempt to fix the Windows CI builds (fuck you Microsoft) 2024-06-26 16:49:05 +02:00
Armand Prioreschi
46a5ff8ac5 Update france.json
Correcting typos
2024-06-10 00:57:42 +02:00
Armand Prioreschi
03559b928b Update france.json
Adding the 2 DAB bands and Police band (tetrapol protocol)
2024-06-10 00:26:48 +02:00
AlexandreRouma
206ce6e8c3 fix formatting mistake in pull request... for fucks sake... 2024-06-08 05:33:57 +02:00
AlexandreRouma
d7a1f46af0 Merge pull request #1416 from Armand31/patch-1
Update france.json
2024-06-07 22:37:40 +02:00
Armand Prioreschi
89e6e4f7ad Update france.json 2024-06-07 22:13:55 +02:00
Armand Prioreschi
6ced9b15c3 Update france.json
Add DVB-T (TNT) band, from 470 to 694 MHz
2024-06-07 21:30:13 +02:00
AlexandreRouma
0de189a7b7 Merge pull request #1413 from ycanerol/master
Add Turkish Bandplan
2024-06-06 19:35:30 +02:00
AlexandreRouma
bb9024fadd disable badgesdr_source module 2024-06-04 22:04:25 +02:00
AlexandreRouma
d1dc20f4e2 fix rfnm device selection 2024-06-04 22:03:39 +02:00
ycanerol
309717b5f8 Add Turkish Bandplan 2024-05-31 01:59:31 +03:00
AlexandreRouma
762444d340 fix version number on Linux, MacOS and Android 2024-05-29 00:56:56 +02:00
AlexandreRouma
18300e8916 switch to SDRplay API v3.15 2024-05-26 22:57:47 +02:00
AlexandreRouma
a93bb9d468 fix source/samplerate selection bugs 2024-05-26 22:27:47 +02:00
AlexandreRouma
ea0362b927 add microtelecom to hardware donor list 2024-05-24 17:06:30 +02:00
AlexandreRouma
ffc642f270 set stage of rfnm source to beta 2024-05-24 01:58:17 +02:00
AlexandreRouma
1b5975f563 Add RFNM to hardware donor list 2024-05-24 01:42:36 +02:00
AlexandreRouma
733dc55723 added missing files 2024-05-24 01:38:24 +02:00
AlexandreRouma
b841180f84 add ubuntu noble build 2024-05-24 01:38:15 +02:00
AlexandreRouma
e99e84e809 add gain slider and FM notch controls to RFNM source$ 2024-05-15 13:01:57 +02:00
AlexandreRouma
7a4281dd76 add rfnm_source module 2024-05-14 22:22:03 +02:00
AlexandreRouma
c89763a989 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-05-04 16:38:52 +02:00
AlexandreRouma
27edc260c9 fix some fft sizes not being saved as described in #1396 2024-05-04 16:38:47 +02:00
AlexandreRouma
2ea7ac496f Merge pull request #1391 from rberaldo/master
Add a bandplan for Brazilian ham bands
2024-05-01 21:13:15 +02:00
AlexandreRouma
314d78d9d2 Add another missing include to the spectran source 2024-04-27 07:00:27 +02:00
AlexandreRouma
4e455e6661 Add missing include in spectran source 2024-04-27 06:53:15 +02:00
Rafael Beraldo
58b86fcee5 add brazil.json
Add a bandplan for the Brazilian ham bands inspired in netherlands.json.
I intend to improve and extend on this band plan, including by adding non-ham
bands.
2024-04-26 16:36:06 -03:00
AlexandreRouma
27072e9fe7 version bump to indicate ABI change. modules just need to be recompiled. 2024-04-23 22:46:43 +02:00
AlexandreRouma
da1417b5ab made recorder crash fix more robust 2024-04-22 21:54:01 +02:00
AlexandreRouma
e60eca5d6d fix crash when attempting to record from disabled radio 2024-04-22 21:51:49 +02:00
AlexandreRouma
ccb10bfb9a fix missing virtual destructors as reported in #1386 2024-04-22 18:54:12 +02:00
AlexandreRouma
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
AlexandreRouma
c61fc400a6 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-04-17 01:20:25 +02:00
AlexandreRouma
779ef7ecf1 fix memcpy erroneously used on overlapping buffer regions as described in #1379 2024-04-17 01:20:17 +02:00
AlexandreRouma
6fdab5e0c2 undo problematic commit 2024-04-15 06:23:39 +02:00
AlexandreRouma
ea08fac32e handle deprecation of rotator in volk 3.1.0 2024-04-14 03:41:00 +02:00
AlexandreRouma
632a4eebab Deprecated SoapySDR support 2024-04-10 22:09:07 +02:00
AlexandreRouma
e118598f57 Fix waterfall size related crash described in #1230 2024-04-10 18:28:59 +02:00
AlexandreRouma
a2d49b2f87 update development status of various modules in the readme 2024-04-08 17:17:16 +02:00
AlexandreRouma
38abfc715e potential fix for #677 2024-04-08 17:01:27 +02:00
AlexandreRouma
07eebd7018 potential fix for #677 2024-04-08 16:59:05 +02:00
AlexandreRouma
d12021fc2f potential fix for #1361 2024-04-08 16:37:03 +02:00
AlexandreRouma
db1682a2ac fix #1034 2024-04-08 16:21:33 +02:00
AlexandreRouma
fdfb1dbf5e remove useless menu item in pager decoder 2024-04-05 20:15:21 +02:00
AlexandreRouma
17f698577f disable FLEX protocol from pager module since it's not implemented and add setBaudrate function 2024-04-05 19:50:14 +02:00
AlexandreRouma
8eaa987d90 fix spectran http source samplerate detection 2024-04-04 19:42:58 +02:00
AlexandreRouma
12f7efed32 Switch spectran http source module to use the remoteconfig endpoint as per #1354 2024-04-04 19:25:02 +02:00
AlexandreRouma
065a5b4c40 add audio source to readme 2024-04-01 19:56:36 +02:00
AlexandreRouma
70f90fd570 MacOS CI workaround (I hate you microsoft) 2024-03-31 23:27:07 +02:00
AlexandreRouma
a2054ad780 fix #1367 2024-03-31 23:00:50 +02:00
AlexandreRouma
e1c48e9a1f try to fix macos CI again (fix your shit microsoft...) 2024-03-26 00:30:31 +01:00
AlexandreRouma
867a8680e1 another MacOS CI fix attempt (this is getting ridiculous, fix your shit github) 2024-03-22 19:14:17 +01:00
AlexandreRouma
bf831e3a50 attempt to fix the MacOS CI yet again 2024-03-22 18:48:27 +01:00
AlexandreRouma
eb8b852ea6 fix macos build 2024-03-22 02:06:11 +01:00
AlexandreRouma
67520ea45e fix linux CI 2024-03-20 01:58:16 +01:00
AlexandreRouma
a3f0ad238a Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-03-20 01:38:44 +01:00
AlexandreRouma
feb9789896 Update sdrplay api version for nightlies to 3.14.0 2024-03-20 01:38:36 +01:00
AlexandreRouma
3a5096092d Merge pull request #1338 from LEDFlighter/master
Update germany.json
2024-02-24 22:33:57 +01:00
LEDFlighter
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
LEDFlighter
b1603f0e72 Update germany.json
Updated and extended the German bandplan
2024-02-23 23:26:36 +01:00
LEDFlighter
09467439e3 Update germany.json
Updated and extended the German bandplan
2024-02-23 23:21:42 +01:00
AlexandreRouma
021928bbda fix show/hide waterfall keybind not working with the Display menu hidden 2024-02-21 21:59:55 +01:00
AlexandreRouma
7c933d5103 fix typo mentioned in #174 2024-02-21 18:47:50 +01:00
AlexandreRouma
a987c112a3 fix crash when USRP has two frontends named the same 2024-02-17 17:37:37 +01:00
AlexandreRouma
f1339f08cf add workaround for uhd bug reporting same device twice 2024-02-17 06:30:06 +01:00
AlexandreRouma
650a61930c fix SDR++ server crash at high samplerates #1326 2024-02-13 18:39:11 +01:00
AlexandreRouma
61ffb3e6bf revert pager decoder to traditional clock recovery 2024-02-13 16:27:54 +01:00
AlexandreRouma
9ab3c97c44 don't include rc file on platforms other than windows 2024-02-13 16:18:25 +01:00
AlexandreRouma
edc08ddc08 more progress on the network source 2024-02-13 16:17:17 +01:00
AlexandreRouma
95052c34ff more work on network source and syntax cleanup in iq exporter 2024-02-13 03:11:37 +01:00
AlexandreRouma
34171d4edc fix windows CI 2024-02-12 23:24:00 +01:00
AlexandreRouma
726e1069bf fix wrong mode name in rigctl server as described in #1327 2024-02-12 22:46:03 +01:00
AlexandreRouma
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
AlexandreRouma
01ab1831e8 more progress on the network source 2024-02-12 22:07:17 +01:00
AlexandreRouma
2b752bb267 disable M17 decoder on M1 CI 2024-02-11 19:57:13 +01:00
AlexandreRouma
5204cfec56 disable perseus source on macos M1 2024-02-11 19:41:49 +01:00
AlexandreRouma
c616892eda attempt to add MacOS M1 CI 2024-02-11 19:36:26 +01:00
AlexandreRouma
5f23c1f312 added new patrons and hardware donors 2024-02-10 20:59:37 +01:00
AlexandreRouma
5e0c4449f8 switched back ATV demod to black and white 2024-02-09 22:13:25 +01:00
AlexandreRouma
cd3e2b6c05 fix network source build on windows 2024-02-08 21:45:58 +01:00
AlexandreRouma
ba5380f9bb started work on the network source 2024-02-08 15:01:11 +01:00
AlexandreRouma
daf0f8c159 more work on new clock recovery 2024-02-08 14:17:35 +01:00
AlexandreRouma
63aa45de9e beginning of new pager clock recovery 2024-02-08 09:03:46 +01:00
AlexandreRouma
c0a84f8703 pocsag menu cleanup 2024-02-06 22:16:21 +01:00
AlexandreRouma
f66f2c25e1 improve pocsag alpha decoding 2024-02-05 16:46:08 +01:00
AlexandreRouma
bddfe5396f fix iq exporter config name 2024-02-03 01:43:16 +01:00
AlexandreRouma
d5fa76df06 removed archived decoder from readme 2024-02-02 23:15:58 +01:00
AlexandreRouma
8029cef4da promote iq_exporter module to Beta status 2024-02-02 23:12:44 +01:00
AlexandreRouma
d84bb9bdec fix module not enabled 2024-02-02 23:07:13 +01:00
AlexandreRouma
a0ff745b63 fix windows CI missing module 2024-02-02 23:06:03 +01:00
AlexandreRouma
a08d2a0f85 more work on debugging the pager decoder 2024-02-02 22:52:19 +01:00
AlexandreRouma
7ab743d05b finish iq exporter and fix network lib send not closing socket on error 2024-02-02 04:11:29 +01:00
AlexandreRouma
122e67ef65 finished VFO mode of the iq exporter 2024-02-01 21:38:13 +01:00
AlexandreRouma
fbeb2195da fix make_windows_package.ps1 issue 2024-02-01 18:54:18 +01:00
AlexandreRouma
1f2b50c9bb add beginning of IQ exporter module 2024-02-01 18:36:25 +01:00
AlexandreRouma
f486c657c1 fix cmake to prevent always enabling the pager decoder 2024-02-01 01:12:51 +01:00
AlexandreRouma
f1f04d59fe add missing files 2024-02-01 00:55:36 +01:00
AlexandreRouma
ef42ea01d8 add flex decoder menu entry and fix pocsag decoding 2024-02-01 00:55:17 +01:00
AlexandreRouma
3fc893568a beginning of pager decoder 2024-01-31 23:34:40 +01:00
AlexandreRouma
4b6835141e fix low PI RDS callsign decoding 2024-01-30 22:18:18 +01:00
AlexandreRouma
a9e59bdf3c removed useless logging again 2024-01-30 00:01:10 +01:00
AlexandreRouma
f0bd17f9f4 fix north americal RDS callsign decoding 2024-01-29 23:57:23 +01:00
AlexandreRouma
a8ed213ed3 remove useless debug logging 2024-01-29 21:49:13 +01:00
AlexandreRouma
f8183739f7 add rds region selection 2024-01-29 21:28:43 +01:00
AlexandreRouma
120745de19 add rds program type name decoding 2024-01-29 21:00:23 +01:00
AlexandreRouma
05ab17add3 Merge pull request #1307 from AlexandreRouma/new_rds
New rds demod and decode
2024-01-29 19:43:06 +01:00
AlexandreRouma
2ef8ee3629 disable rds symbol diagram data stream when not visible 2024-01-29 19:15:45 +01:00
AlexandreRouma
14cb839863 clean up rds code and fix use before init 2024-01-29 18:43:46 +01:00
AlexandreRouma
9501371c6c Merge pull request #1302 from AlexandreRouma/master
keep new_rds branch updated
2024-01-29 01:45:32 +01:00
AlexandreRouma
ff23d7e43f fix warnings 2024-01-29 01:40:20 +01:00
AlexandreRouma
f541328e5c Merge pull request #1301 from AlexandreRouma/new_plutosdr_enum
New plutosdr enum
2024-01-28 23:39:33 +01:00
AlexandreRouma
be8edbfa9e revamp sdr++ server source networking code 2024-01-28 21:46:54 +01:00
AlexandreRouma
11a7c382e8 update more github action versions 2024-01-28 17:28:07 +01:00
AlexandreRouma
54276177ae update github action version 2024-01-28 17:25:27 +01:00
AlexandreRouma
bc77bab45f improved plutosdr device naming 2024-01-28 17:23:18 +01:00
AlexandreRouma
97d0a07ec7 fix plutosdr commit persistence 2024-01-28 15:26:34 +01:00
AlexandreRouma
6b5de78e80 fix plutosdr source not updating samplerate on select 2024-01-28 14:39:01 +01:00
AlexandreRouma
1cd8c2510a fix plutosdr source error checking 2024-01-28 14:38:36 +01:00
AlexandreRouma
32cbd726fd implement enumeration and settings for plutosdr source 2024-01-28 02:16:20 +01:00
AlexandreRouma
175992b081 add android workaround 2024-01-28 00:25:46 +01:00
AlexandreRouma
31c9e5767e beginning of PlutoSDR context enumeration 2024-01-28 00:03:04 +01:00
AlexandreRouma
e6a02a3944 fix bad plutosdr bandwidth selection at very high samplerates 2024-01-27 22:52:11 +01:00
AlexandreRouma
00e6832055 add plutosdr bandwidth selection for #563 2024-01-27 22:49:39 +01:00
AlexandreRouma
bc8baca190 clean up plutosdr source code 2024-01-27 21:35:13 +01:00
AlexandreRouma
08e75b6d14 fix gain mode selection not always applying 2024-01-27 21:30:06 +01:00
AlexandreRouma
e9ec79f6ef modernise gain mode selection for plutosdr 2024-01-27 21:29:44 +01:00
AlexandreRouma
cd996292bc revamp plutosdr samplerate selection code 2024-01-27 21:12:26 +01:00
AlexandreRouma
06b7ad5c98 remove old useless debug code from audio sink 2024-01-27 16:34:54 +01:00
AlexandreRouma
38a95b4011 update SDRplay API in MacOS CI 2024-01-26 19:31:32 +01:00
AlexandreRouma
f6052d913a bump version of updated modules 2024-01-26 19:00:36 +01:00
AlexandreRouma
2b00370cf3 Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 18:47:18 +01:00
AlexandreRouma
09f4071803 Switch SDRplay API in Linux nightly builds to version 3.12 2024-01-26 18:47:13 +01:00
AlexandreRouma
e61ef29e0f Merge pull request #1292 from daviderud/aligned_master
additions and corrections to NL band plan
2024-01-26 17:56:54 +01:00
AlexandreRouma
eb36f86d41 added missing include 2024-01-26 17:21:51 +01:00
AlexandreRouma
29889a289f Merge branch 'master' of https://github.com/AlexandreRouma/SDRPlusPlus 2024-01-26 16:57:19 +01:00
AlexandreRouma
859af77bd3 revamp RFspace source to use new networking library and fix buffering 2024-01-26 16:57:11 +01:00
Davide Rovelli
0a3d1de02f removed tabs and unnecessary pipes 2024-01-26 07:43:01 +01:00
AlexandreRouma
db3fbd2975 update issue template to reflect that fact that filling it out is not optional 2024-01-26 01:40:17 +01:00
AlexandreRouma
f5adc7c587 fix hermes lite buffering 2024-01-25 23:28:03 +01:00
AlexandreRouma
255988ee46 fix hermes lite enumeration issues and fix copy paste fail in network library 2024-01-25 22:27:59 +01:00
AlexandreRouma
68bf2fc16f fix bad network lib broadcast implementation 2024-01-25 21:34:13 +01:00
AlexandreRouma
3aa167701e fix networking library to allow multicast 2024-01-25 20:27:04 +01:00
AlexandreRouma
97c1a132a5 move zoom waterfall's zoom function in the cpp file to avoid annoying recomps 2024-01-25 19:48:04 +01:00
AlexandreRouma
118e56897c Add ubuntu mantic CI build 2024-01-25 15:41:23 +01:00
AlexandreRouma
af8c085d43 switch osx to macos 2024-01-24 18:14:29 +01:00
Davide Rovelli
708f74e179 additions and corrections 2024-01-24 06:14:00 +01:00
AlexandreRouma
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
AlexandreRouma
4b77d8c395 prepare for merge back part 2, electric boogaloo 2024-01-22 20:31:18 +01:00
AlexandreRouma
854ed89b82 prepare for merge back 2024-01-22 20:30:53 +01:00
AlexandreRouma
27ab5bf3c1 Fix exceptions referenced in #1287 2024-01-22 19:46:01 +01:00
AlexandreRouma
159f59b858 fix return warning for #1234 2024-01-22 19:34:56 +01:00
AlexandreRouma
93cafe7109 fix typo referenced in #1083 2024-01-22 19:22:26 +01:00
AlexandreRouma
74ae8a45d9 fix incorrect formatting again for #1288 2024-01-22 18:17:23 +01:00
AlexandreRouma
691216a298 fix incorrect formatting #1288 this time in the right branch... 2024-01-22 17:31:09 +01:00
AlexandreRouma
3e58d4ba31 fix missing return statement 2024-01-22 02:10:48 +01:00
AlexandreRouma
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
Henner Zeller
5a003e99d2 Address review comments. 2024-01-21 15:36:41 -08:00
AlexandreRouma
f197cf6bd9 Fix styling in the other file 2024-01-21 23:49:23 +01:00
AlexandreRouma
8cefeadbd4 Fix styling, modern computers can display more than 40 columns 2024-01-21 23:48:04 +01:00
Henner Zeller
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
AlexandreRouma
fa76b4e865 Made the contributing policy more clear 2024-01-21 20:19:46 +01:00
AlexandreRouma
5e195a0d43 testing something for macos (do not use this CI build) 2024-01-19 20:53:25 +01:00
AlexandreRouma
5e299d9d23 testing something silly 2 2024-01-19 20:01:05 +01:00
AlexandreRouma
fd5813df6d testing something silly 2024-01-19 19:43:31 +01:00
AlexandreRouma
eabb842b6b switched MacOS CI to debug mode 2024-01-19 19:11:01 +01:00
AlexandreRouma
5a1945f779 allow running code on older macos version (test) 2024-01-17 07:07:24 +01:00
AlexandreRouma
193580caf3 Merge pull request #1270 from AlexandreRouma/master
merge
2024-01-08 06:18:00 +01:00
AlexandreRouma
2432390600 Completely redid the RDS demod 2023-12-13 23:25:46 +01:00
209 changed files with 21309 additions and 1607 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. 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** **Hardware**

View File

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

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
@@ -36,14 +36,21 @@ jobs:
working-directory: ${{runner.workspace}} 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/" ; rm "C:/Program Files/PothosSDR/lib/libusb-1.0.lib" ; cp "libusb_old/MS64/dll/libusb-1.0.lib" "C:/Program Files/PothosSDR/lib/" 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 librtlsdr
run: Invoke-WebRequest -Uri "https://ftp.osmocom.org/binaries/windows/rtl-sdr/rtl-sdr-64bit-20240623.zip" -OutFile ${{runner.workspace}}/rtl-sdr.zip
- name: Patch Pothos with newer librtlsdr version
working-directory: ${{runner.workspace}}
run: 7z x rtl-sdr.zip ; rm "C:/Program Files/PothosSDR/bin/rtlsdr.dll" ; cp "rtl-sdr-64bit-20240623/librtlsdr.dll" "C:/Program Files/PothosSDR/bin/rtlsdr.dll"
- name: Download SDRPlay API - name: Download SDRPlay API
run: Invoke-WebRequest -Uri "https://drive.google.com/uc?id=12UHPMwkfa67A11QZDmpCT4iwHnyJHWuu&confirm=t" -OutFile ${{runner.workspace}}/SDRPlay.zip run: Invoke-WebRequest -Uri "https://www.sdrpp.org/SDRplay.zip" -OutFile ${{runner.workspace}}/SDRplay.zip
- name: Install SDRPlay API - 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 - name: Download codec2
run: git clone https://github.com/AlexandreRouma/codec2 run: git clone https://github.com/drowe67/codec2
- name: Prepare MinGW - name: Prepare MinGW
run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja" run: C:/msys64/msys2_shell.cmd -defterm -here -no-start -mingw64 -c "pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja"
@@ -58,17 +65,26 @@ 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" 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 - name: Install vcpkg dependencies
run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows run: vcpkg install fftw3:x64-windows glfw3:x64-windows portaudio:x64-windows zstd:x64-windows libusb:x64-windows spdlog:x64-windows
- name: Install rtaudio - 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 . 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 - 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" run: git clone https://github.com/AlexandreRouma/libperseus-sdr ; cd libperseus-sdr ; mkdir build ; cd build ; cmake -DCMAKE_BUILD_TYPE=Release "-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: Install librfnm
run: git clone https://github.com/AlexandreRouma/librfnm ; cd librfnm ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos ; cd libfobos ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ; cmake --build . --config Release ; cmake --install .
- name: Install libhydrasdr
run: git clone https://github.com/hydrasdr/rfone_host; cd rfone_host/ ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Release "-DLIBUSB_LIBRARIES=C:\Program Files\PothosSDR\lib\libusb-1.0.lib" "-DLIBUSB_INCLUDE_DIR=C:\Program Files\PothosSDR\include\libusb-1.0" "-DTHREADS_PTHREADS_WIN32_LIBRARY=C:\Program Files\PothosSDR\lib\pthreadVC2.lib" "-DTHREADS_PTHREADS_INCLUDE_DIR=C:\Program Files\PothosSDR\include" ; cmake --build . --config Release ; cmake --install .
- name: Prepare CMake - name: Prepare CMake
working-directory: ${{runner.workspace}}/build 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_PERSEUS_SOURCE=ON -DOPT_BUILD_SDRPLAY_SOURCE=ON -DOPT_BUILD_NEW_PORTAUDIO_SINK=ON -DOPT_BUILD_M17_DECODER=ON run: cmake -DCOPY_MSVC_REDISTRIBUTABLES=ON "$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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
@@ -79,50 +95,59 @@ jobs:
run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")' run: '&($Env:GITHUB_WORKSPACE + "/make_windows_package.ps1") ./build ($Env:GITHUB_WORKSPACE + "/root")'
- name: Save Archive - name: Save Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_windows_x64 name: sdrpp_windows_x64
path: ${{runner.workspace}}/sdrpp_windows_x64.zip path: ${{runner.workspace}}/sdrpp_windows_x64.zip
build_macos: build_macos_intel:
runs-on: macos-11 runs-on: macos-15-intel
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Build Environment - name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build run: cmake -E make_directory ${{runner.workspace}}/build
- name: Update brew repositories
run: brew update
- name: Install dependencies - name: Install dependencies
run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool && pip3 install mako run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
- name: Install volk - 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 - 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.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.0.pkg -target /
- name: Install libiio - 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_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 - 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 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && make -j3 && sudo make install && cd ../../
- name: Install LimeSuite - 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 - 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 .. run: git clone https://github.com/Microtelecom/libperseus-sdr && cd libperseus-sdr && autoreconf -i && ./configure --prefix=/usr/local && make && sudo make install && cd ..
- name: Install librfnm
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install more recent librtlsdr - name: Install more recent librtlsdr
run: git clone https://github.com/osmocom/rtl-sdr && cd rtl-sdr && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j3 LIBRARY_PATH=$(pkg-config --libs-only-L libusb-1.0 | sed 's/\-L//') && sudo make install && cd ../../ 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: Install libhydrasdr
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libdlcr
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && sudo make install && cd ../../
- name: Prepare CMake - name: Prepare CMake
working-directory: ${{runner.workspace}}/build 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_PERSEUS_SOURCE=ON -DOPT_BUILD_AUDIO_SOURCE=OFF -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release run: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON -DUSE_BUNDLE_DEFAULTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build - name: Build
working-directory: ${{runner.workspace}}/build working-directory: ${{runner.workspace}}/build
@@ -133,38 +158,79 @@ 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 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 - name: Save Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_macos_intel name: sdrpp_macos_intel
path: ${{runner.workspace}}/sdrpp_macos_intel.zip path: ${{runner.workspace}}/sdrpp_macos_intel.zip
build_debian_buster: build_macos_arm:
runs-on: ubuntu-latest runs-on: macos-15
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image - name: Create Build Environment
run: cd $GITHUB_WORKSPACE/docker_builds/debian_buster && docker build . --tag sdrpp_build run: cmake -E make_directory ${{runner.workspace}}/build
- name: Run Container - name: Install dependencies
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh run: brew install pkg-config libusb fftw glfw airspy airspyhf portaudio hackrf libbladerf codec2 zstd autoconf automake libtool spdlog && pip3 install mako --break-system-packages
- name: Recover Deb Archive - 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.15.0.pkg && sudo installer -pkg SDRplayAPI-macos-installer-universal-3.15.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 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. && 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 librfnm
run: git clone https://github.com/AlexandreRouma/librfnm && cd librfnm && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo make install && cd ..
- name: Install libfobos
run: git clone https://github.com/AlexandreRouma/libfobos && cd libfobos && mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make && sudo 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: Install libhydrasdr
run: git clone https://github.com/hydrasdr/rfone_host && cd rfone_host && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DCMAKE_BUILD_TYPE=Release .. && make -j3 && sudo make install && cd ../../
- name: Install libdlcr
run: wget https://dragnlabs.com/host-tools/dlcr_host_v0.3.0.zip && mkdir dlcr_host && cd dlcr_host && 7z x ../dlcr_host_v0.3.0.zip && mkdir build && cd build && cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 .. -DCMAKE_BUILD_TYPE=Release && make -j2 && sudo make install && cd ../../
- name: Prepare CMake
working-directory: ${{runner.workspace}}/build
run: cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -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 -DOPT_BUILD_RFNM_SOURCE=ON -DOPT_BUILD_FOBOSSDR_SOURCE=ON -DOPT_BUILD_HYDRASDR_SOURCE=ON -DOPT_BUILD_DRAGONLABS_SOURCE=ON -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}} working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ 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 Deb Archive - name: Save Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_debian_buster_amd64 name: sdrpp_macos_arm
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_macos_arm.zip
build_debian_bullseye: build_debian_bullseye_amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
@@ -177,16 +243,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_debian_bullseye_amd64 name: sdrpp_debian_bullseye_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm: build_debian_bullseye_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bullseye && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_bullseye_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_bookworm_amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_bookworm && docker build . --tag sdrpp_build
@@ -199,16 +287,82 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_debian_bookworm_amd64 name: sdrpp_debian_bookworm_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid: build_debian_bookworm_aarch64:
runs-on: ubuntu-24.04-arm
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_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_trixie_amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && 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_trixie_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_trixie_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_trixie && 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_trixie_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_debian_sid_amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
@@ -221,16 +375,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_debian_sid_amd64 name: sdrpp_debian_sid_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal: build_debian_sid_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/debian_sid && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_debian_sid_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_focal_amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
@@ -243,16 +419,38 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_ubuntu_focal_amd64 name: sdrpp_ubuntu_focal_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy: build_ubuntu_focal_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_focal && docker build . --tag sdrpp_build
- name: Run Container
run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
- name: Recover Deb Archive
working-directory: ${{runner.workspace}}
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive
uses: actions/upload-artifact@v4
with:
name: sdrpp_ubuntu_focal_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_jammy_amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Docker Image - name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
@@ -265,43 +463,82 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./ run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_ubuntu_jammy_amd64 name: sdrpp_ubuntu_jammy_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_raspios_bullseye_armhf: build_ubuntu_jammy_aarch64:
runs-on: ARM runs-on: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Create Build Environment - name: Create Docker Image
run: rm -rf ${{runner.workspace}}/build && cmake -E make_directory ${{runner.workspace}}/build run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_jammy && docker build . --tag sdrpp_build
- name: Prepare CMake - name: Run Container
working-directory: ${{runner.workspace}}/build run: docker run --name build -v $GITHUB_WORKSPACE:/root/SDRPlusPlus --env BUILD_NO="-$GITHUB_RUN_NUMBER" sdrpp_build /root/do_build.sh
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 - name: Recover Deb Archive
working-directory: ${{runner.workspace}}/build
run: make VERBOSE=1 -j3
- name: Create Dev Archive
working-directory: ${{runner.workspace}} working-directory: ${{runner.workspace}}
run: sh $GITHUB_WORKSPACE/make_debian_package.sh ./build 'libfftw3-dev, libglfw3-dev, libvolk2-dev, librtaudio-dev' && mv sdrpp_debian_amd64.deb sdrpp_debian_armhf.deb run: docker cp build:/root/SDRPlusPlus/sdrpp_debian_amd64.deb ./
- name: Save Deb Archive - name: Save Deb Archive
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_raspios_bullseye_armhf name: sdrpp_ubuntu_jammy_aarch64
path: ${{runner.workspace}}/sdrpp_debian_armhf.deb path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_amd64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && 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_noble_amd64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_ubuntu_noble_aarch64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Create Docker Image
run: cd $GITHUB_WORKSPACE/docker_builds/ubuntu_noble && 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_noble_aarch64
path: ${{runner.workspace}}/sdrpp_debian_amd64.deb
build_android: build_android:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Fetch container - name: Fetch container
working-directory: ${{runner.workspace}} working-directory: ${{runner.workspace}}
@@ -319,34 +556,61 @@ jobs:
run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk run: docker cp build:/root/SDRPlusPlus/android/app/build/outputs/apk/debug/app-debug.apk ./ && mv app-debug.apk sdrpp.apk
- name: Save APK - name: Save APK
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: sdrpp_android name: sdrpp_android
path: ${{runner.workspace}}/sdrpp.apk path: ${{runner.workspace}}/sdrpp.apk
create_full_archive: create_full_archive:
needs: ['build_windows', 'build_macos', 'build_debian_buster', 'build_debian_bullseye', 'build_debian_bookworm', '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_bullseye_amd64',
'build_debian_bullseye_aarch64',
'build_debian_bookworm_amd64',
'build_debian_bookworm_aarch64',
'build_debian_trixie_amd64',
'build_debian_trixie_aarch64',
'build_debian_sid_amd64',
'build_debian_sid_aarch64',
'build_ubuntu_focal_amd64',
'build_ubuntu_focal_aarch64',
'build_ubuntu_jammy_amd64',
'build_ubuntu_jammy_aarch64',
'build_ubuntu_noble_amd64',
'build_ubuntu_noble_aarch64',
'build_android'
]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download All Builds - name: Download All Builds
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
- name: Create Archive - name: Create Archive
run: > run: >
mkdir sdrpp_all && mkdir sdrpp_all &&
mv sdrpp_windows_x64/sdrpp_windows_x64.zip 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_intel/sdrpp_macos_intel.zip sdrpp_all/ &&
mv sdrpp_debian_buster_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_buster_amd64.deb && mv sdrpp_macos_arm/sdrpp_macos_arm.zip sdrpp_all/ &&
mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb && mv sdrpp_debian_bullseye_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_amd64.deb &&
mv sdrpp_debian_bullseye_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bullseye_aarch64.deb &&
mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb && mv sdrpp_debian_bookworm_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_amd64.deb &&
mv sdrpp_debian_bookworm_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_bookworm_aarch64.deb &&
mv sdrpp_debian_trixie_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_amd64.deb &&
mv sdrpp_debian_trixie_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_trixie_aarch64.deb &&
mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb && mv sdrpp_debian_sid_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_amd64.deb &&
mv sdrpp_debian_sid_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_debian_sid_aarch64.deb &&
mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb && mv sdrpp_ubuntu_focal_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_amd64.deb &&
mv sdrpp_ubuntu_focal_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_focal_aarch64.deb &&
mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb && mv sdrpp_ubuntu_jammy_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_amd64.deb &&
mv sdrpp_raspios_bullseye_armhf/sdrpp_debian_armhf.deb sdrpp_all/sdrpp_raspios_bullseye_armhf.deb && mv sdrpp_ubuntu_jammy_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_jammy_aarch64.deb &&
mv sdrpp_ubuntu_noble_amd64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_amd64.deb &&
mv sdrpp_ubuntu_noble_aarch64/sdrpp_debian_amd64.deb sdrpp_all/sdrpp_ubuntu_noble_aarch64.deb &&
mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk mv sdrpp_android/sdrpp.apk sdrpp_all/sdrpp.apk
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: sdrpp_all name: sdrpp_all
path: sdrpp_all/ path: sdrpp_all/
@@ -358,7 +622,7 @@ jobs:
steps: steps:
- name: Download All Builds - name: Download All Builds
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
- name: Update Nightly - name: Update Nightly
run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber run: gh release upload nightly sdrpp_all/* -R ${{github.repository}} --clobber
@@ -367,7 +631,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Install codespell - name: Install codespell
run: sudo apt update -y && sudo apt install -y codespell run: sudo apt update -y && sudo apt install -y codespell
@@ -379,7 +643,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Run check_clang_format - name: Run check_clang_format
run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true run: cd $GITHUB_WORKSPACE && chmod +x ./check_clang_format.sh && ./check_clang_format.sh || true

4
.gitignore vendored
View File

@@ -17,3 +17,7 @@ m17_decoder/libcorrect
SDR++.app SDR++.app
android/deps android/deps
android/app/assets android/app/assets
source_modules/badgesdr_source
decoder_modules/mystery_decoder
decoder_modules/tetra_decoder
misc_modules/modulation_monitor

View File

@@ -12,19 +12,27 @@ option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on sy
option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON) option(OPT_BUILD_AIRSPY_SOURCE "Build Airspy Source Module (Dependencies: libairspy)" ON)
option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON) option(OPT_BUILD_AIRSPYHF_SOURCE "Build Airspy HF+ Source Module (Dependencies: libairspyhf)" ON)
option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON) option(OPT_BUILD_AUDIO_SOURCE "Build Audio Source Module (Dependencies: rtaudio)" ON)
option(OPT_BUILD_BADGESDR_SOURCE "Build BadgeSDR Source Module (Dependencies: libusb)" OFF)
option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF) option(OPT_BUILD_BLADERF_SOURCE "Build BladeRF Source Module (Dependencies: libbladeRF)" OFF)
option(OPT_BUILD_DRAGONLABS_SOURCE "Build Dragon Labs Source Module (Dependencies: libdlcr)" OFF)
option(OPT_BUILD_FILE_SOURCE "Wav file source" ON) option(OPT_BUILD_FILE_SOURCE "Wav file source" ON)
option(OPT_BUILD_FOBOSSDR_SOURCE "Build FobosSDR Source Module (Dependencies: libfobos)" OFF)
option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON) option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Dependencies: libhackrf)" ON)
option(OPT_BUILD_HAROGIC_SOURCE "Build Harogic Source Module (Dependencies: htra_api)" OFF)
option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON) option(OPT_BUILD_HERMES_SOURCE "Build Hermes Source Module (no dependencies required)" ON)
option(OPT_BUILD_HYDRASDR_SOURCE "Build HydraSDR Source Module (Dependencies: libhydrasdr)" OFF)
option(OPT_BUILD_KCSDR_SOURCE "Build KCSDR Source Module (Dependencies: libkcsdr)" OFF)
option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF) option(OPT_BUILD_LIMESDR_SOURCE "Build LimeSDR Source Module (Dependencies: liblimesuite)" OFF)
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_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_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON)
option(OPT_BUILD_RFNM_SOURCE "Build RFNM Source Module (Dependencies: librfnm)" OFF)
option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON) option(OPT_BUILD_RFSPACE_SOURCE "Build RFspace Source Module (no dependencies required)" ON)
option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Dependencies: librtlsdr)" ON) option(OPT_BUILD_RTL_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_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_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_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_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_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_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencies required)" ON)
@@ -38,17 +46,22 @@ option(OPT_BUILD_NEW_PORTAUDIO_SINK "Build the new PortAudio Sink Module (Depend
option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF)
# Decoders # Decoders
option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" OFF) option(OPT_BUILD_ATV_DECODER "Build ATV decoder (no dependencies required)" ON)
option(OPT_BUILD_DAB_DECODER "Build the DAB/DAB+ decoder (no dependencies required)" OFF)
option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF)
option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF) option(OPT_BUILD_KG_SSTV_DECODER "Build the KG SSTV (KG-STV) decoder module (no dependencies required)" OFF)
option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2)" OFF) option(OPT_BUILD_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_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_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON)
option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF)
option(OPT_BUILD_VOR_RECEIVER "VOR beacon receiver" OFF)
option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF) option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF)
# Misc # Misc
option(OPT_BUILD_DISCORD_PRESENCE "Build the Discord Rich Presence module" ON) 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_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_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_CLIENT "Rigctl client to make SDR++ act as a panadapter" ON)
option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON) option(OPT_BUILD_RIGCTL_SERVER "Rigctl backend for controlling SDR++ with software like gpredict" ON)
@@ -58,6 +71,7 @@ option(OPT_BUILD_SCHEDULER "Build the scheduler" OFF)
# Other options # Other options
option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON) option(USE_INTERNAL_LIBCORRECT "Use an internal version of libcorrect" ON)
option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF) option(USE_BUNDLE_DEFAULTS "Set the default resource and module directories to the right ones for a MacOS .app" OFF)
option(COPY_MSVC_REDISTRIBUTABLES "Copy over the Visual C++ Redistributable" OFF)
# Module cmake path # Module cmake path
set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake") set(SDRPP_MODULE_CMAKE "${CMAKE_SOURCE_DIR}/sdrpp_module.cmake")
@@ -122,26 +136,54 @@ if (OPT_BUILD_AUDIO_SOURCE)
add_subdirectory("source_modules/audio_source") add_subdirectory("source_modules/audio_source")
endif (OPT_BUILD_AUDIO_SOURCE) endif (OPT_BUILD_AUDIO_SOURCE)
if (OPT_BUILD_BADGESDR_SOURCE)
add_subdirectory("source_modules/badgesdr_source")
endif (OPT_BUILD_BADGESDR_SOURCE)
if (OPT_BUILD_BLADERF_SOURCE) if (OPT_BUILD_BLADERF_SOURCE)
add_subdirectory("source_modules/bladerf_source") add_subdirectory("source_modules/bladerf_source")
endif (OPT_BUILD_BLADERF_SOURCE) endif (OPT_BUILD_BLADERF_SOURCE)
if (OPT_BUILD_DRAGONLABS_SOURCE)
add_subdirectory("source_modules/dragonlabs_source")
endif (OPT_BUILD_DRAGONLABS_SOURCE)
if (OPT_BUILD_FILE_SOURCE) if (OPT_BUILD_FILE_SOURCE)
add_subdirectory("source_modules/file_source") add_subdirectory("source_modules/file_source")
endif (OPT_BUILD_FILE_SOURCE) endif (OPT_BUILD_FILE_SOURCE)
if (OPT_BUILD_FOBOSSDR_SOURCE)
add_subdirectory("source_modules/fobossdr_source")
endif (OPT_BUILD_FOBOSSDR_SOURCE)
if (OPT_BUILD_HACKRF_SOURCE) if (OPT_BUILD_HACKRF_SOURCE)
add_subdirectory("source_modules/hackrf_source") add_subdirectory("source_modules/hackrf_source")
endif (OPT_BUILD_HACKRF_SOURCE) endif (OPT_BUILD_HACKRF_SOURCE)
if (OPT_BUILD_HAROGIC_SOURCE)
add_subdirectory("source_modules/harogic_source")
endif (OPT_BUILD_HAROGIC_SOURCE)
if (OPT_BUILD_HERMES_SOURCE) if (OPT_BUILD_HERMES_SOURCE)
add_subdirectory("source_modules/hermes_source") add_subdirectory("source_modules/hermes_source")
endif (OPT_BUILD_HERMES_SOURCE) endif (OPT_BUILD_HERMES_SOURCE)
if (OPT_BUILD_HYDRASDR_SOURCE)
add_subdirectory("source_modules/hydrasdr_source")
endif (OPT_BUILD_HYDRASDR_SOURCE)
if (OPT_BUILD_KCSDR_SOURCE)
add_subdirectory("source_modules/kcsdr_source")
endif (OPT_BUILD_KCSDR_SOURCE)
if (OPT_BUILD_LIMESDR_SOURCE) if (OPT_BUILD_LIMESDR_SOURCE)
add_subdirectory("source_modules/limesdr_source") add_subdirectory("source_modules/limesdr_source")
endif (OPT_BUILD_LIMESDR_SOURCE) endif (OPT_BUILD_LIMESDR_SOURCE)
if (OPT_BUILD_NETWORK_SOURCE)
add_subdirectory("source_modules/network_source")
endif (OPT_BUILD_NETWORK_SOURCE)
if (OPT_BUILD_PERSEUS_SOURCE) if (OPT_BUILD_PERSEUS_SOURCE)
add_subdirectory("source_modules/perseus_source") add_subdirectory("source_modules/perseus_source")
endif (OPT_BUILD_PERSEUS_SOURCE) endif (OPT_BUILD_PERSEUS_SOURCE)
@@ -150,6 +192,10 @@ if (OPT_BUILD_PLUTOSDR_SOURCE)
add_subdirectory("source_modules/plutosdr_source") add_subdirectory("source_modules/plutosdr_source")
endif (OPT_BUILD_PLUTOSDR_SOURCE) endif (OPT_BUILD_PLUTOSDR_SOURCE)
if (OPT_BUILD_RFNM_SOURCE)
add_subdirectory("source_modules/rfnm_source")
endif (OPT_BUILD_RFNM_SOURCE)
if (OPT_BUILD_RFSPACE_SOURCE) if (OPT_BUILD_RFSPACE_SOURCE)
add_subdirectory("source_modules/rfspace_source") add_subdirectory("source_modules/rfspace_source")
endif (OPT_BUILD_RFSPACE_SOURCE) endif (OPT_BUILD_RFSPACE_SOURCE)
@@ -162,6 +208,10 @@ if (OPT_BUILD_RTL_TCP_SOURCE)
add_subdirectory("source_modules/rtl_tcp_source") add_subdirectory("source_modules/rtl_tcp_source")
endif (OPT_BUILD_RTL_TCP_SOURCE) endif (OPT_BUILD_RTL_TCP_SOURCE)
if (OPT_BUILD_SDDC_SOURCE)
add_subdirectory("source_modules/sddc_source")
endif (OPT_BUILD_SDDC_SOURCE)
if (OPT_BUILD_SDRPP_SERVER_SOURCE) if (OPT_BUILD_SDRPP_SERVER_SOURCE)
add_subdirectory("source_modules/sdrpp_server_source") add_subdirectory("source_modules/sdrpp_server_source")
endif (OPT_BUILD_SDRPP_SERVER_SOURCE) endif (OPT_BUILD_SDRPP_SERVER_SOURCE)
@@ -218,6 +268,10 @@ if (OPT_BUILD_ATV_DECODER)
add_subdirectory("decoder_modules/atv_decoder") add_subdirectory("decoder_modules/atv_decoder")
endif (OPT_BUILD_ATV_DECODER) endif (OPT_BUILD_ATV_DECODER)
if (OPT_BUILD_DAB_DECODER)
add_subdirectory("decoder_modules/dab_decoder")
endif (OPT_BUILD_DAB_DECODER)
if (OPT_BUILD_FALCON9_DECODER) if (OPT_BUILD_FALCON9_DECODER)
add_subdirectory("decoder_modules/falcon9_decoder") add_subdirectory("decoder_modules/falcon9_decoder")
endif (OPT_BUILD_FALCON9_DECODER) endif (OPT_BUILD_FALCON9_DECODER)
@@ -234,10 +288,22 @@ if (OPT_BUILD_METEOR_DEMODULATOR)
add_subdirectory("decoder_modules/meteor_demodulator") add_subdirectory("decoder_modules/meteor_demodulator")
endif (OPT_BUILD_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) if (OPT_BUILD_RADIO)
add_subdirectory("decoder_modules/radio") add_subdirectory("decoder_modules/radio")
endif (OPT_BUILD_RADIO) endif (OPT_BUILD_RADIO)
if (OPT_BUILD_RYFI_DECODER)
add_subdirectory("decoder_modules/ryfi_decoder")
endif (OPT_BUILD_RYFI_DECODER)
if (OPT_BUILD_VOR_RECEIVER)
add_subdirectory("decoder_modules/vor_receiver")
endif (OPT_BUILD_VOR_RECEIVER)
if (OPT_BUILD_WEATHER_SAT_DECODER) if (OPT_BUILD_WEATHER_SAT_DECODER)
add_subdirectory("decoder_modules/weather_sat_decoder") add_subdirectory("decoder_modules/weather_sat_decoder")
endif (OPT_BUILD_WEATHER_SAT_DECODER) endif (OPT_BUILD_WEATHER_SAT_DECODER)
@@ -252,6 +318,10 @@ if (OPT_BUILD_FREQUENCY_MANAGER)
add_subdirectory("misc_modules/frequency_manager") add_subdirectory("misc_modules/frequency_manager")
endif (OPT_BUILD_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) if (OPT_BUILD_RECORDER)
add_subdirectory("misc_modules/recorder") add_subdirectory("misc_modules/recorder")
endif (OPT_BUILD_RECORDER) endif (OPT_BUILD_RECORDER)
@@ -272,7 +342,12 @@ if (OPT_BUILD_SCHEDULER)
add_subdirectory("misc_modules/scheduler") add_subdirectory("misc_modules/scheduler")
endif (OPT_BUILD_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) target_link_libraries(sdrpp PRIVATE sdrpp_core)
# Compiler arguments # Compiler arguments
@@ -282,6 +357,21 @@ target_compile_options(sdrpp PRIVATE ${SDRPP_COMPILER_FLAGS})
if (MSVC) if (MSVC)
add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) add_custom_target(do_always ALL xcopy /s \"$<TARGET_FILE_DIR:sdrpp_core>\\*.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y) add_custom_target(do_always_volk ALL xcopy /s \"C:/Program Files/PothosSDR/bin\\volk.dll\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y)
if (COPY_MSVC_REDISTRIBUTABLES)
# Get the list of Visual C++ runtime DLLs
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP True)
include(InstallRequiredSystemLibraries)
# Create a space sperated list
set(REDIST_DLLS_STR "")
foreach(DLL IN LISTS CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS)
set(REDIST_DLLS_STR COMMAND xcopy /F \"${DLL}\" \"$<TARGET_FILE_DIR:sdrpp>\" /Y ${REDIST_DLLS_STR})
endforeach()
# Create target
add_custom_target(do_always_msvc ALL ${REDIST_DLLS_STR})
endif ()
endif () endif ()
@@ -302,7 +392,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>\") add_custom_target(do_always ALL cp \"$<TARGET_FILE_DIR:sdrpp_core>/libsdrpp_core.dylib\" \"$<TARGET_FILE_DIR:sdrpp>\")
endif () 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 # Create module cmake file
configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/sdrpp_module.cmake ${CMAKE_CURRENT_BINARY_DIR}/sdrpp_module.cmake @ONLY)
@@ -323,3 +413,5 @@ endif ()
# Create uninstall target # Create uninstall target
configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY) configure_file(${CMAKE_SOURCE_DIR}/cmake_uninstall.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake @ONLY)
add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) add_custom_target(uninstall ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
# Create headers target

View File

@@ -10,11 +10,11 @@ android {
minSdkVersion 28 minSdkVersion 28
targetSdkVersion 28 targetSdkVersion 28
versionCode 1 versionCode 1
versionName "1.1.0" versionName "1.2.1"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF" arguments "-DOPT_BACKEND_GLFW=OFF", "-DOPT_BACKEND_ANDROID=ON", "-DOPT_BUILD_SOAPY_SOURCE=OFF", "-DOPT_BUILD_ANDROID_AUDIO_SINK=ON", "-DOPT_BUILD_AUDIO_SINK=OFF", "-DOPT_BUILD_DISCORD_PRESENCE=OFF", "-DOPT_BUILD_M17_DECODER=ON", "-DOPT_BUILD_PLUTOSDR_SOURCE=ON", "-DOPT_BUILD_AUDIO_SOURCE=OFF", "-DOPT_BUILD_HYDRASDR_SOURCE=ON"
} }
} }
} }

View File

@@ -1,65 +1,12 @@
# AI USE IS FORBIDDEN
Use of AI for the creation of code, pull requests or issues is forbidden.
Submitting code, pull requests or issues generated by AI will result in a permanent ban from all of my (Alexandre Rouma) repositories.
# Pull Requests # Pull Requests
**I DO NOT ACCEPT PULL-REQUEST FOR FEATURES OR BUGFIXES REQUIRING SIGNIFICANT CODE/STRUCTURE CHANGES.** Code pull requests are **NOT welcome**. Please open an issue discussing potential bugfixes or feature requests instead.
**SUCH PULL REQUESTS WILL BE CLOSED AUTOMATICALLY. OPEN AN ISSUE DETAILING FEATURE REQUESTS OR POTENTIAL BUGFIX INSTEAD.**
# 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**
## Band Frequency Allocation ## Band Frequency Allocation
@@ -119,8 +66,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. 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.
* Use VSCode for development, VS seems to cause issues.
* DO NOT use libboost for any code meant for this repository **IMPORTANT: JSON File cannot contain comments, there are only in this example for clarity**

View File

@@ -11,6 +11,7 @@ namespace backend {
extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS; extern const std::vector<DevVIDPID> AIRSPY_VIDPIDS;
extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS; extern const std::vector<DevVIDPID> AIRSPYHF_VIDPIDS;
extern const std::vector<DevVIDPID> HACKRF_VIDPIDS; extern const std::vector<DevVIDPID> HACKRF_VIDPIDS;
extern const std::vector<DevVIDPID> HYDRASDR_VIDPIDS;
extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS; extern const std::vector<DevVIDPID> RTL_SDR_VIDPIDS;
int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids); int getDeviceFD(int& vid, int& pid, const std::vector<DevVIDPID>& allowedVidPids);

View File

@@ -408,6 +408,11 @@ namespace backend {
{ 0x1d50, 0xcc15 } { 0x1d50, 0xcc15 }
}; };
const std::vector<DevVIDPID> HYDRASDR_VIDPIDS = {
{ 0x1d50, 0x60a1 },
{ 0x38af, 0x0001 }
};
const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = { const std::vector<DevVIDPID> RTL_SDR_VIDPIDS = {
{ 0x0bda, 0x2832 }, { 0x0bda, 0x2832 },
{ 0x0bda, 0x2838 }, { 0x0bda, 0x2838 },

View File

@@ -99,6 +99,9 @@ namespace backend {
glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API); glfwWindowHint(GLFW_CLIENT_API, OPENGL_VERSIONS_IS_ES[i] ? GLFW_OPENGL_ES_API : GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_VERSIONS_MAJOR[i]);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_VERSIONS_MINOR[i]);
#if GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4)
glfwWindowHintString(GLFW_WAYLAND_APP_ID, "sdrpp");
#endif
// Create window with graphics context // Create window with graphics context
monitor = glfwGetPrimaryMonitor(); monitor = glfwGetPrimaryMonitor();

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8) cmake_minimum_required(VERSION 3.13)
project(Correct C) project(Correct C)
include(CheckLibraryExists) include(CheckLibraryExists)
include(CheckIncludeFiles) include(CheckIncludeFiles)

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, shift_register_t history_buffer_search(history_buffer *buf, const distance_t *distances,
unsigned int search_every) { unsigned int search_every) {
shift_register_t bestpath; shift_register_t bestpath = 0;
distance_t leasterror = USHRT_MAX; distance_t leasterror = USHRT_MAX;
// search for a state with the least error // search for a state with the least error
for (shift_register_t state = 0; state < buf->num_states; state += search_every) { 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 { try {
carg.ival = std::stoi(arg); carg.ival = std::stoi(arg);
} }
catch (std::exception e) { catch (const std::exception& e) {
printf("Invalid argument, failed to parse integer\n"); printf("Invalid argument, failed to parse integer\n");
showHelp(); showHelp();
return -1; return -1;
@@ -98,7 +98,7 @@ int CommandArgsParser::parse(int argc, char* argv[]) {
try { try {
carg.fval = std::stod(arg); carg.fval = std::stod(arg);
} }
catch (std::exception e) { catch (const std::exception& e) {
printf("Invalid argument, failed to parse float\n"); printf("Invalid argument, failed to parse float\n");
showHelp(); showHelp();
return -1; return -1;

View File

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

View File

@@ -147,12 +147,12 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["menuElements"][3]["name"] = "Sinks"; defConfig["menuElements"][3]["name"] = "Sinks";
defConfig["menuElements"][3]["open"] = true; defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][3]["name"] = "Frequency Manager"; defConfig["menuElements"][4]["name"] = "Frequency Manager";
defConfig["menuElements"][3]["open"] = true;
defConfig["menuElements"][4]["name"] = "VFO Color";
defConfig["menuElements"][4]["open"] = true; defConfig["menuElements"][4]["open"] = true;
defConfig["menuElements"][5]["name"] = "VFO Color";
defConfig["menuElements"][5]["open"] = true;
defConfig["menuElements"][6]["name"] = "Band Plan"; defConfig["menuElements"][6]["name"] = "Band Plan";
defConfig["menuElements"][6]["open"] = true; defConfig["menuElements"][6]["open"] = true;
@@ -171,18 +171,30 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["Audio Source"]["enabled"] = true; defConfig["moduleInstances"]["Audio Source"]["enabled"] = true;
defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source"; defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source";
defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true; defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["Dragon Labs Source"]["module"] = "dragonlabs_source";
defConfig["moduleInstances"]["Dragon Labs Source"]["enabled"] = true;
defConfig["moduleInstances"]["File Source"]["module"] = "file_source"; defConfig["moduleInstances"]["File Source"]["module"] = "file_source";
defConfig["moduleInstances"]["File Source"]["enabled"] = true; defConfig["moduleInstances"]["File Source"]["enabled"] = true;
defConfig["moduleInstances"]["FobosSDR Source"]["module"] = "fobossdr_source";
defConfig["moduleInstances"]["FobosSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source"; defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source";
defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true; defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true;
defConfig["moduleInstances"]["Harogic Source"]["module"] = "harogic_source";
defConfig["moduleInstances"]["Harogic Source"]["enabled"] = true;
defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source"; defConfig["moduleInstances"]["Hermes Source"]["module"] = "hermes_source";
defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true; defConfig["moduleInstances"]["Hermes Source"]["enabled"] = true;
defConfig["moduleInstances"]["HydraSDR Source"]["module"] = "hydrasdr_source";
defConfig["moduleInstances"]["HydraSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source"; defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source";
defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; defConfig["moduleInstances"]["Network Source"]["module"] = "network_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["Network Source"]["enabled"] = true;
defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source"; defConfig["moduleInstances"]["PerseusSDR Source"]["module"] = "perseus_source";
defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["PerseusSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source";
defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFNM Source"]["module"] = "rfnm_source";
defConfig["moduleInstances"]["RFNM Source"]["enabled"] = true;
defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source"; defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source";
defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true; defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true;
defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source"; defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source";
@@ -193,10 +205,12 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true; defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true;
defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source"; defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source";
defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true; defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true;
defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source"; defConfig["moduleInstances"]["Spectran HTTP Source"]["module"] = "spectran_http_source";
defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true; defConfig["moduleInstances"]["Spectran HTTP Source"]["enabled"] = true;
defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source"; defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source";
defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true; defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true;
defConfig["moduleInstances"]["USRP Source"]["module"] = "usrp_source";
defConfig["moduleInstances"]["USRP Source"]["enabled"] = true;
defConfig["moduleInstances"]["Audio Sink"] = "audio_sink"; defConfig["moduleInstances"]["Audio Sink"] = "audio_sink";
defConfig["moduleInstances"]["Network Sink"] = "network_sink"; defConfig["moduleInstances"]["Network Sink"] = "network_sink";
@@ -222,12 +236,19 @@ int sdrpp_main(int argc, char* argv[]) {
defConfig["modules"] = json::array(); defConfig["modules"] = json::array();
defConfig["offsetMode"] = (int)0; // Off defConfig["offsets"]["SpyVerter"] = 120000000.0;
defConfig["offset"] = 0.0; defConfig["offsets"]["Ham-It-Up"] = 125000000.0;
defConfig["offsets"]["MMDS S-band (1998MHz)"] = -1998000000.0;
defConfig["offsets"]["DK5AV X-Band"] = -6800000000.0;
defConfig["offsets"]["Ku LNB (9750MHz)"] = -9750000000.0;
defConfig["offsets"]["Ku LNB (10700MHz)"] = -10700000000.0;
defConfig["selectedOffset"] = "None";
defConfig["manualOffset"] = 0.0;
defConfig["showMenu"] = true; defConfig["showMenu"] = true;
defConfig["showWaterfall"] = true; defConfig["showWaterfall"] = true;
defConfig["source"] = ""; defConfig["source"] = "";
defConfig["decimationPower"] = 0; defConfig["decimation"] = 1;
defConfig["iqCorrection"] = false; defConfig["iqCorrection"] = false;
defConfig["invertIQ"] = false; defConfig["invertIQ"] = false;
@@ -278,6 +299,7 @@ int sdrpp_main(int argc, char* argv[]) {
core::configManager.conf["modules"][modCount++] = "airspyhf_source.so"; core::configManager.conf["modules"][modCount++] = "airspyhf_source.so";
core::configManager.conf["modules"][modCount++] = "hackrf_source.so"; core::configManager.conf["modules"][modCount++] = "hackrf_source.so";
core::configManager.conf["modules"][modCount++] = "hermes_source.so"; core::configManager.conf["modules"][modCount++] = "hermes_source.so";
core::configManager.conf["modules"][modCount++] = "hydrasdr_source.so";
core::configManager.conf["modules"][modCount++] = "plutosdr_source.so"; core::configManager.conf["modules"][modCount++] = "plutosdr_source.so";
core::configManager.conf["modules"][modCount++] = "rfspace_source.so"; core::configManager.conf["modules"][modCount++] = "rfspace_source.so";
core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so"; core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so";
@@ -308,12 +330,18 @@ int sdrpp_main(int argc, char* argv[]) {
// Remove unused elements // Remove unused elements
auto items = core::configManager.conf.items(); auto items = core::configManager.conf.items();
auto newConf = core::configManager.conf;
bool configCorrected = false;
for (auto const& item : items) { for (auto const& item : items) {
if (!defConfig.contains(item.key())) { if (!defConfig.contains(item.key())) {
flog::info("Unused key in config {0}, repairing", item.key()); flog::info("Unused key in config {0}, repairing", item.key());
core::configManager.conf.erase(item.key()); newConf.erase(item.key());
configCorrected = true;
} }
} }
if (configCorrected) {
core::configManager.conf = newConf;
}
// Update to new module representation in config if needed // Update to new module representation in config if needed
for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) { for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) {

View File

@@ -37,13 +37,20 @@ namespace sdrpp_credits {
const char* hardwareDonators[] = { const char* hardwareDonators[] = {
"Aaronia AG", "Aaronia AG",
"Airspy", "Airspy",
"Alex 4Z5LV",
"Analog Devices", "Analog Devices",
"CaribouLabs", "CaribouLabs",
"Deepace",
"Ettus Research", "Ettus Research",
"Harogic",
"Howard Su", "Howard Su",
"MicroPhase",
"Microtelecom",
"MyriadRF", "MyriadRF",
"Nuand", "Nuand",
"RFNM",
"RFspace", "RFspace",
"RigExpert",
"RTL-SDRblog", "RTL-SDRblog",
"SDRplay" "SDRplay"
}; };
@@ -54,6 +61,7 @@ namespace sdrpp_credits {
"Croccydile", "Croccydile",
"Dale L Puckett (K0HYD)", "Dale L Puckett (K0HYD)",
"Daniele D'Agnelli", "Daniele D'Agnelli",
"David Taylor (GM8ARV)",
"D. Jones", "D. Jones",
"Dexruus", "Dexruus",
"EB3FRN", "EB3FRN",
@@ -62,15 +70,18 @@ namespace sdrpp_credits {
"Flinger Films", "Flinger Films",
"Frank Werner (HB9FXQ)", "Frank Werner (HB9FXQ)",
"gringogrigio", "gringogrigio",
"Jandro",
"Jeff Moe", "Jeff Moe",
"Joe Cupano", "Joe Cupano",
"KD1SQ", "KD1SQ",
"Kezza", "Kezza",
"Krys Kamieniecki", "Krys Kamieniecki",
"Lee Donaghy", "Lee Donaghy",
"Lee KD1SQ", "Lee (KD1SQ)",
".lozenge. (Hank Hill)", ".lozenge. (Hank Hill)",
"Martin Herren (HB9FXX)", "Martin Herren (HB9FXX)",
"NeoVilsonWong",
"Nitin (VU2JEK)",
"ON4MU", "ON4MU",
"Passion-Radio.com", "Passion-Radio.com",
"Paul Maine", "Paul Maine",
@@ -81,6 +92,7 @@ namespace sdrpp_credits {
"Syne Ardwin (WI9SYN)", "Syne Ardwin (WI9SYN)",
"W4IPA", "W4IPA",
"William Arcand (W1WRA)", "William Arcand (W1WRA)",
"William Pitchford",
"Yves Rougy", "Yves Rougy",
"Zipper" "Zipper"
}; };

View File

@@ -9,6 +9,7 @@
namespace dsp { namespace dsp {
class generic_block { class generic_block {
public: public:
virtual ~generic_block() {}
virtual void start() {} virtual void start() {}
virtual void stop() {} virtual void stop() {}
virtual int run() { return -1; } virtual int run() { return -1; }
@@ -16,8 +17,6 @@ namespace dsp {
class block : public generic_block { class block : public generic_block {
public: public:
virtual void init() {}
virtual ~block() { virtual ~block() {
if (!_block_init) { return; } if (!_block_init) { return; }
stop(); stop();

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <volk/volk.h> #include <volk/volk.h>
#include <string.h>
namespace dsp::buffer { namespace dsp::buffer {
template<class T> template<class T>

View File

@@ -93,7 +93,7 @@ namespace dsp {
void disableBlock(Processor<T, T>* block, Func onOutputChange) { void disableBlock(Processor<T, T>* block, Func onOutputChange) {
// Check that the block is part of the chain // Check that the block is part of the chain
if (!blockExists(block)) { 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 // If already disabled, don't do anything
@@ -163,10 +163,12 @@ namespace dsp {
private: private:
Processor<T, T>* blockBefore(Processor<T, T>* block) { Processor<T, T>* blockBefore(Processor<T, T>* block) {
Processor<T, T>* prev = NULL;
for (auto& ln : links) { for (auto& ln : links) {
if (ln == block) { return NULL; } if (ln == block) { return prev; }
if (states[ln]) { return ln; } if (states[ln]) { prev = ln; }
} }
return NULL;
} }
Processor<T, T>* blockAfter(Processor<T, T>* block) { 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) { 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); volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)out, (lv_32fc_t*)in, phaseDelta, &phase, count);
#endif
return count; return count;
} }

View File

@@ -12,6 +12,10 @@ namespace dsp::compression {
void init(stream<complex_t>* in, PCMType pcmType) { void init(stream<complex_t>* in, PCMType pcmType) {
_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); base_type::init(in);
} }

View File

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

View File

@@ -22,18 +22,17 @@ namespace dsp::demod {
dsp::taps::free(filterTaps); dsp::taps::free(filterTaps);
} }
void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass, bool highPass) { void init(dsp::stream<dsp::complex_t>* in, double samplerate, double bandwidth, bool lowPass) {
_samplerate = samplerate; _samplerate = samplerate;
_bandwidth = bandwidth; _bandwidth = bandwidth;
_lowPass = lowPass; _lowPass = lowPass;
_highPass = highPass;
demod.init(NULL, bandwidth / 2.0, _samplerate); demod.init(NULL, bandwidth / 2.0, _samplerate);
loadDummyTaps(); loadDummyTaps();
fir.init(NULL, filterTaps); fir.init(NULL, filterTaps);
// Initialize taps // Initialize taps
updateFilter(lowPass, highPass); updateFilter(lowPass);
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.out.free(); demod.out.free();
@@ -49,7 +48,7 @@ namespace dsp::demod {
base_type::tempStop(); base_type::tempStop();
_samplerate = samplerate; _samplerate = samplerate;
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
updateFilter(_lowPass, _highPass); updateFilter(_lowPass);
base_type::tempStart(); base_type::tempStart();
} }
@@ -59,19 +58,13 @@ namespace dsp::demod {
if (bandwidth == _bandwidth) { return; } if (bandwidth == _bandwidth) { return; }
_bandwidth = bandwidth; _bandwidth = bandwidth;
demod.setDeviation(_bandwidth / 2.0, _samplerate); demod.setDeviation(_bandwidth / 2.0, _samplerate);
updateFilter(_lowPass, _highPass); updateFilter(_lowPass);
} }
void setLowPass(bool lowPass) { void setLowPass(bool lowPass) {
assert(base_type::_block_init); assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
updateFilter(lowPass, _highPass); updateFilter(lowPass);
}
void setHighPass(bool highPass) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
updateFilter(_lowPass, highPass);
} }
void reset() { void reset() {
@@ -86,14 +79,14 @@ namespace dsp::demod {
inline int process(int count, dsp::complex_t* in, T* out) { inline int process(int count, dsp::complex_t* in, T* out) {
if constexpr (std::is_same_v<T, float>) { if constexpr (std::is_same_v<T, float>) {
demod.process(count, in, out); demod.process(count, in, out);
if (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
fir.process(count, out, out); fir.process(count, out, out);
} }
} }
if constexpr (std::is_same_v<T, stereo_t>) { if constexpr (std::is_same_v<T, stereo_t>) {
demod.process(count, in, demod.out.writeBuf); demod.process(count, in, demod.out.writeBuf);
if (filtering) { if (_lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
fir.process(count, demod.out.writeBuf, demod.out.writeBuf); fir.process(count, demod.out.writeBuf, demod.out.writeBuf);
} }
@@ -114,25 +107,17 @@ namespace dsp::demod {
} }
private: private:
void updateFilter(bool lowPass, bool highPass) { void updateFilter(bool lowPass) {
std::lock_guard<std::mutex> lck(filterMtx); std::lock_guard<std::mutex> lck(filterMtx);
// Update values // Update values
_lowPass = lowPass; _lowPass = lowPass;
_highPass = highPass;
filtering = (lowPass || highPass);
// Free filter taps // Free filter taps
dsp::taps::free(filterTaps); dsp::taps::free(filterTaps);
// Generate filter depending on low and high pass settings // Generate filter depending on the low pass settings
if (_lowPass && _highPass) { if (_lowPass) {
filterTaps = dsp::taps::bandPass<float>(300.0, _bandwidth / 2.0, 100.0, _samplerate);
}
else if (_highPass) {
filterTaps = dsp::taps::highPass(300.0, 100.0, _samplerate);
}
else if (_lowPass) {
filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate); filterTaps = dsp::taps::lowPass(_bandwidth / 2.0, (_bandwidth / 2.0) * 0.1, _samplerate);
} }
else { else {
@@ -152,7 +137,6 @@ namespace dsp::demod {
double _samplerate; double _samplerate;
double _bandwidth; double _bandwidth;
bool _lowPass; bool _lowPass;
bool _highPass;
bool filtering; bool filtering;
Quadrature demod; Quadrature demod;

View File

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

View File

@@ -41,10 +41,10 @@ namespace dsp::filter {
// Move existing data to make transition seemless // Move existing data to make transition seemless
if (_taps.size < oldTC) { 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) { 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); buffer::clear<D>(buffer, _taps.size - oldTC);
} }

View File

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

View File

@@ -0,0 +1,303 @@
#pragma once
#include "../channel/rx_vfo.h"
#include "../demod/quadrature.h"
#include "../filter/fir.h"
#include "../taps/high_pass.h"
#include <fftw3.h>
#include <map>
#include <atomic>
#define CTCSS_DECODE_SAMPLERATE 500//250.0
#define CTCSS_DECODE_BANDWIDTH 200.0
#define CTCSS_DECODE_OFFSET 160.55
namespace dsp::noise_reduction {
enum CTCSSTone {
/**
* Indicates that any valid tone will let audio through.
*/
CTCSS_TONE_ANY = -2,
/**
* Indicates that no tone is being received, or to act as a decoder only, letting audio through continuously.
*/
CTCSS_TONE_NONE = -1,
/**
* CTCSS Tone Frequency.
*/
CTCSS_TONE_67Hz,
CTCSS_TONE_69_3Hz,
CTCSS_TONE_71_9Hz,
CTCSS_TONE_74_4Hz,
CTCSS_TONE_77Hz,
CTCSS_TONE_79_7Hz,
CTCSS_TONE_82_5Hz,
CTCSS_TONE_85_4Hz,
CTCSS_TONE_88_5Hz,
CTCSS_TONE_91_5Hz,
CTCSS_TONE_94_8Hz,
CTCSS_TONE_97_4Hz,
CTCSS_TONE_100Hz,
CTCSS_TONE_103_5Hz,
CTCSS_TONE_107_2Hz,
CTCSS_TONE_110_9Hz,
CTCSS_TONE_114_8Hz,
CTCSS_TONE_118_8Hz,
CTCSS_TONE_123Hz,
CTCSS_TONE_127_3Hz,
CTCSS_TONE_131_8Hz,
CTCSS_TONE_136_5Hz,
CTCSS_TONE_141_3Hz,
CTCSS_TONE_146_2Hz,
CTCSS_TONE_150Hz,
CTCSS_TONE_151_4Hz,
CTCSS_TONE_156_7Hz,
CTCSS_TONE_159_8Hz,
CTCSS_TONE_162_2Hz,
CTCSS_TONE_165_5Hz,
CTCSS_TONE_167_9Hz,
CTCSS_TONE_171_3Hz,
CTCSS_TONE_173_8Hz,
CTCSS_TONE_177_3Hz,
CTCSS_TONE_179_9Hz,
CTCSS_TONE_183_5Hz,
CTCSS_TONE_186_2Hz,
CTCSS_TONE_189_9Hz,
CTCSS_TONE_192_8Hz,
CTCSS_TONE_196_6Hz,
CTCSS_TONE_199_5Hz,
CTCSS_TONE_203_5Hz,
CTCSS_TONE_206_5Hz,
CTCSS_TONE_210_7Hz,
CTCSS_TONE_218_1Hz,
CTCSS_TONE_225_7Hz,
CTCSS_TONE_229_1Hz,
CTCSS_TONE_233_6Hz,
CTCSS_TONE_241_8Hz,
CTCSS_TONE_250_3Hz,
CTCSS_TONE_254_1Hz,
_CTCSS_TONE_COUNT
};
const float CTCSS_TONES[_CTCSS_TONE_COUNT] = {
67.0f,
69.3f,
71.9f,
74.4f,
77.0f,
79.7f,
82.5f,
85.4f,
88.5f,
91.5f,
94.8f,
97.4f,
100.0f,
103.5f,
107.2f,
110.9f,
114.8f,
118.8f,
123.0f,
127.3f,
131.8f,
136.5f,
141.3f,
146.2f,
150.0f,
151.4f,
156.7f,
159.8f,
162.2f,
165.5f,
167.9f,
171.3f,
173.8f,
177.3f,
179.9f,
183.5f,
186.2f,
189.9f,
192.8f,
196.6f,
199.5f,
203.5f,
206.5f,
210.7f,
218.1f,
225.7f,
229.1f,
233.6f,
241.8f,
250.3f,
254.1f
};
class CTCSSSquelch : public Processor<stereo_t, stereo_t> {
using base_type = Processor<stereo_t, stereo_t>;
public:
CTCSSSquelch() {}
CTCSSSquelch(stream<stereo_t>* in, double samplerate) { init(in, samplerate); }
~CTCSSSquelch() {
// If not initialized, do nothing
if (!base_type::_block_init) { return; }
// Stop the DSP thread
base_type::stop();
}
void init(stream<stereo_t>* in, double samplerate) {
// Save settings
_samplerate = samplerate;
// Create dummy taps just for initialization
float dummy[1] = { 1.0f };
auto dummyTaps = dsp::taps::fromArray(1, dummy);
// Initialize the DDC and FM demod
ddc.init(NULL, samplerate, CTCSS_DECODE_SAMPLERATE, CTCSS_DECODE_BANDWIDTH, CTCSS_DECODE_OFFSET);
fm.init(NULL, 1.0, CTCSS_DECODE_SAMPLERATE);
// Initilize the base block class
base_type::init(in);
}
void setSamplerate(double samplerate) {
assert(base_type::_block_init);
std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx);
base_type::tempStop();
_samplerate = samplerate;
ddc.setInSamplerate(samplerate);
base_type::tempStart();
}
void setRequiredTone(CTCSSTone tone) {
assert(base_type::_block_init);
requiredTone = tone;
}
CTCSSTone getCurrentTone() {
assert(base_type::_block_init);
return currentTone;
}
inline int process(int count, const stereo_t* in, stereo_t* out) {
// Shift and resample to the correct samplerate
int ddcOutCount = ddc.process(count, (complex_t*)in, ddc.out.writeBuf);
// FM Demod the CTCSS tone
fm.process(ddcOutCount, ddc.out.writeBuf, fm.out.writeBuf);
// Get the required tone
const CTCSSTone rtone = requiredTone;
// Detect the tone frequency
for (int i = 0; i < ddcOutCount; i++) {
// Compute the running mean
const float val = fm.out.writeBuf[i];
mean = 0.95f*mean + 0.05f*val;
// Compute the running variance
const float err = val - mean;
var = 0.95f*var + 0.05f*err*err;
// Run a schmitt trigger on the variance
bool nvarOk = varOk ? (var < 1100.0f) : (var < 1000.0f);
// Check if the tone has to be rematched
if (nvarOk && (!varOk || mean < minFreq || mean > maxFreq)) {
// Compute the absolute frequency
float freq = mean + CTCSS_DECODE_OFFSET;
// Check it against the known tones
if (freq < CTCSS_TONES[0] - 2.5) {
currentTone = CTCSS_TONE_NONE;
}
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1] + 2.5) {
currentTone = CTCSS_TONE_NONE;
}
else if (freq < CTCSS_TONES[0]) {
currentTone = (CTCSSTone)0;
}
else if (freq > CTCSS_TONES[_CTCSS_TONE_COUNT-1]) {
currentTone = (CTCSSTone)(_CTCSS_TONE_COUNT-1);
}
else {
int a = 0;
int b = _CTCSS_TONE_COUNT-1;
while (b - a > 1) {
int c = (a + b) >> 1;
((CTCSS_TONES[c] < freq) ? a : b) = c;
}
currentTone = (CTCSSTone)((freq - CTCSS_TONES[a] < CTCSS_TONES[b] - freq) ? a : b);
}
// Update the mute status
mute = !(currentTone == rtone || (currentTone != CTCSS_TONE_NONE && rtone == CTCSS_TONE_ANY));
// Unmuted the audio if needed
// TODO
// Recompute min and max freq if a valid tone is detected
if (currentTone != CTCSS_TONE_NONE) {
float c = CTCSS_TONES[currentTone];
float l = (currentTone > CTCSS_TONE_67Hz) ? CTCSS_TONES[currentTone - 1] : c - 2.5f;
float r = (currentTone < CTCSS_TONE_254_1Hz) ? CTCSS_TONES[currentTone + 1] : c + 2.5f;
minFreq = (l+c) / 2.0f;
maxFreq = (r+c) / 2.0f;
}
}
// Check for a rising edge on the variance
if (!nvarOk && varOk) {
// Mute the audio
// TODO
mute = true;
currentTone = CTCSS_TONE_NONE;
}
// Save the new variance state
varOk = nvarOk;
}
// DEBUG ONLY
if ((rtone != CTCSS_TONE_NONE) && mute) {
memset(out, 0, count * sizeof(stereo_t));
}
else {
memcpy(out, in, count * sizeof(stereo_t));
}
return count;
}
int run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
process(count, base_type::_in->readBuf, base_type::out.writeBuf);
base_type::_in->flush();
if (!base_type::out.swap(count)) { return -1; }
return count;
}
private:
double _samplerate;
std::atomic<CTCSSTone> requiredTone = CTCSS_TONE_ANY;
float mean = 0.0f;
float var = 0.0f;
bool varOk = false;
float minFreq = 0.0f;
float maxFreq = 0.0f;
bool mute = true;
std::atomic<CTCSSTone> currentTone = CTCSS_TONE_NONE;
channel::RxVFO ddc;
demod::Quadrature fm;
};
}

View File

@@ -3,14 +3,14 @@
// TODO: Rewrite better!!!!! // TODO: Rewrite better!!!!!
namespace dsp::noise_reduction { namespace dsp::noise_reduction {
class Squelch : public Processor<complex_t, complex_t> { class PowerSquelch : public Processor<complex_t, complex_t> {
using base_type = Processor<complex_t, complex_t>; using base_type = Processor<complex_t, complex_t>;
public: public:
Squelch() {} PowerSquelch() {}
Squelch(stream<complex_t>* in, double level) {} PowerSquelch(stream<complex_t>* in, double level) {}
~Squelch() { ~PowerSquelch() {
if (!base_type::_block_init) { return; } if (!base_type::_block_init) { return; }
base_type::stop(); base_type::stop();
buffer::free(normBuffer); buffer::free(normBuffer);
@@ -31,8 +31,11 @@ namespace dsp::noise_reduction {
} }
inline int process(int count, const complex_t* in, complex_t* out) { inline int process(int count, const complex_t* in, complex_t* out) {
float sum; // Compute the amplitude of each sample
volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count); volk_32fc_magnitude_32f(normBuffer, (lv_32fc_t*)in, count);
// Compute the mean amplitude
float sum = 0.0f;
volk_32f_accumulator_s32f(&sum, normBuffer, count); volk_32f_accumulator_s32f(&sum, normBuffer, count);
sum /= (float)count; sum /= (float)count;
@@ -46,8 +49,6 @@ namespace dsp::noise_reduction {
return count; return count;
} }
//DEFAULT_PROC_RUN();
int run() { int run() {
int count = base_type::_in->read(); int count = base_type::_in->read();
if (count < 0) { return -1; } if (count < 0) { return -1; }

View File

@@ -10,6 +10,8 @@ namespace dsp {
Operator(stream<A>* a, stream<B>* b) { init(a, b); } Operator(stream<A>* a, stream<B>* b) { init(a, b); }
virtual ~Operator() {}
virtual void init(stream<A>* a, stream<B>* b) { virtual void init(stream<A>* a, stream<B>* b) {
_a = a; _a = a;
_b = b; _b = b;

View File

@@ -5,8 +5,6 @@ namespace dsp {
template <class T> template <class T>
class Source : public block { class Source : public block {
public: public:
Source() {}
Source() { init(); } Source() { init(); }
virtual ~Source() {} virtual ~Source() {}

View File

@@ -11,6 +11,7 @@
namespace dsp { namespace dsp {
class untyped_stream { class untyped_stream {
public: public:
virtual ~untyped_stream() {}
virtual bool swap(int size) { return false; } virtual bool swap(int size) { return false; }
virtual int read() { return -1; } virtual int read() { return -1; }
virtual void flush() {} virtual void flush() {}

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <volk/volk.h> #include <volk/volk.h>
#include "../buffer/buffer.h"
namespace dsp { namespace dsp {
template<class T> template<class T>

View File

@@ -433,6 +433,9 @@ void MainWindow::draw() {
showCredits = false; showCredits = false;
} }
// Reset waterfall lock
lockWaterfallControls = showCredits;
// Handle menu resize // Handle menu resize
ImVec2 winSize = ImGui::GetWindowSize(); ImVec2 winSize = ImGui::GetWindowSize();
ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 mousePos = ImGui::GetMousePos();
@@ -463,9 +466,10 @@ void MainWindow::draw() {
} }
} }
// Process menu keybinds
displaymenu::checkKeybinds();
// Left Column // Left Column
lockWaterfallControls = false;
if (showMenu) { if (showMenu) {
ImGui::Columns(3, "WindowColumns", false); ImGui::Columns(3, "WindowColumns", false);
ImGui::SetColumnWidth(0, menuWidth); ImGui::SetColumnWidth(0, menuWidth);
@@ -574,6 +578,8 @@ void MainWindow::draw() {
// Handle scrollwheel // Handle scrollwheel
int wheel = ImGui::GetIO().MouseWheel; int wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) { if (wheel != 0 && (gui::waterfall.mouseInFFT || gui::waterfall.mouseInWaterfall)) {
double nfreq;
if (vfo != NULL) {
// Select factor depending on modifier keys // Select factor depending on modifier keys
double interval; double interval;
if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) {
@@ -586,8 +592,6 @@ void MainWindow::draw() {
interval = vfo->snapInterval; interval = vfo->snapInterval;
} }
double nfreq;
if (vfo != NULL) {
nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel); nfreq = gui::waterfall.getCenterFrequency() + vfo->generalOffset + (interval * wheel);
nfreq = roundl(nfreq / interval) * interval; nfreq = roundl(nfreq / interval) * interval;
} }

View File

@@ -19,6 +19,7 @@ namespace displaymenu {
std::string colorMapAuthor = ""; std::string colorMapAuthor = "";
int selectedWindow = 0; int selectedWindow = 0;
int fftRate = 20; int fftRate = 20;
int fftSizeId = 0;
int uiScaleId = 0; int uiScaleId = 0;
bool restartRequired = false; bool restartRequired = false;
bool fftHold = false; bool fftHold = false;
@@ -28,34 +29,9 @@ namespace displaymenu {
bool snrSmoothing = false; bool snrSmoothing = false;
int snrSmoothingSpeed = 20; int snrSmoothingSpeed = 20;
OptionList<int, int> fftSizes;
OptionList<float, float> uiScales; OptionList<float, float> uiScales;
const int FFTSizes[] = {
524288,
262144,
131072,
65536,
32768,
16384,
8192,
4096,
2048,
1024
};
const char* FFTSizesStr = "524288\0"
"262144\0"
"131072\0"
"65536\0"
"32768\0"
"16384\0"
"8192\0"
"4096\0"
"2048\0"
"1024\0";
int fftSizeId = 0;
const IQFrontEnd::FFTWindow fftWindowList[] = { const IQFrontEnd::FFTWindow fftWindowList[] = {
IQFrontEnd::FFTWindow::RECTANGULAR, IQFrontEnd::FFTWindow::RECTANGULAR,
IQFrontEnd::FFTWindow::BLACKMAN, IQFrontEnd::FFTWindow::BLACKMAN,
@@ -69,6 +45,18 @@ namespace displaymenu {
} }
void init() { void init() {
// Define FFT sizes
fftSizes.define(524288, "524288", 524288);
fftSizes.define(262144, "262144", 262144);
fftSizes.define(131072, "131072", 131072);
fftSizes.define(65536, "65536", 65536);
fftSizes.define(32768, "32768", 32768);
fftSizes.define(16384, "16384", 16384);
fftSizes.define(8192, "8192", 8192);
fftSizes.define(4096, "4096", 4096);
fftSizes.define(2048, "2048", 2048);
fftSizes.define(1024, "1024", 1024);
showWaterfall = core::configManager.conf["showWaterfall"]; showWaterfall = core::configManager.conf["showWaterfall"];
showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall(); showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
std::string colormapName = core::configManager.conf["colorMap"]; std::string colormapName = core::configManager.conf["colorMap"];
@@ -90,15 +78,12 @@ namespace displaymenu {
fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"]; fullWaterfallUpdate = core::configManager.conf["fullWaterfallUpdate"];
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate); gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
fftSizeId = 3; fftSizeId = fftSizes.valueId(65536);
int fftSize = core::configManager.conf["fftSize"]; int size = core::configManager.conf["fftSize"];
for (int i = 0; i < 7; i++) { if (fftSizes.keyExists(size)) {
if (fftSize == FFTSizes[i]) { fftSizeId = fftSizes.keyId(size);
fftSizeId = i;
break;
} }
} sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]);
fftRate = core::configManager.conf["fftRate"]; fftRate = core::configManager.conf["fftRate"];
sigpath::iqFrontEnd.setFFTRate(fftRate); sigpath::iqFrontEnd.setFFTRate(fftRate);
@@ -127,17 +112,26 @@ namespace displaymenu {
uiScaleId = uiScales.valueId(style::uiScale); uiScaleId = uiScales.valueId(style::uiScale);
} }
void draw(void* ctx) { void setWaterfallShown(bool shown) {
float menuWidth = ImGui::GetContentRegionAvail().x; showWaterfall = shown;
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(); showWaterfall ? gui::waterfall.showWaterfall() : gui::waterfall.hideWaterfall();
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["showWaterfall"] = showWaterfall; core::configManager.conf["showWaterfall"] = showWaterfall;
core::configManager.release(true); core::configManager.release(true);
} }
void checkKeybinds() {
if (ImGui::IsKeyPressed(ImGuiKey_Home, false)) {
setWaterfallShown(!showWaterfall);
}
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvail().x;
if (ImGui::Checkbox("Show Waterfall##_sdrpp", &showWaterfall)) {
setWaterfallShown(showWaterfall);
}
if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) { if (ImGui::Checkbox("Full Waterfall Update##_sdrpp", &fullWaterfallUpdate)) {
gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate); gui::waterfall.setFullWaterfallUpdate(fullWaterfallUpdate);
core::configManager.acquire(); core::configManager.acquire();
@@ -220,10 +214,10 @@ namespace displaymenu {
ImGui::LeftLabel("FFT Size"); ImGui::LeftLabel("FFT Size");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, FFTSizesStr)) { if (ImGui::Combo("##sdrpp_fft_size", &fftSizeId, fftSizes.txt)) {
sigpath::iqFrontEnd.setFFTSize(FFTSizes[fftSizeId]); sigpath::iqFrontEnd.setFFTSize(fftSizes.value(fftSizeId));
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["fftSize"] = FFTSizes[fftSizeId]; core::configManager.conf["fftSize"] = fftSizes.key(fftSizeId);
core::configManager.release(true); core::configManager.release(true);
} }

View File

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

View File

@@ -39,7 +39,7 @@ namespace module_manager_menu {
ImVec2 btnSize = ImVec2(lheight, lheight - 1); ImVec2 btnSize = ImVec2(lheight, lheight - 1);
ImVec2 textOff = ImVec2(3.0f * style::uiScale, -5.0f * style::uiScale); 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("Name");
ImGui::TableSetupColumn("Type"); ImGui::TableSetupColumn("Type");
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, cellWidth);

View File

@@ -5,113 +5,120 @@
#include <gui/main_window.h> #include <gui/main_window.h>
#include <gui/style.h> #include <gui/style.h>
#include <signal_path/signal_path.h> #include <signal_path/signal_path.h>
#include <utils/optionlist.h>
#include <gui/dialogs/dialog_box.h>
namespace sourcemenu { namespace sourcemenu {
int offsetMode = 0;
int sourceId = 0; int sourceId = 0;
double customOffset = 0.0; EventHandler<std::string> sourcesChangedHandler;
double effectiveOffset = 0.0; EventHandler<std::string> sourceUnregisterHandler;
int decimationPower = 0; OptionList<std::string, std::string> sources;
std::string selectedSource;
int decimId = 0;
OptionList<int, int> decimations;
bool iqCorrection = false; bool iqCorrection = false;
bool invertIQ = false; bool invertIQ = false;
EventHandler<std::string> sourceRegisteredHandler; int offsetId = 0;
EventHandler<std::string> sourceUnregisterHandler; double manualOffset = 0.0;
EventHandler<std::string> sourceUnregisteredHandler; std::string selectedOffset;
double effectiveOffset = 0.0;
OptionList<std::string, double> offsets;
std::map<std::string, double> namedOffsets;
std::vector<std::string> sourceNames; bool showAddOffsetDialog = false;
std::string sourceNamesTxt; char newOffsetName[1024];
std::string selectedSource; double newOffset = 0.0;
bool showDelOffsetDialog = false;
std::string delOffsetName = "";
// Offset IDs
enum { enum {
OFFSET_MODE_NONE, OFFSET_ID_NONE,
OFFSET_MODE_CUSTOM, OFFSET_ID_MANUAL,
OFFSET_MODE_SPYVERTER, OFFSET_ID_CUSTOM_BASE
OFFSET_MODE_HAM_IT_UP,
OFFSET_MODE_MMDS_SB_1998,
OFFSET_MODE_DK5AV_XB,
OFFSET_MODE_KU_LNB_9750,
OFFSET_MODE_KU_LNB_10700,
_OFFSET_MODE_COUNT
}; };
const char* offsetModesTxt = "None\0"
"Custom\0"
"SpyVerter\0"
"Ham-It-Up\0"
"MMDS S-band (1998MHz)\0"
"DK5AV X-Band\0"
"Ku LNB (9750MHz)\0"
"Ku LNB (10700MHz)\0";
const char* decimationStages = "None\0"
"2\0"
"4\0"
"8\0"
"16\0"
"32\0"
"64\0";
void updateOffset() { void updateOffset() {
if (offsetMode == OFFSET_MODE_CUSTOM) { effectiveOffset = customOffset; } // Compute the effective offset
else if (offsetMode == OFFSET_MODE_SPYVERTER) { switch (offsetId) {
effectiveOffset = 120000000; case OFFSET_ID_NONE:
} // 120MHz Up-conversion
else if (offsetMode == OFFSET_MODE_HAM_IT_UP) {
effectiveOffset = 125000000;
} // 125MHz Up-conversion
else if (offsetMode == OFFSET_MODE_MMDS_SB_1998) {
effectiveOffset = -1998000000;
} // 1.998GHz Down-conversion
else if (offsetMode == OFFSET_MODE_DK5AV_XB) {
effectiveOffset = -6800000000;
} // 6.8GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_9750) {
effectiveOffset = -9750000000;
} // 9.750GHz Down-conversion
else if (offsetMode == OFFSET_MODE_KU_LNB_10700) {
effectiveOffset = -10700000000;
} // 10.7GHz Down-conversion
else {
effectiveOffset = 0; effectiveOffset = 0;
break;
case OFFSET_ID_MANUAL:
effectiveOffset = manualOffset;
break;
default:
effectiveOffset = namedOffsets[offsets.name(offsetId)];
break;
} }
// Apply it
sigpath::sourceManager.setTuningOffset(effectiveOffset); sigpath::sourceManager.setTuningOffset(effectiveOffset);
} }
void selectOffsetById(int id) {
// Update the offset mode
offsetId = id;
selectedOffset = offsets.name(id);
// Update the offset
updateOffset();
}
void selectOffsetByName(const std::string& name) {
// If the name doesn't exist, select 'None'
if (!offsets.nameExists(name)) {
selectOffsetById(OFFSET_ID_NONE);
return;
}
// Select using the ID associated with the name
selectOffsetById(offsets.nameId(name));
}
void refreshSources() { void refreshSources() {
sourceNames = sigpath::sourceManager.getSourceNames(); // Get sources
sourceNamesTxt.clear(); auto sourceNames = sigpath::sourceManager.getSourceNames();
// Define source options
sources.clear();
for (auto name : sourceNames) { for (auto name : sourceNames) {
sourceNamesTxt += name; sources.define(name, name, name);
sourceNamesTxt += '\0';
} }
} }
void selectSource(std::string name) { void selectSource(std::string name) {
if (sourceNames.empty()) { // If there is no source, give up
if (sources.empty()) {
sourceId = 0;
selectedSource.clear(); selectedSource.clear();
return; return;
} }
auto it = std::find(sourceNames.begin(), sourceNames.end(), name);
if (it == sourceNames.end()) { // If a source with the given name doesn't exist, select the first source instead
selectSource(sourceNames[0]); if (!sources.valueExists(name)) {
selectSource(sources.value(0));
return; return;
} }
sourceId = std::distance(sourceNames.begin(), it);
selectedSource = sourceNames[sourceId]; // Update the GUI variables
sigpath::sourceManager.selectSource(sourceNames[sourceId]); sourceId = sources.valueId(name);
selectedSource = name;
// Select the source module
sigpath::sourceManager.selectSource(name);
} }
void onSourceRegistered(std::string name, void* ctx) { void onSourcesChanged(std::string name, void* ctx) {
// Update the source list
refreshSources(); refreshSources();
if (selectedSource.empty()) { // Reselect the current source
sourceId = 0; selectSource(selectedSource);
selectSource(sourceNames[0]);
return;
}
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource));
} }
void onSourceUnregister(std::string name, void* ctx) { void onSourceUnregister(std::string name, void* ctx) {
@@ -120,60 +127,173 @@ namespace sourcemenu {
// TODO: Stop everything // TODO: Stop everything
} }
void onSourceUnregistered(std::string name, void* ctx) { void reloadOffsets() {
refreshSources(); // Clear list
offsets.clear();
namedOffsets.clear();
if (sourceNames.empty()) { // Define special offset modes
selectedSource = ""; offsets.define("None", OFFSET_ID_NONE);
return; offsets.define("Manual", OFFSET_ID_MANUAL);
// Acquire the config file
core::configManager.acquire();
// Load custom offsets
auto ofs = core::configManager.conf["offsets"].items();
for (auto& o : ofs) {
namedOffsets[o.key()] = (double)o.value();
} }
if (name == selectedSource) { // Define custom offsets
sourceId = std::clamp<int>(sourceId, 0, sourceNames.size() - 1); for (auto& [name, offset] : namedOffsets) {
selectSource(sourceNames[sourceId]); offsets.define(name, offsets.size());
return;
} }
sourceId = std::distance(sourceNames.begin(), std::find(sourceNames.begin(), sourceNames.end(), selectedSource)); // Release the config file
core::configManager.release();
} }
void init() { void init() {
// Load offset modes
reloadOffsets();
// Define decimation values
decimations.define(1, "None", 1);
decimations.define(2, "2x", 2);
decimations.define(4, "4x", 4);
decimations.define(8, "8x", 8);
decimations.define(16, "16x", 16);
decimations.define(32, "32x", 32);
decimations.define(64, "64x", 64);
// Acquire the config file
core::configManager.acquire(); core::configManager.acquire();
std::string selected = core::configManager.conf["source"];
customOffset = core::configManager.conf["offset"]; // Load other settings
offsetMode = core::configManager.conf["offsetMode"]; std::string selectedSource = core::configManager.conf["source"];
decimationPower = core::configManager.conf["decimationPower"]; manualOffset = core::configManager.conf["manualOffset"];
std::string selectedOffset = core::configManager.conf["selectedOffset"];
iqCorrection = core::configManager.conf["iqCorrection"]; iqCorrection = core::configManager.conf["iqCorrection"];
invertIQ = core::configManager.conf["invertIQ"]; invertIQ = core::configManager.conf["invertIQ"];
int decimation = core::configManager.conf["decimation"];
if (decimations.keyExists(decimation)) {
decimId = decimations.keyId(decimation);
}
// Release the config file
core::configManager.release();
// Select the source module
refreshSources();
selectSource(selectedSource);
// Update frontend settings
sigpath::iqFrontEnd.setDCBlocking(iqCorrection); sigpath::iqFrontEnd.setDCBlocking(iqCorrection);
sigpath::iqFrontEnd.setInvertIQ(invertIQ); sigpath::iqFrontEnd.setInvertIQ(invertIQ);
updateOffset(); sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
selectOffsetByName(selectedOffset);
refreshSources(); // Register handlers
selectSource(selected); sourcesChangedHandler.handler = onSourcesChanged;
sigpath::iqFrontEnd.setDecimation(1 << decimationPower);
sourceRegisteredHandler.handler = onSourceRegistered;
sourceUnregisterHandler.handler = onSourceUnregister; sourceUnregisterHandler.handler = onSourceUnregister;
sourceUnregisteredHandler.handler = onSourceUnregistered; sigpath::sourceManager.onSourceRegistered.bindHandler(&sourcesChangedHandler);
sigpath::sourceManager.onSourceRegistered.bindHandler(&sourceRegisteredHandler);
sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler); sigpath::sourceManager.onSourceUnregister.bindHandler(&sourceUnregisterHandler);
sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourceUnregisteredHandler); sigpath::sourceManager.onSourceUnregistered.bindHandler(&sourcesChangedHandler);
}
core::configManager.release(); void addOffset(const std::string& name, double offset) {
// Acquire the config file
core::configManager.acquire();
// Define a new offset
core::configManager.conf["offsets"][name] = offset;
// Acquire the config file
core::configManager.release(true);
// Reload the offsets
reloadOffsets();
// Attempt to re-select the same one
selectOffsetByName(selectedOffset);
}
void delOffset(const std::string& name) {
// Acquire the config file
core::configManager.acquire();
// Define a new offset
core::configManager.conf["offsets"].erase(name);
// Acquire the config file
core::configManager.release(true);
// Reload the offsets
reloadOffsets();
// Attempt to re-select the same one
selectOffsetByName(selectedOffset);
}
bool addOffsetDialog() {
bool open = true;
gui::mainWindow.lockWaterfallControls = true;
float menuWidth = ImGui::GetContentRegionAvail().x;
const char* id = "Add offset##sdrpp_add_offset_dialog_";
ImGui::OpenPopup(id);
if (ImGui::BeginPopup(id, ImGuiWindowFlags_NoResize)) {
ImGui::LeftLabel("Name");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::InputText("##sdrpp_add_offset_name", newOffsetName, 1023);
ImGui::LeftLabel("Offset");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
ImGui::InputDouble("##sdrpp_add_offset_offset", &newOffset);
bool nameExists = offsets.nameExists(newOffsetName);
bool reservedName = !strcmp(newOffsetName, "None") || !strcmp(newOffsetName, "Manual");
bool denyApply = !newOffsetName[0] || nameExists || reservedName;
if (nameExists) {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "An offset with the given name already exists.");
}
else if (reservedName) {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "The given name is reserved.");
}
if (denyApply) { style::beginDisabled(); }
if (ImGui::Button("Apply")) {
addOffset(newOffsetName, newOffset);
open = false;
}
if (denyApply) { style::endDisabled(); }
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
open = false;
}
ImGui::EndPopup();
}
return open;
} }
void draw(void* ctx) { void draw(void* ctx) {
float itemWidth = ImGui::GetContentRegionAvail().x; float itemWidth = ImGui::GetContentRegionAvail().x;
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float spacing = lineHeight - ImGui::GetTextLineHeight();
bool running = gui::mainWindow.sdrIsRunning(); bool running = gui::mainWindow.sdrIsRunning();
if (running) { style::beginDisabled(); } if (running) { style::beginDisabled(); }
ImGui::SetNextItemWidth(itemWidth); ImGui::SetNextItemWidth(itemWidth);
if (ImGui::Combo("##source", &sourceId, sourceNamesTxt.c_str())) { if (ImGui::Combo("##source", &sourceId, sources.txt)) {
selectSource(sourceNames[sourceId]); std::string newSource = sources.value(sourceId);
selectSource(newSource);
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["source"] = sourceNames[sourceId]; core::configManager.conf["source"] = newSource;
core::configManager.release(true); core::configManager.release(true);
} }
@@ -196,21 +316,45 @@ namespace sourcemenu {
} }
ImGui::LeftLabel("Offset mode"); ImGui::LeftLabel("Offset mode");
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX() - 2.0f*(lineHeight + 1.5f*spacing));
if (ImGui::Combo("##_sdrpp_offset_mode", &offsetMode, offsetModesTxt)) { if (ImGui::Combo("##_sdrpp_offset", &offsetId, offsets.txt)) {
updateOffset(); selectOffsetById(offsetId);
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["offsetMode"] = offsetMode; core::configManager.conf["selectedOffset"] = offsets.key(offsetId);
core::configManager.release(true); core::configManager.release(true);
} }
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::BeginDisabled(); }
if (ImGui::Button("-##_sdrpp_offset_del_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
delOffsetName = selectedOffset;
showDelOffsetDialog = true;
}
if (offsetId < OFFSET_ID_CUSTOM_BASE) { ImGui::EndDisabled(); }
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - spacing);
if (ImGui::Button("+##_sdrpp_offset_add_", ImVec2(lineHeight + 0.5f*spacing, 0))) {
strcpy(newOffsetName, "New Offset");
showAddOffsetDialog = true;
}
// Offset delete confirmation
if (ImGui::GenericDialog("sdrpp_del_offset_confirm", showDelOffsetDialog, GENERIC_DIALOG_BUTTONS_YES_NO, []() {
ImGui::Text("Deleting offset named \"%s\". Are you sure?", delOffsetName.c_str());
}) == GENERIC_DIALOG_BUTTON_YES) {
delOffset(delOffsetName);
}
// Offset add diaglog
if (showAddOffsetDialog) { showAddOffsetDialog = addOffsetDialog(); }
ImGui::LeftLabel("Offset"); ImGui::LeftLabel("Offset");
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); ImGui::FillWidth();
if (offsetMode == OFFSET_MODE_CUSTOM) { if (offsetId == OFFSET_ID_MANUAL) {
if (ImGui::InputDouble("##freq_offset", &customOffset, 1.0, 100.0)) { if (ImGui::InputDouble("##freq_offset", &manualOffset, 1.0, 100.0)) {
updateOffset(); updateOffset();
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["offset"] = customOffset; core::configManager.conf["manualOffset"] = manualOffset;
core::configManager.release(true); core::configManager.release(true);
} }
} }
@@ -222,11 +366,11 @@ namespace sourcemenu {
if (running) { style::beginDisabled(); } if (running) { style::beginDisabled(); }
ImGui::LeftLabel("Decimation"); ImGui::LeftLabel("Decimation");
ImGui::SetNextItemWidth(itemWidth - ImGui::GetCursorPosX()); ImGui::FillWidth();
if (ImGui::Combo("##source_decim", &decimationPower, decimationStages)) { if (ImGui::Combo("##source_decim", &decimId, decimations.txt)) {
sigpath::iqFrontEnd.setDecimation(1 << decimationPower); sigpath::iqFrontEnd.setDecimation(decimations.value(decimId));
core::configManager.acquire(); core::configManager.acquire();
core::configManager.conf["decimationPower"] = decimationPower; core::configManager.conf["decimation"] = decimations.key(decimId);
core::configManager.release(true); core::configManager.release(true);
} }
if (running) { style::endDisabled(); } if (running) { style::endDisabled(); }

View File

@@ -3,6 +3,7 @@
#include <gui/style.h> #include <gui/style.h>
#include <gui/gui.h> #include <gui/gui.h>
#include <backend.h> #include <backend.h>
#include <utils/hrfreq.h>
#ifndef IMGUI_DEFINE_MATH_OPERATORS #ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS
@@ -90,6 +91,7 @@ void FrequencySelect::moveCursorToDigit(int i) {
void FrequencySelect::draw() { void FrequencySelect::draw() {
auto window = ImGui::GetCurrentWindow(); auto window = ImGui::GetCurrentWindow();
auto io = ImGui::GetIO();
widgetPos = ImGui::GetWindowContentRegionMin(); widgetPos = ImGui::GetWindowContentRegionMin();
ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec2 cursorPos = ImGui::GetCursorPos();
widgetPos.x += window->Pos.x + cursorPos.x; widgetPos.x += window->Pos.x + cursorPos.x;
@@ -132,7 +134,7 @@ void FrequencySelect::draw() {
ImVec2 mousePos = ImGui::GetMousePos(); ImVec2 mousePos = ImGui::GetMousePos();
bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); bool leftClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); bool rightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
int mw = ImGui::GetIO().MouseWheel; int mw = io.MouseWheel;
bool onDigit = false; bool onDigit = false;
bool hovered = false; bool hovered = false;
@@ -174,7 +176,7 @@ void FrequencySelect::draw() {
moveCursorToDigit(i + 1); moveCursorToDigit(i + 1);
} }
auto chars = ImGui::GetIO().InputQueueCharacters; auto chars = io.InputQueueCharacters;
// For each keyboard characters, type it // For each keyboard characters, type it
for (int j = 0; j < chars.Size; j++) { for (int j = 0; j < chars.Size; j++) {
@@ -194,6 +196,34 @@ void FrequencySelect::draw() {
} }
} }
digitHovered = hovered; digitHovered = hovered;
if (isInArea(mousePos, digitTopMins[0], digitBottomMaxs[11])) {
bool shortcutKey = io.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
bool ctrlOnly = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
bool shiftOnly = (io.KeyMods == ImGuiKeyModFlags_Shift);
bool copy = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_C)) || (ctrlOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
bool paste = ((shortcutKey && ImGui::IsKeyPressed(ImGuiKey_V)) || (shiftOnly && ImGui::IsKeyPressed(ImGuiKey_Insert)));
if (copy) {
// Convert the freqency to a string
std::string freqStr = hrfreq::toString(frequency);
// Write it to the clipboard
ImGui::SetClipboardText(freqStr.c_str());
}
if (paste) {
// Attempt to parse the clipboard as a number
const char* clip = ImGui::GetClipboardText();
// If the clipboard is not empty, attempt to parse it
if (clip) {
double newFreq;
if (hrfreq::fromString(clip, newFreq)) {
setFrequency(abs(newFreq));
frequencyChanged = true;
}
}
}
}
} }
uint64_t freq = 0; uint64_t freq = 0;

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 { namespace ImGui {
WaterFall::WaterFall() { WaterFall::WaterFall() {
fftMin = -70.0; fftMin = -70.0;
@@ -586,7 +613,7 @@ namespace ImGui {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize; drawDataSize = (viewBandwidth / wholeBandwidth) * rawFFTSize;
drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); 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++) { for (int j = 0; j < dataWidth; j++) {
pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange; pixel = (std::clamp<float>(tempData[j], waterfallMin, waterfallMax) - waterfallMin) / dataRange;
waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))]; waterfallFb[(i * dataWidth) + j] = waterfallPallet[(int)(pixel * (WATERFALL_RESOLUTION - 1))];
@@ -867,7 +894,7 @@ namespace ImGui {
int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2); int drawDataStart = (((double)rawFFTSize / 2.0) * (offsetRatio + 1)) - (drawDataSize / 2);
if (waterfallVisible) { 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)); memmove(&waterfallFb[dataWidth], waterfallFb, dataWidth * (waterfallHeight - 1) * sizeof(uint32_t));
float pixel; float pixel;
float dataRange = waterfallMax - waterfallMin; float dataRange = waterfallMax - waterfallMin;
@@ -879,7 +906,7 @@ namespace ImGui {
waterfallUpdate = true; waterfallUpdate = true;
} }
else { else {
doZoom(drawDataStart, drawDataSize, dataWidth, rawFFTs, latestFFT); doZoom(drawDataStart, drawDataSize, rawFFTSize, dataWidth, rawFFTs, latestFFT);
fftLines = 1; fftLines = 1;
} }

View File

@@ -90,33 +90,6 @@ namespace ImGui {
float* getFFTBuffer(); float* getFFTBuffer();
void pushFFT(); 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 updatePallette(float colors[][3], int colorCount);
void updatePalletteFromArray(float* colors, int colorCount); void updatePalletteFromArray(float* colors, int colorCount);

View File

@@ -42,6 +42,7 @@ public:
class Instance { class Instance {
public: public:
virtual ~Instance() {}
virtual void postInit() = 0; virtual void postInit() = 0;
virtual void enable() = 0; virtual void enable() = 0;
virtual void disable() = 0; virtual void disable() = 0;

View File

@@ -230,7 +230,7 @@ namespace server {
// Compress data if needed and fill out header fields // Compress data if needed and fill out header fields
if (compression) { if (compression) {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND_COMPRESSED; 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 { else {
bb_pkt_hdr->type = PACKET_TYPE_BASEBAND; bb_pkt_hdr->type = PACKET_TYPE_BASEBAND;

View File

@@ -84,9 +84,9 @@ void SourceManager::tune(double freq) {
if (selectedHandler == NULL) { if (selectedHandler == NULL) {
return; 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); selectedHandler->tuneHandler(abs(((tuneMode == TuningMode::NORMAL) ? (freq + tuneOffset) : ifFreq)), selectedHandler->ctx);
onRetune.emit(freq); onRetune.emit(freq + tuneOffset);
currentFreq = freq; currentFreq = freq;
} }
@@ -100,7 +100,7 @@ void SourceManager::setTuningMode(TuningMode mode) {
tune(currentFreq); tune(currentFreq);
} }
void SourceManager::setPanadpterIF(double freq) { void SourceManager::setPanadapterIF(double freq) {
ifFreq = freq; ifFreq = freq;
tune(currentFreq); tune(currentFreq);
} }

View File

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

120
core/src/utils/hrfreq.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "hrfreq.h"
#include <utils/flog.h>
namespace hrfreq {
std::string toString(double freq) {
// Determine the scale
int maxDecimals = 0;
const char* suffix = "Hz";
if (freq >= 1e9) {
freq /= 1e9;
maxDecimals = 9;
suffix = "GHz";
}
else if (freq >= 1e6) {
freq /= 1e6;
maxDecimals = 6;
suffix = "MHz";
}
else if (freq >= 1e3) {
freq /= 1e3;
maxDecimals = 3;
suffix = "KHz";
}
// Convert to string (TODO: Not sure if limiting the decimals rounds)
char numBuf[128];
int numLen = sprintf(numBuf, "%0.*lf", maxDecimals, freq);
// If there is a decimal point, remove the useless zeros
if (maxDecimals) {
for (int i = numLen-1; i >= 0; i--) {
bool dot = (numBuf[i] == '.');
if (numBuf[i] != '0' && !dot) { break; }
numBuf[i] = 0;
if (dot) { break; }
}
}
// Concat the suffix
char finalBuf[128];
sprintf(finalBuf, "%s%s", numBuf, suffix);
// Return the final string
return finalBuf;
}
bool isNumeric(char c) {
return std::isdigit(c) || c == '+' || c == '-' || c == '.' || c == ',';
}
bool fromString(const std::string& str, double& freq) {
// Skip non-numeric characters
int i = 0;
char c;
for (; i < str.size(); i++) {
if (isNumeric(str[i])) { break; }
}
// Extract the numeric part
std::string numeric;
for (; i < str.size(); i++) {
// Get the character
c = str[i];
// If it's a letter, stop
if (std::isalpha(c)) { break; }
// If isn't numeric, skip it
if (!isNumeric(c)) { continue; }
// If it's a comma, skip it for now. This enforces a dot as a decimal point
if (c == ',') { continue; }
// Add the character to the numeric string
numeric += c;
}
// Attempt to parse the numeric part
double num;
try {
num = std::stod(numeric);
}
catch (const std::exception& e) {
flog::error("Failed to parse numeric part: '{}'", numeric);
return false;
}
// If no more text is available, assume the numeric part gives a frequency in Hz
if (i == str.size()) {
flog::warn("No unit given, assuming it's Hz");
freq = num;
return true;
}
// Scale the numeric value depending on the first scale character
char scale = std::toupper(str[i]);
switch (scale) {
case 'G':
num *= 1e9;
break;
case 'M':
num *= 1e6;
break;
case 'K':
num *= 1e3;
break;
case 'H':
break;
default:
flog::warn("Unknown frequency scale: '{}'", scale);
break;
}
// Return the frequency
freq = num;
return true; // TODO
}
}

19
core/src/utils/hrfreq.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <string>
namespace hrfreq {
/**
* Convert a frequency to a human-readable string.
* @param freq Frequency in Hz.
* @return Human-readable representation of the frequency.
*/
std::string toString(double freq);
/**
* Convert a human-readable representation of a frequency to a frequency value.
* @param str String containing the human-readable frequency.
* @param freq Value to write the decoded frequency to.
* @return True on success, false otherwise.
*/
bool fromString(const std::string& str, double& freq);
}

View File

@@ -86,14 +86,14 @@ namespace net {
addr.sin_port = htons(port); addr.sin_port = htons(port);
} }
std::string Address::getIPStr() { std::string Address::getIPStr() const {
char buf[128]; char buf[128];
IP_t ip = getIP(); IP_t ip = getIP();
sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); sprintf(buf, "%d.%d.%d.%d", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
return buf; return buf;
} }
IP_t Address::getIP() { IP_t Address::getIP() const {
return htonl(addr.sin_addr.s_addr); return htonl(addr.sin_addr.s_addr);
} }
@@ -101,7 +101,7 @@ namespace net {
addr.sin_addr.s_addr = htonl(ip); addr.sin_addr.s_addr = htonl(ip);
} }
int Address::getPort() { int Address::getPort() const {
return htons(addr.sin_port); return htons(addr.sin_port);
} }
@@ -138,7 +138,16 @@ namespace net {
} }
int Socket::send(const uint8_t* data, size_t len, const Address* dest) { 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) { int Socket::sendstr(const std::string& str, const Address* dest) {
@@ -160,8 +169,8 @@ namespace net {
// Set timeout // Set timeout
timeval tv; timeval tv;
tv.tv_sec = 0; tv.tv_sec = timeout / 1000;
tv.tv_usec = timeout * 1000; tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data // Wait for data
int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL); int err = select(sock+1, &set, NULL, &set, (timeout > 0) ? &tv : NULL);
@@ -225,8 +234,8 @@ namespace net {
// Define timeout // Define timeout
timeval tv; timeval tv;
tv.tv_sec = 0; tv.tv_sec = timeout / 1000;
tv.tv_usec = timeout * 1000; tv.tv_usec = (timeout - tv.tv_sec*1000) * 1000;
// Wait for data or error // Wait for data or error
if (timeout != NONBLOCKING) { if (timeout != NONBLOCKING) {
@@ -375,13 +384,25 @@ namespace net {
return connect(Address(host, port)); 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 library if needed
init(); init();
// Create socket // Create socket
SockHandle_t s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 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 // Bind socket to local port
if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) { if (bind(s, (sockaddr*)&laddr.addr, sizeof(sockaddr_in))) {
closeSocket(s); closeSocket(s);
@@ -393,15 +414,15 @@ namespace net {
return std::make_shared<Socket>(s, &raddr); return std::make_shared<Socket>(s, &raddr);
} }
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) {
return openudp(Address(rhost, rport), laddr); return openudp(Address(rhost, rport), laddr, allowBroadcast);
} }
std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport) { std::shared_ptr<Socket> openudp(const Address& raddr, std::string lhost, int lport, bool allowBroadcast) {
return openudp(raddr, Address(lhost, lport)); return openudp(raddr, Address(lhost, lport), allowBroadcast);
} }
std::shared_ptr<Socket> openudp(std::string rhost, int rport, std::string lhost, int 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)); return openudp(Address(rhost, rport), Address(lhost, lport), allowBroadcast);
} }
} }

View File

@@ -67,13 +67,13 @@ namespace net {
* Get the IP address. * Get the IP address.
* @return IP address in standard string format. * @return IP address in standard string format.
*/ */
std::string getIPStr(); std::string getIPStr() const;
/** /**
* Get the IP address. * Get the IP address.
* @return IP address in host byte order. * @return IP address in host byte order.
*/ */
IP_t getIP(); IP_t getIP() const;
/** /**
* Set the IP address. * Set the IP address.
@@ -85,7 +85,7 @@ namespace net {
* Get the TCP/UDP port. * Get the TCP/UDP port.
* @return TCP/UDP port number. * @return TCP/UDP port number.
*/ */
int getPort(); int getPort() const;
/** /**
* Set the TCP/UDP port. * Set the TCP/UDP port.
@@ -246,37 +246,37 @@ namespace net {
/** /**
* Create UDP socket. * 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. * @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise. * @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. * 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 rport Remote port.
* @param laddr Local address to bind the socket to. * @param laddr Local address to bind the socket to.
* @return Socket instance on success, Throws runtime_error otherwise. * @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. * 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 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). * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise. * @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. * 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 rport Remote port.
* @param lhost Local hostname or IP used to bind the socket (optional, "0.0.0.0" for Any). * @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). * @param lpost Local port used to bind the socket to (optional, 0 to allocate automatically).
* @return Socket instance on success, Throws runtime_error otherwise. * @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); entry.handler(std::move(client), entry.ctx);
} }
catch (std::exception e) { catch (const std::exception& e) {
listening = false; listening = false;
return; return;
} }

View File

@@ -257,6 +257,7 @@ namespace net::http {
// Deserialize // Deserialize
req.deserialize(respData); req.deserialize(respData);
return 0; // Might wanna return size instead
} }
int Client::sendResponseHeader(ResponseHeader& resp) { int Client::sendResponseHeader(ResponseHeader& resp) {
@@ -274,6 +275,7 @@ namespace net::http {
// Deserialize // Deserialize
resp.deserialize(respData); resp.deserialize(respData);
return 0; // Might wanna return size instead
} }
int Client::sendChunkHeader(ChunkHeader& chdr) { int Client::sendChunkHeader(ChunkHeader& chdr) {

View File

@@ -7,6 +7,14 @@ namespace riff {
const char* LIST_SIGNATURE = "LIST"; const char* LIST_SIGNATURE = "LIST";
const size_t RIFF_LABEL_SIZE = 4; 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]) { bool Writer::open(std::string path, const char form[4]) {
std::lock_guard<std::recursive_mutex> lck(mtx); std::lock_guard<std::recursive_mutex> lck(mtx);

View File

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

@@ -1,3 +1,3 @@
#pragma once #pragma once
#define VERSION_STR "1.1.0" #define VERSION_STR "1.3.0"

View File

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

View File

@@ -0,0 +1,176 @@
617 + 6 -> I
304 + 6 -> II
616 + 6 -> III
305 + 6 -> IV
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624
305
306
307
308
309
310
311
312
313
617
618
619
620
621
622
623
624
0
304
305
306
307
308
309
310
311
312
616
617
618
619
620
621
622
623
624

View File

@@ -0,0 +1,131 @@
#pragma once
#include <dsp/types.h>
const dsp::complex_t CHROMA_BANDPASS[123] = {
{ -0.000007675039564594f, -0.000017362992335168f },
{ 0.000050180791439308f, -0.000005054021864311f },
{ -0.000022529111707761f, 0.000102942513429095f },
{ -0.000157609487484146f, -0.000092618697641464f },
{ 0.000205649042029007f, -0.000181710515677257f },
{ 0.000143445458895462f, 0.000331994546004200f },
{ -0.000414693079508517f, 0.000038265188132615f },
{ 0.000090081630021837f, -0.000395731646002122f },
{ 0.000257705918065856f, 0.000154354504676150f },
{ -0.000064051192147575f, 0.000055648228186439f },
{ 0.000089938060647145f, 0.000213032074676941f },
{ -0.000604775098099200f, 0.000050706635726124f },
{ 0.000223309865890358f, -0.000944433958755193f },
{ 0.001049943574694384f, 0.000640863688898729f },
{ -0.000983491651119595f, 0.000840133365053179f },
{ -0.000417178588714773f, -0.001011686459999295f },
{ 0.000616677332283103f, -0.000046513429902547f },
{ 0.000018549463752019f, -0.000075619948809012f },
{ 0.000734408386201158f, 0.000456742966201638f },
{ -0.001192460562555901f, 0.001001510577200253f },
{ -0.000729137747758392f, -0.001811046261815935f },
{ 0.001878272869910273f, -0.000125879189667096f },
{ -0.000312873903977849f, 0.001230889889574772f },
{ -0.000142534831707354f, -0.000090307321579771f },
{ -0.000942796972567241f, 0.000778470227412111f },
{ -0.000945381510920278f, -0.002406055808135091f },
{ 0.003537159230775561f, -0.000207350791625892f },
{ -0.000956199555190230f, 0.003634225577771235f },
{ -0.002543835202533561f, -0.001641705037372486f },
{ 0.001064108471592447f, -0.000863770138941644f },
{ -0.000335799601479829f, -0.000876091753216939f },
{ 0.003390761989356699f, -0.000170321604912419f },
{ -0.001408130728751909f, 0.005175554625981795f },
{ -0.005203055300834108f, -0.003419861284250694f },
{ 0.004342719678657084f, -0.003465264906764298f },
{ 0.001143432997855297f, 0.003059520699490539f },
{ 0.000304096484476364f, -0.000012725974706621f },
{ -0.001193870642975282f, 0.004247469277548632f },
{ -0.006681021498855877f, -0.004471771356204969f },
{ 0.007965721969864534f, -0.006247895626072559f },
{ 0.003365883969059717f, 0.009241201835481184f },
{ -0.006835562188141396f, 0.000228798228738161f },
{ 0.000409900284971528f, -0.001412838961851673f },
{ -0.004331406608345981f, -0.002951876085350234f },
{ 0.009290089917766562f, -0.007161958719089258f },
{ 0.005418326020709935f, 0.015272361365960607f },
{ -0.017077565432843410f, 0.000428641984774326f },
{ 0.003850771342644978f, -0.012869517593577566f },
{ 0.004380859690202961f, 0.003039552423897447f },
{ 0.004761181766399753f, -0.003607421240356480f },
{ 0.005926935731028822f, 0.017160134858844222f },
{ -0.028153584885925551f, 0.000471042980325370f },
{ 0.009655944938035437f, -0.031314555422639050f },
{ 0.023930146568136038f, 0.016901617811072800f },
{ -0.012998853255109976f, 0.009678807314399702f },
{ 0.002043176559434885f, 0.006079907699564680f },
{ -0.036686455817128191f, 0.000306882557812233f },
{ 0.021529138474771701f, -0.067800343150283604f },
{ 0.085421344938160879f, 0.061409588050754214f },
{ -0.108166660998898100f, 0.079141989828113088f },
{ -0.047617308971534079f, -0.145721049254261960f },
{ 0.160079041453427080f, -0.000000000000000427f },
{ -0.047617308971533295f, 0.145721049254262240f },
{ -0.108166660998898530f, -0.079141989828112505f },
{ 0.085421344938160546f, -0.061409588050754672f },
{ 0.021529138474772065f, 0.067800343150283493f },
{ -0.036686455817128191f, -0.000306882557812037f },
{ 0.002043176559434853f, -0.006079907699564691f },
{ -0.012998853255110026f, -0.009678807314399631f },
{ 0.023930146568135951f, -0.016901617811072928f },
{ 0.009655944938035604f, 0.031314555422638994f },
{ -0.028153584885925554f, -0.000471042980325220f },
{ 0.005926935731028730f, -0.017160134858844253f },
{ 0.004761181766399772f, 0.003607421240356455f },
{ 0.004380859690202943f, -0.003039552423897470f },
{ 0.003850771342645046f, 0.012869517593577545f },
{ -0.017077565432843413f, -0.000428641984774235f },
{ 0.005418326020709854f, -0.015272361365960637f },
{ 0.009290089917766600f, 0.007161958719089209f },
{ -0.004331406608345964f, 0.002951876085350257f },
{ 0.000409900284971536f, 0.001412838961851670f },
{ -0.006835562188141398f, -0.000228798228738125f },
{ 0.003365883969059667f, -0.009241201835481201f },
{ 0.007965721969864567f, 0.006247895626072517f },
{ -0.006681021498855855f, 0.004471771356205005f },
{ -0.001193870642975304f, -0.004247469277548626f },
{ 0.000304096484476364f, 0.000012725974706619f },
{ 0.001143432997855281f, -0.003059520699490545f },
{ 0.004342719678657102f, 0.003465264906764274f },
{ -0.005203055300834089f, 0.003419861284250722f },
{ -0.001408130728751936f, -0.005175554625981787f },
{ 0.003390761989356700f, 0.000170321604912401f },
{ -0.000335799601479825f, 0.000876091753216940f },
{ 0.001064108471592452f, 0.000863770138941638f },
{ -0.002543835202533552f, 0.001641705037372499f },
{ -0.000956199555190250f, -0.003634225577771230f },
{ 0.003537159230775563f, 0.000207350791625874f },
{ -0.000945381510920265f, 0.002406055808135096f },
{ -0.000942796972567245f, -0.000778470227412106f },
{ -0.000142534831707354f, 0.000090307321579771f },
{ -0.000312873903977856f, -0.001230889889574770f },
{ 0.001878272869910274f, 0.000125879189667086f },
{ -0.000729137747758382f, 0.001811046261815939f },
{ -0.001192460562555906f, -0.001001510577200246f },
{ 0.000734408386201156f, -0.000456742966201642f },
{ 0.000018549463752019f, 0.000075619948809012f },
{ 0.000616677332283103f, 0.000046513429902543f },
{ -0.000417178588714767f, 0.001011686459999298f },
{ -0.000983491651119600f, -0.000840133365053174f },
{ 0.001049943574694380f, -0.000640863688898734f },
{ 0.000223309865890363f, 0.000944433958755192f },
{ -0.000604775098099200f, -0.000050706635726121f },
{ 0.000089938060647144f, -0.000213032074676941f },
{ -0.000064051192147576f, -0.000055648228186438f },
{ 0.000257705918065856f, -0.000154354504676151f },
{ 0.000090081630021839f, 0.000395731646002121f },
{ -0.000414693079508517f, -0.000038265188132613f },
{ 0.000143445458895461f, -0.000331994546004200f },
{ 0.000205649042029008f, 0.000181710515677256f },
{ -0.000157609487484145f, 0.000092618697641465f },
{ -0.000022529111707761f, -0.000102942513429094f },
{ 0.000050180791439308f, 0.000005054021864311f },
{ -0.000007675039564594f, 0.000017362992335168f }
};
#define CHROMA_BANDPASS_SIZE (sizeof(CHROMA_BANDPASS)/sizeof(dsp::complex_t))
#define CHROMA_BANDPASS_DELAY (CHROMA_BANDPASS_SIZE/2)

View File

@@ -0,0 +1,255 @@
#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>
#define LINE_SIZE 945
#define SYNC_LEN 70
#define SYNC_SIDE_LEN 17
#define SYNC_L_START (LINE_SIZE - SYNC_SIDE_LEN)
#define SYNC_R_START (SYNC_LEN/2)
#define SYNC_R_END (SYNC_R_START + (SYNC_LEN/2) + SYNC_SIDE_LEN)
#define SYNC_HALF_LEN ((SYNC_LEN/2) + SYNC_SIDE_LEN)
#define EQUAL_LEN 35
#define HBLANK_START SYNC_LEN
#define HBLANK_END 155
#define HBLANK_LEN (HBLANK_END - HBLANK_START + 1)
#define SYNC_LEVEL (-0.428)
#define COLORBURST_START 84
#define COLORBURST_LEN 33
#define MAX_LOCK 1000
dsp::complex_t PHASE_REF[2] = {
{ -0.707106781186547f, 0.707106781186547f },
{ -0.707106781186547f, -0.707106781186547f }
};
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;
generateInterpTaps();
buffer = dsp::buffer::alloc<float>(STREAM_BUFFER_SIZE + _interpTapCount);
bufStart = &buffer[_interpTapCount - 1];
// TODO: Needs tuning, so do the gains
maxPeriod = (int32_t)(1.0001 * (float)(1 << 30));
minPeriod = (int32_t)(0.9999 * (float)(1 << 30));
base_type::init(in);
}
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;
phase = 0;
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));
// Process samples while they are available
while (offset < count) {
// While the offset is negative, out put zeros
while (offset < 0 && pixel < LINE_SIZE) {
// Output a zero
base_type::out.writeBuf[pixel++] = 0.0f;
// Increment the phase
phase += period;
offset += (phase >> 30);
phase &= 0x3FFFFFFF;
}
// Process as much of a line as possible
while (offset < count && pixel < LINE_SIZE) {
// Compute the output sample
volk_32f_x2_dot_prod_32f(&base_type::out.writeBuf[pixel++], &buffer[offset], interpBank.phases[(phase >> 23) & 0x7F], _interpTapCount);
// Increment the phase
phase += period;
offset += (phase >> 30);
phase &= 0x3FFFFFFF;
}
// If the line is done, process it
if (pixel == LINE_SIZE) {
// Compute averages. (TODO: Try faster method)
float left = 0.0f, right = 0.0f;
int lc = 0, rc = 0;
for (int i = SYNC_L_START; i < LINE_SIZE; i++) {
left += base_type::out.writeBuf[i];
lc++;
}
for (int i = 0; i < SYNC_R_START; i++) {
left += base_type::out.writeBuf[i];
lc++;
}
for (int i = SYNC_R_START; i < SYNC_R_END; i++) {
right += base_type::out.writeBuf[i];
rc++;
}
// Compute the error
float error = (left - right) * (1.0f/((float)SYNC_HALF_LEN));
// Compute the change in phase and frequency due to the error
float periodDelta = error * _omegaGain;
float phaseDelta = error * _muGain;
// Normalize the phase delta (TODO: Make faster)
while (phaseDelta <= -1.0f) {
phaseDelta += 1.0f;
offset--;
}
while (phaseDelta >= 1.0f) {
phaseDelta -= 1.0f;
offset++;
}
// Update the period (TODO: Clamp error*omegaGain to prevent weird shit with corrupt samples)
period += (int32_t)(periodDelta * (float)(1 << 30));
period = std::clamp<uint32_t>(period, minPeriod, maxPeriod);
// Update the phase
phase += (int32_t)(phaseDelta * (float)(1 << 30));
// Normalize the phase
uint32_t overflow = phase >> 30;
if (overflow) {
if (error < 0) {
offset -= 4 - overflow;
}
else {
offset += overflow;
}
}
phase &= 0x3FFFFFFF;
// Find the lowest value
float lowest = INFINITY;
int lowestId = -1;
for (int i = 0; i < LINE_SIZE; i++) {
float val = base_type::out.writeBuf[i];
if (val < lowest) {
lowest = val;
lowestId = i;
}
}
// Check the the line is in lock
bool lineLocked = (lowestId < SYNC_R_END || lowestId >= SYNC_L_START);
// Update the lock status based on the line lock
if (!lineLocked && locked) {
locked--;
}
else if (lineLocked && locked < MAX_LOCK) {
locked++;
}
// If not locked, attempt to lock by forcing the sync to happen at the right spot
// TODO: This triggers waaaay too easily at low SNR
if (!locked && fastLock) {
offset += lowestId - SYNC_R_START;
locked = MAX_LOCK / 2;
}
// Output line
if (!base_type::out.swap(LINE_SIZE)) { break; }
pixel = 0;
}
}
// Get the offset ready for the next buffer
offset -= count;
// Update delay buffer
memmove(buffer, &buffer[count], (_interpTapCount - 1) * sizeof(float));
// Swap if some data was generated
base_type::_in->flush();
return 0;
}
float syncBias = 0;
uint32_t period = (0x800072F3 >> 1);//(1 << 31) + 1;
int locked = 0;
bool fastLock = true;
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;
double _omega;
double _omegaGain;
double _muGain;
double _omegaRelLimit;
int _interpPhaseCount;
int _interpTapCount;
float* buffer;
float* bufStart;
uint32_t phase = 0;
uint32_t maxPeriod;
uint32_t minPeriod;
float syncLevel = -0.03f;
int offset = 0;
int pixel = 0;
};

View File

@@ -10,6 +10,19 @@
#include <dsp/demod/quadrature.h> #include <dsp/demod/quadrature.h>
#include <dsp/sink/handler_sink.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 "amplitude.h"
#include <dsp/demod/am.h>
#include <dsp/loop/fast_agc.h>
#include "filters.h"
#include <dsp/math/normalize_phase.h>
#include <fstream>
#define CONCAT(a, b) ((std::string(a) + b).c_str()) #define CONCAT(a, b) ((std::string(a) + b).c_str())
@@ -17,21 +30,30 @@ SDRPP_MOD_INFO{/* Name: */ "atv_decoder",
/* Description: */ "ATV decoder for SDR++", /* Description: */ "ATV decoder for SDR++",
/* Author: */ "Ryzerth", /* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0, /* Version: */ 0, 1, 0,
/* Max instances */ -1}; /* Max instances */ -1
};
#define SAMPLE_RATE (625.0f * 720.0f * 25.0f) #define SAMPLE_RATE (625.0f * (float)LINE_SIZE * 25.0f)
class ATVDecoderModule : public ModuleManager::Instance { class ATVDecoderModule : public ModuleManager::Instance {
public: public:
ATVDecoderModule(std::string name) : img(720, 625) { ATVDecoderModule(std::string name) : img(768, 576) {
this->name = name; this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 8000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true); vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 7000000.0f, SAMPLE_RATE, SAMPLE_RATE, SAMPLE_RATE, true);
demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f); agc.init(vfo->output, 1.0f, 1e6, 0.001f, 1.0f);
sink.init(&demod.out, handler, this); demod.init(&agc.out, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
// demod.init(vfo->output, dsp::demod::AM<float>::CARRIER, 8000000.0f, 50.0 / SAMPLE_RATE, 50.0 / SAMPLE_RATE, 0.0f, SAMPLE_RATE);
// demod.init(vfo->output, SAMPLE_RATE, SAMPLE_RATE / 2.0f);
sync.init(&demod.out, 1.0f, 1e-6, 1.0, 0.05);
sink.init(&sync.out, handler, this);
r2c.init(NULL);
agc.start();
demod.start(); demod.start();
sync.start();
sink.start(); sink.start();
gui::menu.registerEntry(name, menuHandler, this, this); gui::menu.registerEntry(name, menuHandler, this, this);
@@ -41,15 +63,22 @@ class ATVDecoderModule : public ModuleManager::Instance {
if (vfo) { if (vfo) {
sigpath::vfoManager.deleteVFO(vfo); sigpath::vfoManager.deleteVFO(vfo);
} }
agc.stop();
demod.stop(); demod.stop();
sync.stop();
sink.stop();
gui::menu.removeEntry(name); gui::menu.removeEntry(name);
} }
void postInit() {} void postInit() {}
void enable() { enabled = true; } void enable() {
enabled = true;
}
void disable() { enabled = false; } void disable() {
enabled = false;
}
bool isEnabled() { return enabled; } bool isEnabled() { return enabled; }
@@ -64,110 +93,215 @@ class ATVDecoderModule : public ModuleManager::Instance {
ImGui::FillWidth(); ImGui::FillWidth();
_this->img.draw(); _this->img.draw();
ImGui::LeftLabel("Sync"); ImGui::TextUnformatted("Horizontal Sync:");
ImGui::FillWidth(); ImGui::SameLine();
ImGui::SliderFloat("##syncLvl", &_this->sync_level, -2, 2); if (_this->sync.locked > 750) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::LeftLabel("Min"); ImGui::TextUnformatted("Vertical Sync:");
ImGui::FillWidth(); ImGui::SameLine();
ImGui::SliderFloat("##minLvl", &_this->minLvl, -1.0, 1.0); if (_this->vlock > 15) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Locked");
}
else {
ImGui::TextUnformatted("Not locked");
}
ImGui::LeftLabel("Span"); ImGui::Checkbox("Fast Lock", &_this->sync.fastLock);
ImGui::FillWidth(); ImGui::Checkbox("Color Mode", &_this->colorMode);
ImGui::SliderFloat("##spanLvl", &_this->spanLvl, 0, 1.0);
if (!_this->enabled) { if (!_this->enabled) {
style::endDisabled(); style::endDisabled();
} }
ImGui::Text("Gain: %f", _this->gain);
ImGui::Text("Offset: %f", _this->offset);
ImGui::Text("Subcarrier: %f", _this->subcarrierFreq);
} }
uint32_t pp = 0;
static void handler(float *data, int count, void *ctx) { static void handler(float *data, int count, void *ctx) {
ATVDecoderModule *_this = (ATVDecoderModule *)ctx; ATVDecoderModule *_this = (ATVDecoderModule *)ctx;
uint8_t *buf = (uint8_t *)_this->img.buffer; // Correct the offset
float val; #if VOLK_VERSION_MAJOR > 2 || (VOLK_VERSION_MAJOR == 2 && VOLK_VERSION_MINOR >= 3)
float imval; volk_32f_s32f_add_32f(data, data, _this->offset, count);
int pos = 0; #else
const float ofs = _this->offset;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
val = data[i]; data[i] += ofs;
// Sync }
if (val < _this->sync_level) { #endif
_this->sync_count++;
// Correct the gain
volk_32f_s32f_multiply_32f(data, data, _this->gain, count);
// Compute the sync levels
float syncLLevel = 0.0f;
float syncRLevel = 0.0f;
volk_32f_accumulator_s32f(&syncLLevel, data, EQUAL_LEN);
volk_32f_accumulator_s32f(&syncRLevel, &data[EQUAL_LEN], SYNC_LEN - EQUAL_LEN);
syncLLevel *= 1.0f / EQUAL_LEN;
syncRLevel *= 1.0f / (SYNC_LEN - EQUAL_LEN);
float syncLevel = (syncLLevel + syncRLevel) * 0.5f; // TODO: It's technically correct but if the sizes were different it wouldn't be
// Compute the blanking level
float blankLevel = 0.0f;
volk_32f_accumulator_s32f(&blankLevel, &data[HBLANK_START], HBLANK_LEN);
blankLevel /= (float)HBLANK_LEN;
// Run the offset control loop
_this->offset -= (blankLevel / _this->gain)*0.001;
_this->offset = std::clamp<float>(_this->offset, -1.0f, 1.0f);
_this->gain -= (blankLevel - syncLevel + SYNC_LEVEL)*0.01f;
_this->gain = std::clamp<float>(_this->gain, 0.1f, 10.0f);
// Detect the sync type
uint16_t shortSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel > 0.5f*SYNC_LEVEL) && (blankLevel > 0.5f*SYNC_LEVEL);
uint16_t longSync = (syncLLevel < 0.5f*SYNC_LEVEL) && (syncRLevel < 0.5f*SYNC_LEVEL) && (blankLevel < 0.5f*SYNC_LEVEL);
// Save sync type to history
_this->syncHistory = (_this->syncHistory << 2) | (longSync << 1) | shortSync;
// // If the line has a colorburst, decode it
// dsp::complex_t* buf1 = _this->r2c.out.readBuf;
// dsp::complex_t* buf2 = _this->r2c.out.writeBuf;
// if (true) {
// // Convert the line into complex
// _this->r2c.process(count, data, buf1);
// // Extract the chroma subcarrier (TODO: Optimise by running only where needed)
// for (int i = COLORBURST_START; i < count-(CHROMA_BANDPASS_DELAY+1); i++) {
// volk_32fc_x2_dot_prod_32fc((lv_32fc_t*)&buf2[i], (lv_32fc_t*)&buf1[i - CHROMA_BANDPASS_DELAY], (lv_32fc_t*)CHROMA_BANDPASS, CHROMA_BANDPASS_SIZE);
// }
// // Down convert the chroma subcarrier (TODO: Optimise by running only where needed)
// lv_32fc_t startPhase = { 1.0f, 0.0f };
// lv_32fc_t phaseDelta = { sinf(_this->subcarrierFreq), cosf(_this->subcarrierFreq) };
// #if VOLK_VERSION >= 030100
// volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], &phaseDelta, &startPhase, count - COLORBURST_START);
// #else
// volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], phaseDelta, &startPhase, count - COLORBURST_START);
// #endif
// // Compute the phase of the burst
// dsp::complex_t burstAvg = { 0.0f, 0.0f };
// volk_32fc_accumulator_s32fc((lv_32fc_t*)&burstAvg, (lv_32fc_t*)&buf2[COLORBURST_START], COLORBURST_LEN);
// float burstAmp = burstAvg.amplitude();
// if (burstAmp*(1.0f/(float)COLORBURST_LEN) < 0.02f) {
// printf("%d\n", _this->line);
// }
// burstAvg *= (1.0f / (burstAmp*burstAmp));
// burstAvg = burstAvg.conj();
// // Normalize the chroma data (TODO: Optimise by running only where needed)
// volk_32fc_s32fc_multiply_32fc((lv_32fc_t*)&buf2[COLORBURST_START], (lv_32fc_t*)&buf2[COLORBURST_START], *((lv_32fc_t*)&burstAvg), count - COLORBURST_START);
// // Compute the frequency error of the burst
// float phase = buf2[COLORBURST_START].phase();
// float error = 0.0f;
// for (int i = COLORBURST_START+1; i < COLORBURST_START+COLORBURST_LEN; i++) {
// float cphase = buf2[i].phase();
// error += dsp::math::normalizePhase(cphase - phase);
// phase = cphase;
// }
// error *= (1.0f / (float)(COLORBURST_LEN-1));
// // Update the subcarrier freq
// _this->subcarrierFreq += error*0.0001f;
// }
// Render the line if it's visible
if (_this->ypos >= 34 && _this->ypos <= 34+576-1) {
uint32_t* currentLine = &((uint32_t *)_this->img.buffer)[(_this->ypos - 34)*768];
if (_this->colorMode) {
// for (int i = 155; i < (155+768); i++) {
// int imval1 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].re*5.0f) * 255.0f, 0, 255);
// int imval2 = std::clamp<float>(fabsf(buf2[i-155+COLORBURST_START].im*5.0f) * 255.0f, 0, 255);
// currentLine[i-155] = 0xFF000000 | (imval2 << 8) | imval1;
// }
} }
else { else {
if (_this->sync_count >= 300) { for (int i = 155; i < (155+768); i++) {
_this->short_sync = 0; int imval = std::clamp<float>(data[i] * 255.0f, 0, 255);
currentLine[i-155] = 0xFF000000 | (imval << 16) | (imval << 8) | imval;
} }
else if (_this->sync_count >= 33) { }
if (_this->short_sync == 5) { }
_this->even_field = false;
// Compute whether to rollover
bool rollToOdd = (_this->ypos == 624);
bool rollToEven = (_this->ypos == 623);
// Compute the field sync
bool syncToOdd = (_this->syncHistory == 0b0101011010010101);
bool syncToEven = (_this->syncHistory == 0b0001011010100101);
// Process the sync (NOTE: should start with 0b01, but for some reason I don't see a sync?)
if (rollToOdd || syncToOdd) {
// Update the vertical lock state
bool disagree = (rollToOdd ^ syncToOdd);
if (disagree && _this->vlock > 0) {
_this->vlock--;
}
else if (!disagree && _this->vlock < 20) {
_this->vlock++;
}
// Start the odd field
_this->ypos = 1;
_this->line++;
}
else if (rollToEven || syncToEven) {
// Update the vertical lock state
bool disagree = (rollToEven ^ syncToEven);
if (disagree && _this->vlock > 0) {
_this->vlock--;
}
else if (!disagree && _this->vlock < 20) {
_this->vlock++;
}
// Start the even field
_this->ypos = 0; _this->ypos = 0;
_this->line = 0;
// Swap the video buffer
_this->img.swap(); _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;
}
// 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 { else {
pos = ((720 * (_this->ypos * 2 + 1)) + _this->xpos) * 4; _this->ypos += 2;
_this->line++;
}
} }
buf[pos] = imval; // NEW SYNC:
buf[pos + 1] = imval; float offset = 0.0f;
buf[pos + 2] = imval; float gain = 1.0f;
buf[pos + 3] = imval; uint16_t syncHistory = 0;
int line = 0;
// Image logic int ypos = 0;
_this->xpos++; int vlock = 0;
if (_this->xpos >= 720) { float subcarrierFreq = 0.0f;
_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;
}
}
}
}
std::string name; std::string name;
bool enabled = true; bool enabled = true;
VFOManager::VFO *vfo = NULL; VFOManager::VFO *vfo = NULL;
dsp::demod::Quadrature demod; // dsp::demod::Quadrature demod;
dsp::loop::FastAGC<dsp::complex_t> agc;
dsp::demod::Amplitude demod;
//dsp::demod::AM<float> demod;
LineSync sync;
dsp::sink::Handler<float> sink; dsp::sink::Handler<float> sink;
dsp::convert::RealToComplex r2c;
int xpos = 0; bool colorMode = false;
int ypos = 0;
bool even_field = false;
float sync_level = -0.3f;
int sync_count = 0;
int short_sync = 0;
float minLvl = 0.0f;
float spanLvl = 1.0f;
ImGui::ImageDisplay img; ImGui::ImageDisplay img;
}; };

View File

@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.13)
project(dab_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(dab_decoder PRIVATE "src/")
if (MSVC)
# Lib path
target_include_directories(dab_decoder PRIVATE "C:/Program Files/codec2/include/")
target_link_directories(dab_decoder PRIVATE "C:/Program Files/codec2/lib")
target_link_libraries(dab_decoder PRIVATE libcodec2)
elseif (ANDROID)
target_include_directories(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/include/codec2
)
target_link_libraries(dab_decoder PUBLIC
/sdr-kit/${ANDROID_ABI}/lib/libcodec2.so
)
else ()
find_package(PkgConfig)
pkg_check_modules(LIBCODEC2 REQUIRED codec2)
target_include_directories(dab_decoder PRIVATE ${LIBCODEC2_INCLUDE_DIRS})
target_link_directories(dab_decoder PRIVATE ${LIBCODEC2_LIBRARY_DIRS})
target_link_libraries(dab_decoder PRIVATE ${LIBCODEC2_LIBRARIES})
# Include it because for some reason pkgconfig doesn't look here?
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
target_include_directories(dab_decoder PRIVATE "/usr/local/include")
endif()
endif ()

View File

@@ -0,0 +1,280 @@
#pragma once
#include <dsp/processor.h>
#include <utils/flog.h>
#include <fftw3.h>
#include "dab_phase_sym.h"
namespace dab {
class CyclicSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
CyclicSync() {}
// TODO: The default AGC rate is probably way too fast, plot out the avgCorr to see how much it moves
CyclicSync(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) { init(in, symbolLength, cyclicPrefixLength, samplerate, agcRate); }
void init(dsp::stream<dsp::complex_t>* in, double symbolLength, double cyclicPrefixLength, double samplerate, float agcRate = 1e-3) {
// Computer the number of samples for the symbol and its cyclic prefix
symbolSamps = round(samplerate * symbolLength);
prefixSamps = round(samplerate * cyclicPrefixLength);
// Allocate and clear the delay buffer
delayBuf = dsp::buffer::alloc<dsp::complex_t>(STREAM_BUFFER_SIZE + 64000);
dsp::buffer::clear(delayBuf, symbolSamps);
// Allocate and clear the history buffer
histBuf = dsp::buffer::alloc<dsp::complex_t>(prefixSamps);
dsp::buffer::clear(histBuf, prefixSamps);
// Compute the delay input addresses
delayBufInput = &delayBuf[symbolSamps];
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
void reset() {
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; }
// Copy the data into the normal delay buffer
memcpy(delayBufInput, base_type::_in->readBuf, count * sizeof(dsp::complex_t));
// Flush the input stream
base_type::_in->flush();
// Do cross-correlation
for (int i = 0; i < count; i++) {
// Get the current history slot
dsp::complex_t* slot = &histBuf[histId++];
// Wrap around the history slot index (TODO: Check that the history buffer's length is correct)
histId %= prefixSamps;
// Kick out last value from the correlation
corr -= *slot;
// Save input value and compute the new prodct
dsp::complex_t val = delayBuf[i];
dsp::complex_t prod = val.conj()*delayBuf[i+symbolSamps];
// Add the new value to the correlation
*slot = prod;
// Add the new value to the history buffer
corr += prod;
// Compute sample amplitude
float rcorr = corr.amplitude();
// If a high enough peak is reached, reset the symbol counter
if (rcorr > avgCorr && rcorr > peakCorr) { // Note keeping an average level might not be needed
peakCorr = rcorr;
peakLCorr = lastCorr;
samplesSincePeak = 0;
}
// If this is the sample right after the peak, save it
if (samplesSincePeak == 1) {
peakRCorr = rcorr;
}
// Write the sample to the output
out.writeBuf[samplesSincePeak++] = val;
// If the end of the symbol is reached, send it off
if (samplesSincePeak >= symbolSamps) {
if (!out.swap(symbolSamps)) {
return -1;
}
samplesSincePeak = 0;
peakCorr = 0;
}
// Update the average correlation
lastCorr = rcorr;
// Update the average correlation value
avgCorr = agcRate*rcorr + agcRateInv*avgCorr;
}
// Move unused data
memmove(delayBuf, &delayBuf[count], symbolSamps * sizeof(dsp::complex_t));
return count;
}
protected:
int symbolSamps;
int prefixSamps;
int histId = 0;
dsp::complex_t* histBuf;
dsp::complex_t* delayBuf;
dsp::complex_t* delayBufInput;
dsp::complex_t corr = { 0.0f, 0.0f };
int samplesSincePeak = 0;
float lastCorr = 0.0f;
float peakCorr = 0.0f;
float peakLCorr = 0.0f;
float peakRCorr = 0.0f;
// Note only required for DAB
float avgCorr = 0.0f;
float agcRate;
float agcRateInv;
};
class FrameFreqSync : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
FrameFreqSync() {}
FrameFreqSync(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) { init(in, agcRate); }
void init(dsp::stream<dsp::complex_t>* in, float agcRate = 0.01f) {
// Allocate buffers
amps = dsp::buffer::alloc<float>(2048);
conjRef = dsp::buffer::alloc<dsp::complex_t>(2048);
corrIn = (dsp::complex_t*)fftwf_alloc_complex(2048);
corrOut = (dsp::complex_t*)fftwf_alloc_complex(2048);
// Copy the phase reference
memcpy(conjRef, DAB_PHASE_SYM_CONJ, 2048 * sizeof(dsp::complex_t));
// Plan the FFT computation
plan = fftwf_plan_dft_1d(2048, (fftwf_complex*)corrIn, (fftwf_complex*)corrOut, FFTW_FORWARD, FFTW_ESTIMATE);
// Compute the correlation AGC configuration
this->agcRate = agcRate;
agcRateInv = 1.0f - agcRate;
base_type::init(in);
}
void reset() {
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; }
// Apply frequency shift
lv_32fc_t phase = lv_cmake(1.0f, 0.0f);
lv_32fc_t phaseDelta = lv_cmake(cos(offset), sin(offset));
#if VOLK_VERSION >= 030100
volk_32fc_s32fc_x2_rotator2_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#else
volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_in->readBuf, (lv_32fc_t*)_in->readBuf, phaseDelta, &phase, count);
#endif
// Compute the amplitude amplitude of all samples
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)_in->readBuf, 2048);
// Compute the average signal level by adding up all values
float level = 0.0f;
volk_32f_accumulator_s32f(&level, amps, 2048);
// Detect a frame sync condition
if (level < avgLvl * 0.5f) {
// Reset symbol counter
sym = 1;
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
// Update the average level
avgLvl = agcRate*level + agcRateInv*avgLvl;
// Handle phase reference
if (sym == 1) {
// Output the symbols (DEBUG ONLY)
memcpy(corrIn, _in->readBuf, 2048 * sizeof(dsp::complex_t));
fftwf_execute(plan);
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
int outCount = 0;
dsp::complex_t pi4 = { cos(3.1415926535*0.25), sin(3.1415926535*0.25) };
for (int i = -767; i < 768; i++) {
if (!i) { continue; }
int cid0 = ((i-1) >= 0) ? (i-1) : 2048+(i-1);
int cid1 = (i >= 0) ? i : 2048+i;;
out.writeBuf[outCount++] = pi4 * (corrOut[cid1] * corrOut[cid0].conj()) * (1.0f/(amps[cid0]*amps[cid0]));
}
out.swap(outCount);
// Multiply the samples with the conjugated phase reference signal
volk_32fc_x2_multiply_32fc((lv_32fc_t*)corrIn, (lv_32fc_t*)_in->readBuf, (lv_32fc_t*)conjRef, 2048);
// Compute the FFT of the product
fftwf_execute(plan);
// Compute the amplitude of the bins
volk_32fc_magnitude_32f(amps, (lv_32fc_t*)corrOut, 2048);
// Locate highest power bin
uint32_t peakId;
volk_32f_index_max_32u(&peakId, amps, 2048);
// Obtain the value of the bins next to the peak
float peakL = amps[(peakId + 2047) % 2048];
float peakR = amps[(peakId + 1) % 2048];
// Compute the integer frequency offset
float offInt = (peakId < 1024) ? (float)peakId : ((float)peakId - 2048.0f);
// Compute the frequency offset in rad/samp
float off = 3.1415926535f * (offInt + ((peakR - peakL) / (peakR + peakL))) * (1.0f / 1024.0f);
// Run control loop
offset -= 0.1f*off;
flog::debug("Offset: {} Hz, Error: {} Hz, Avg Level: {}", offset * (0.5f/3.1415926535f)*2.048e6, off * (0.5f/3.1415926535f)*2.048e6, avgLvl);
}
// Increment the symbol counter
sym++;
// Flush the input stream and return
base_type::_in->flush();
return count;
}
protected:
fftwf_plan plan;
float* amps;
dsp::complex_t* conjRef;
dsp::complex_t* corrIn;
dsp::complex_t* corrOut;
int sym;
float offset = 0.0f;
float avgLvl = 0.0f;
float agcRate;
float agcRateInv;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,163 @@
#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 <filesystem>
#include <dsp/stream.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/multirate/rational_resampler.h>
#include <dsp/sink/handler_sink.h>
#include <fstream>
#include <chrono>
#include "dab_dsp.h"
#include <gui/widgets/constellation_diagram.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "dab_decoder",
/* Description: */ "DAB/DAB+ Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE 2.048e6
#define VFO_BANDWIDTH 1.6e6
class M17DecoderModule : public ModuleManager::Instance {
public:
M17DecoderModule(std::string name) {
this->name = name;
file = std::ofstream("sync4.f32", std::ios::out | std::ios::binary);
// Load config
config.acquire();
config.release(true);
// Initialize VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
vfo->setSnapInterval(250);
// Initialize DSP here
csync.init(vfo->output, 1e-3, 246e-6, INPUT_SAMPLE_RATE);
ffsync.init(&csync.out);
ns.init(&ffsync.out, handler, this);
// Start DSO Here
csync.start();
ffsync.start();
ns.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~M17DecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP Here
if (enabled) {
csync.stop();
ffsync.stop();
ns.stop();
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), VFO_BANDWIDTH, INPUT_SAMPLE_RATE, VFO_BANDWIDTH, VFO_BANDWIDTH, true);
vfo->setSnapInterval(250);
// Set Input of demod here
csync.setInput(vfo->output);
// Start DSP here
csync.start();
ffsync.start();
ns.start();
enabled = true;
}
void disable() {
// Stop DSP here
csync.stop();
ffsync.stop();
ns.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
_this->constDiagram.draw();
if (!_this->enabled) { style::endDisabled(); }
}
std::ofstream file;
static void handler(dsp::complex_t* data, int count, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
//_this->file.write((char*)data, count * sizeof(dsp::complex_t));
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
_this->constDiagram.releaseBuffer();
}
std::string name;
bool enabled = true;
dab::CyclicSync csync;
dab::FrameFreqSync ffsync;
dsp::sink::Handler<dsp::complex_t> ns;
ImGui::ConstellationDiagram constDiagram;
// DSP Chain
VFOManager::VFO* vfo;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/dab_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new M17DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (M17DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@@ -0,0 +1,34 @@
0123456789
--- ---
0*4
1*5
2*6
1*5
2*6 = L + 3*7 - 0*4
3*7
2*6
3*7 = L + 4*8 - 1*5
4*8
3*7
4*8 = L + 5*9 - 2*6
5*9
0*5
1*6
2*7
1*6
2*7
3*8
2*7
3*8
4*9
=> Use same technique to cache the interpolation results

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

@@ -20,6 +20,16 @@ enum IFNRPreset {
IFNR_PRESET_BROADCAST IFNR_PRESET_BROADCAST
}; };
enum SquelchMode {
SQUELCH_MODE_OFF,
SQUELCH_MODE_POWER,
SQUELCH_MODE_SNR,
SQUELCH_MODE_CTCSS_MUTE,
SQUELCH_MODE_CTCSS_DECODE,
SQUELCH_MODE_DCS_MUTE,
SQUELCH_MODE_DCS_DECODE,
};
namespace demod { namespace demod {
class Demodulator { class Demodulator {
public: public:
@@ -45,6 +55,8 @@ namespace demod {
virtual int getDefaultDeemphasisMode() = 0; virtual int getDefaultDeemphasisMode() = 0;
virtual bool getFMIFNRAllowed() = 0; virtual bool getFMIFNRAllowed() = 0;
virtual bool getNBAllowed() = 0; virtual bool getNBAllowed() = 0;
virtual bool getHighPassAllowed() = 0;
virtual bool getSquelchAllowed() = 0;
virtual dsp::stream<dsp::stereo_t>* getOutput() = 0; virtual dsp::stream<dsp::stereo_t>* getOutput() = 0;
}; };
} }

View File

@@ -40,6 +40,12 @@ namespace demod {
void showMenu() { void showMenu() {
float menuWidth = ImGui::GetContentRegionAvail().x; float menuWidth = ImGui::GetContentRegionAvail().x;
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
_config->acquire();
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
_config->release(true);
}
ImGui::LeftLabel("AGC Attack"); ImGui::LeftLabel("AGC Attack");
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) { if (ImGui::SliderFloat(("##_radio_am_agc_attack_" + name).c_str(), &agcAttack, 1.0f, 200.0f)) {
@@ -56,12 +62,6 @@ namespace demod {
_config->conf[name][getName()]["agcDecay"] = agcDecay; _config->conf[name][getName()]["agcDecay"] = agcDecay;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("Carrier AGC##_radio_am_carrier_agc_" + name).c_str(), &carrierAgc)) {
demod.setAGCMode(carrierAgc ? dsp::demod::AM<dsp::stereo_t>::AGCMode::CARRIER : dsp::demod::AM<dsp::stereo_t>::AGCMode::AUDIO);
_config->acquire();
_config->conf[name][getName()]["carrierAgc"] = carrierAgc;
_config->release(true);
}
} }
void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); } void setBandwidth(double bandwidth) { demod.setBandwidth(bandwidth); }
@@ -86,6 +86,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -92,6 +92,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getHighPassAllowed() { return false; }
bool getSquelchAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -79,6 +79,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -79,6 +79,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -22,14 +22,11 @@ namespace demod {
if (config->conf[name][getName()].contains("lowPass")) { if (config->conf[name][getName()].contains("lowPass")) {
_lowPass = config->conf[name][getName()]["lowPass"]; _lowPass = config->conf[name][getName()]["lowPass"];
} }
if (config->conf[name][getName()].contains("highPass")) {
_highPass = config->conf[name][getName()]["highPass"];
}
_config->release(); _config->release();
// Define structure // Define structure
demod.init(input, getIFSampleRate(), bandwidth, _lowPass, _highPass); demod.init(input, getIFSampleRate(), bandwidth, _lowPass);
} }
void start() { demod.start(); } void start() { demod.start(); }
@@ -43,12 +40,6 @@ namespace demod {
_config->conf[name][getName()]["lowPass"] = _lowPass; _config->conf[name][getName()]["lowPass"] = _lowPass;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("High Pass##_radio_wfm_highpass_" + name).c_str(), &_highPass)) {
demod.setHighPass(_highPass);
_config->acquire();
_config->conf[name][getName()]["highPass"] = _highPass;
_config->release(true);
}
} }
void setBandwidth(double bandwidth) { void setBandwidth(double bandwidth) {
@@ -75,6 +66,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return true; } bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:
@@ -83,7 +76,6 @@ namespace demod {
ConfigManager* _config = NULL; ConfigManager* _config = NULL;
bool _lowPass = true; bool _lowPass = true;
bool _highPass = false;
std::string name; std::string name;
}; };

View File

@@ -59,6 +59,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getHighPassAllowed() { return false; }
bool getSquelchAllowed() { return false; }
dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; }
private: private:

View File

@@ -80,6 +80,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; }
bool getFMIFNRAllowed() { return false; } bool getFMIFNRAllowed() { return false; }
bool getNBAllowed() { return true; } bool getNBAllowed() { return true; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
private: private:

View File

@@ -1,22 +1,22 @@
#pragma once #pragma once
#include "../demod.h" #include "../demod.h"
#include <dsp/demod/broadcast_fm.h> #include <dsp/demod/broadcast_fm.h>
#include <dsp/clock_recovery/mm.h> #include "../rds_demod.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 <gui/widgets/symbol_diagram.h> #include <gui/widgets/symbol_diagram.h>
#include <fstream> #include <fstream>
#include <rds.h> #include <rds.h>
namespace demod { namespace demod {
enum RDSRegion {
RDS_REGION_EUROPE,
RDS_REGION_NORTH_AMERICA
};
class WFM : public Demodulator { class WFM : public Demodulator {
public: public:
WFM() {} WFM() : diag(0.5, 4096) {}
WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) { WFM(std::string name, ConfigManager* config, dsp::stream<dsp::complex_t>* input, double bandwidth, double audioSR) : diag(0.5, 4096) {
init(name, config, input, bandwidth, audioSR); init(name, config, input, bandwidth, audioSR);
} }
@@ -29,10 +29,18 @@ namespace demod {
this->name = name; this->name = name;
_config = config; _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.handler = fftRedraw;
fftRedrawHandler.ctx = this; fftRedrawHandler.ctx = this;
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler); gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
// Default
std::string rdsRegionStr = "eu";
// Load config // Load config
_config->acquire(); _config->acquire();
bool modified = false; bool modified = false;
@@ -45,48 +53,65 @@ namespace demod {
if (config->conf[name][getName()].contains("rds")) { if (config->conf[name][getName()].contains("rds")) {
_rds = config->conf[name][getName()]["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); _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); demod.init(input, bandwidth / 2.0f, getIFSampleRate(), _stereo, _lowPass, _rds);
recov.init(&demod.rdsOut, 5000.0 / 2375, omegaGain, muGain, 0.01); rdsDemod.init(&demod.rdsOut, _rdsInfo);
slice.init(&recov.out); hs.init(&rdsDemod.out, rdsHandler, this);
manch.init(&slice.out); reshape.init(&rdsDemod.soft, 4096, (1187 / 30) - 4096);
diff.init(&manch.out, 2); diagHandler.init(&reshape.out, _diagHandler, this);
hs.init(&diff.out, rdsHandler, this);
// Init RDS display
diag.lines.push_back(-0.8);
diag.lines.push_back(0.8);
} }
void start() { void start() {
demod.start(); demod.start();
recov.start(); rdsDemod.start();
slice.start();
manch.start();
diff.start();
hs.start(); hs.start();
reshape.start();
diagHandler.start();
} }
void stop() { void stop() {
demod.stop(); demod.stop();
recov.stop(); rdsDemod.stop();
slice.stop();
manch.stop();
diff.stop();
hs.stop(); hs.stop();
reshape.stop();
diagHandler.stop();
} }
void showMenu() { void showMenu() {
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
setStereo(_stereo);
_config->acquire();
_config->conf[name][getName()]["stereo"] = _stereo;
_config->release(true);
}
if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) { if (ImGui::Checkbox(("Low Pass##_radio_wfm_lowpass_" + name).c_str(), &_lowPass)) {
demod.setLowPass(_lowPass); demod.setLowPass(_lowPass);
_config->acquire(); _config->acquire();
_config->conf[name][getName()]["lowPass"] = _lowPass; _config->conf[name][getName()]["lowPass"] = _lowPass;
_config->release(true); _config->release(true);
} }
if (ImGui::Checkbox(("Stereo##_radio_wfm_stereo_" + name).c_str(), &_stereo)) {
setStereo(_stereo);
_config->acquire();
_config->conf[name][getName()]["stereo"] = _stereo;
_config->release(true);
}
if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) { if (ImGui::Checkbox(("Decode RDS##_radio_wfm_rds_" + name).c_str(), &_rds)) {
demod.setRDSOut(_rds); demod.setRDSOut(_rds);
_config->acquire(); _config->acquire();
@@ -94,14 +119,129 @@ namespace demod {
_config->release(true); _config->release(true);
} }
// if (_rds) { // TODO: This might break when the entire radio module is disabled
// if (rdsDecode.countryCodeValid()) { ImGui::Text("Country code: %d", rdsDecode.getCountryCode()); } if (!_rds) { ImGui::BeginDisabled(); }
// if (rdsDecode.programCoverageValid()) { ImGui::Text("Program coverage: %d", rdsDecode.getProgramCoverage()); } if (ImGui::Checkbox(("Advanced RDS Info##_radio_wfm_rds_info_" + name).c_str(), &_rdsInfo)) {
// if (rdsDecode.programRefNumberValid()) { ImGui::Text("Reference number: %d", rdsDecode.getProgramRefNumber()); } setAdvancedRds(_rdsInfo);
// if (rdsDecode.programTypeValid()) { ImGui::Text("Program type: %d", rdsDecode.getProgramType()); } _config->acquire();
// if (rdsDecode.PSNameValid()) { ImGui::Text("Program name: [%s]", rdsDecode.getPSName().c_str()); } _config->conf[name][getName()]["rdsInfo"] = _rdsInfo;
// if (rdsDecode.radioTextValid()) { ImGui::Text("Radiotext: [%s]", rdsDecode.getRadioText().c_str()); } _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) { void setBandwidth(double bandwidth) {
@@ -130,6 +270,8 @@ namespace demod {
int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; } int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; }
bool getFMIFNRAllowed() { return true; } bool getFMIFNRAllowed() { return true; }
bool getNBAllowed() { return false; } bool getNBAllowed() { return false; }
bool getHighPassAllowed() { return true; }
bool getSquelchAllowed() { return true; }
dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; }
// ============= DEDICATED FUNCTIONS ============= // ============= DEDICATED FUNCTIONS =============
@@ -139,12 +281,24 @@ namespace demod {
demod.setStereo(_stereo); demod.setStereo(_stereo);
} }
void setAdvancedRds(bool enabled) {
rdsDemod.setSoftEnabled(enabled);
_rdsInfo = enabled;
}
private: private:
static void rdsHandler(uint8_t* data, int count, void* ctx) { static void rdsHandler(uint8_t* data, int count, void* ctx) {
WFM* _this = (WFM*)ctx; WFM* _this = (WFM*)ctx;
_this->rdsDecode.process(data, count); _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) { static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
WFM* _this = (WFM*)ctx; WFM* _this = (WFM*)ctx;
if (!_this->_rds) { return; } if (!_this->_rds) { return; }
@@ -186,23 +340,31 @@ namespace demod {
} }
dsp::demod::BroadcastFM demod; dsp::demod::BroadcastFM demod;
dsp::clock_recovery::FD recov; RDSDemod rdsDemod;
dsp::digital::BinarySlicer slice;
dsp::digital::ManchesterDecoder manch;
dsp::digital::DifferentialDecoder diff;
dsp::sink::Handler<uint8_t> hs; dsp::sink::Handler<uint8_t> hs;
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler; 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; ConfigManager* _config = NULL;
bool _stereo = false; bool _stereo = false;
bool _lowPass = true; bool _lowPass = true;
bool _rds = false; bool _rds = false;
bool _rdsInfo = false;
float muGain = 0.01; float muGain = 0.01;
float omegaGain = (0.01*0.01)/4.0; float omegaGain = (0.01*0.01)/4.0;
int rdsRegionId = 0;
RDSRegion rdsRegion = RDS_REGION_EUROPE;
OptionList<std::string, RDSRegion> rdsRegions;
std::string name; std::string name;
}; };
} }

View File

@@ -5,10 +5,14 @@ enum {
RADIO_IFACE_CMD_SET_MODE, RADIO_IFACE_CMD_SET_MODE,
RADIO_IFACE_CMD_GET_BANDWIDTH, RADIO_IFACE_CMD_GET_BANDWIDTH,
RADIO_IFACE_CMD_SET_BANDWIDTH, RADIO_IFACE_CMD_SET_BANDWIDTH,
RADIO_IFACE_CMD_GET_SQUELCH_ENABLED, RADIO_IFACE_CMD_GET_SQUELCH_MODE,
RADIO_IFACE_CMD_SET_SQUELCH_ENABLED, RADIO_IFACE_CMD_SET_SQUELCH_MODE,
RADIO_IFACE_CMD_GET_SQUELCH_LEVEL, RADIO_IFACE_CMD_GET_SQUELCH_LEVEL,
RADIO_IFACE_CMD_SET_SQUELCH_LEVEL, RADIO_IFACE_CMD_SET_SQUELCH_LEVEL,
RADIO_IFACE_CMD_GET_CTCSS_TONE,
RADIO_IFACE_CMD_SET_CTCSS_TONE,
RADIO_IFACE_CMD_GET_HIGHPASS,
RADIO_IFACE_CMD_SET_HIGHPASS
}; };
enum { enum {

View File

@@ -8,7 +8,8 @@
#include <dsp/chain.h> #include <dsp/chain.h>
#include <dsp/noise_reduction/noise_blanker.h> #include <dsp/noise_reduction/noise_blanker.h>
#include <dsp/noise_reduction/fm_if.h> #include <dsp/noise_reduction/fm_if.h>
#include <dsp/noise_reduction/squelch.h> #include <dsp/noise_reduction/power_squelch.h>
#include <dsp/noise_reduction/ctcss_squelch.h>
#include <dsp/multirate/rational_resampler.h> #include <dsp/multirate/rational_resampler.h>
#include <dsp/filter/deephasis.h> #include <dsp/filter/deephasis.h>
#include <core.h> #include <core.h>
@@ -49,6 +50,22 @@ public:
ifnrPresets.define("Voice", IFNR_PRESET_VOICE); ifnrPresets.define("Voice", IFNR_PRESET_VOICE);
ifnrPresets.define("Narrow Band", IFNR_PRESET_NARROW_BAND); ifnrPresets.define("Narrow Band", IFNR_PRESET_NARROW_BAND);
squelchModes.define("off", "Off", SQUELCH_MODE_OFF);
squelchModes.define("power", "Power", SQUELCH_MODE_POWER);
//squelchModes.define("snr", "SNR", SQUELCH_MODE_SNR);
squelchModes.define("ctcss_mute", "CTCSS (Mute)", SQUELCH_MODE_CTCSS_MUTE);
squelchModes.define("ctcss_decode", "CTCSS (Decode Only)", SQUELCH_MODE_CTCSS_DECODE);
//squelchModes.define("dcs_mute", "DCS (Mute)", SQUELCH_MODE_DCS_MUTE);
//squelchModes.define("dcs_decode", "DCS (Decode Only)", SQUELCH_MODE_DCS_DECODE);
for (int i = 0; i < dsp::noise_reduction::_CTCSS_TONE_COUNT; i++) {
float tone = dsp::noise_reduction::CTCSS_TONES[i];
char buf[64];
sprintf(buf, "%.1fHz", tone);
ctcssTones.define((int)round(tone) * 10, buf, (dsp::noise_reduction::CTCSSTone)i);
}
ctcssTones.define(-1, "Any", dsp::noise_reduction::CTCSS_TONE_ANY);
// Initialize the config if it doesn't exist // Initialize the config if it doesn't exist
bool created = false; bool created = false;
config.acquire(); config.acquire();
@@ -72,19 +89,24 @@ public:
nb.init(NULL, 500.0 / 24000.0, 10.0); nb.init(NULL, 500.0 / 24000.0, 10.0);
fmnr.init(NULL, 32); fmnr.init(NULL, 32);
squelch.init(NULL, MIN_SQUELCH); powerSquelch.init(NULL, MIN_SQUELCH);
ifChain.addBlock(&nb, false); ifChain.addBlock(&nb, false);
ifChain.addBlock(&squelch, false); ifChain.addBlock(&powerSquelch, false);
ifChain.addBlock(&fmnr, false); ifChain.addBlock(&fmnr, false);
// Initialize audio DSP chain // Initialize audio DSP chain
afChain.init(&dummyAudioStream); afChain.init(&dummyAudioStream);
ctcss.init(NULL, 50000.0);
resamp.init(NULL, 250000.0, 48000.0); resamp.init(NULL, 250000.0, 48000.0);
hpTaps = dsp::taps::highPass(300.0, 100.0, 48000.0);
hpf.init(NULL, hpTaps);
deemp.init(NULL, 50e-6, 48000.0); deemp.init(NULL, 50e-6, 48000.0);
afChain.addBlock(&ctcss, false);
afChain.addBlock(&resamp, true); afChain.addBlock(&resamp, true);
afChain.addBlock(&hpf, false);
afChain.addBlock(&deemp, false); afChain.addBlock(&deemp, false);
// Initialize the sink // Initialize the sink
@@ -233,6 +255,33 @@ private:
} }
} }
// Squelch
if (_this->squelchAllowed) {
ImGui::LeftLabel("Squelch Mode");
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_sqelch_mode_" + _this->name).c_str(), &_this->squelchModeId, _this->squelchModes.txt)) {
_this->setSquelchMode(_this->squelchModes[_this->squelchModeId]);
}
switch (_this->squelchModes[_this->squelchModeId]) {
case SQUELCH_MODE_POWER:
ImGui::LeftLabel("Squelch Level");
ImGui::FillWidth();
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
_this->setSquelchLevel(_this->squelchLevel);
}
break;
case SQUELCH_MODE_CTCSS_MUTE:
if (_this->squelchModes[_this->squelchModeId] == SQUELCH_MODE_CTCSS_MUTE) {
ImGui::LeftLabel("CTCSS Tone");
ImGui::FillWidth();
if (ImGui::Combo(("##_radio_ctcss_tone_" + _this->name).c_str(), &_this->ctcssToneId, _this->ctcssTones.txt)) {
_this->setCTCSSTone(_this->ctcssTones[_this->ctcssToneId]);
}
}
}
}
// Noise blanker // Noise blanker
if (_this->nbAllowed) { if (_this->nbAllowed) {
if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) { if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) {
@@ -247,19 +296,6 @@ private:
if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); }
} }
// Squelch
if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) {
_this->setSquelchEnabled(_this->squelchEnabled);
}
if (!_this->squelchEnabled && _this->enabled) { style::beginDisabled(); }
ImGui::SameLine();
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
if (ImGui::SliderFloat(("##_radio_sqelch_lvl_" + _this->name).c_str(), &_this->squelchLevel, _this->MIN_SQUELCH, _this->MAX_SQUELCH, "%.3fdB")) {
_this->setSquelchLevel(_this->squelchLevel);
}
if (!_this->squelchEnabled && _this->enabled) { style::endDisabled(); }
// FM IF Noise Reduction // FM IF Noise Reduction
if (_this->FMIFNRAllowed) { if (_this->FMIFNRAllowed) {
if (ImGui::Checkbox(("IF Noise Reduction##_radio_fmifnr_ena_" + _this->name).c_str(), &_this->FMIFNREnabled)) { if (ImGui::Checkbox(("IF Noise Reduction##_radio_fmifnr_ena_" + _this->name).c_str(), &_this->FMIFNREnabled)) {
@@ -276,9 +312,53 @@ private:
} }
} }
// High pass
if (_this->highPassAllowed) {
if (ImGui::Checkbox(("High Pass##_radio_hpf_" + _this->name).c_str(), &_this->highPass)) {
_this->setHighPass(_this->highPass);
}
}
// Demodulator specific menu // Demodulator specific menu
_this->selectedDemod->showMenu(); _this->selectedDemod->showMenu();
// Display the squelch diagnostics
switch (_this->squelchModes[_this->squelchModeId]) {
case SQUELCH_MODE_CTCSS_MUTE:
ImGui::TextUnformatted("Received Tone:");
ImGui::SameLine();
{
auto ctone = _this->ctcss.getCurrentTone();
auto dtone = _this->ctcssTones[_this->ctcssToneId];
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
if (dtone == dsp::noise_reduction::CTCSS_TONE_ANY || ctone == dtone) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
else {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
}
else {
ImGui::TextUnformatted("None");
}
}
break;
case SQUELCH_MODE_CTCSS_DECODE:
ImGui::TextUnformatted("Received Tone:");
ImGui::SameLine();
{
auto ctone = _this->ctcss.getCurrentTone();
if (ctone != dsp::noise_reduction::CTCSS_TONE_NONE) {
ImGui::TextColored(ImVec4(0, 1, 0, 1), "%.1fHz", dsp::noise_reduction::CTCSS_TONES[_this->ctcss.getCurrentTone()]);
}
else {
ImGui::TextUnformatted("None");
}
}
break;
}
if (!_this->enabled) { style::endDisabled(); } if (!_this->enabled) { style::endDisabled(); }
} }
@@ -360,15 +440,20 @@ private:
maxBandwidth = selectedDemod->getMaxBandwidth(); maxBandwidth = selectedDemod->getMaxBandwidth();
bandwidthLocked = selectedDemod->getBandwidthLocked(); bandwidthLocked = selectedDemod->getBandwidthLocked();
snapInterval = selectedDemod->getDefaultSnapInterval(); snapInterval = selectedDemod->getDefaultSnapInterval();
squelchLevel = MIN_SQUELCH;
deempAllowed = selectedDemod->getDeempAllowed(); deempAllowed = selectedDemod->getDeempAllowed();
deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode()); deempId = deempModes.valueId((DeemphasisMode)selectedDemod->getDefaultDeemphasisMode());
squelchEnabled = false; squelchModeId = squelchModes.valueId(SQUELCH_MODE_OFF);
squelchLevel = MIN_SQUELCH;
ctcssToneId = ctcssTones.valueId(dsp::noise_reduction::CTCSS_TONE_67Hz);
highPass = false;
postProcEnabled = selectedDemod->getPostProcEnabled(); postProcEnabled = selectedDemod->getPostProcEnabled();
FMIFNRAllowed = selectedDemod->getFMIFNRAllowed(); FMIFNRAllowed = selectedDemod->getFMIFNRAllowed();
FMIFNREnabled = false; FMIFNREnabled = false;
fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE); fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE);
nbAllowed = selectedDemod->getNBAllowed(); nbAllowed = selectedDemod->getNBAllowed();
squelchAllowed = selectedDemod->getSquelchAllowed();
highPassAllowed = selectedDemod->getHighPassAllowed();
nbEnabled = false; nbEnabled = false;
nbLevel = 0.0f; nbLevel = 0.0f;
double ifSamplerate = selectedDemod->getIFSampleRate(); double ifSamplerate = selectedDemod->getIFSampleRate();
@@ -380,11 +465,24 @@ private:
if (config.conf[name][selectedDemod->getName()].contains("snapInterval")) { if (config.conf[name][selectedDemod->getName()].contains("snapInterval")) {
snapInterval = config.conf[name][selectedDemod->getName()]["snapInterval"]; snapInterval = config.conf[name][selectedDemod->getName()]["snapInterval"];
} }
if (config.conf[name][selectedDemod->getName()].contains("squelchMode")) {
std::string squelchModeStr = config.conf[name][selectedDemod->getName()]["squelchMode"];
if (squelchModes.keyExists(squelchModeStr)) {
squelchModeId = squelchModes.keyId(squelchModeStr);
}
}
if (config.conf[name][selectedDemod->getName()].contains("squelchLevel")) { if (config.conf[name][selectedDemod->getName()].contains("squelchLevel")) {
squelchLevel = config.conf[name][selectedDemod->getName()]["squelchLevel"]; squelchLevel = config.conf[name][selectedDemod->getName()]["squelchLevel"];
} }
if (config.conf[name][selectedDemod->getName()].contains("squelchEnabled")) { if (config.conf[name][selectedDemod->getName()].contains("ctcssTone")) {
squelchEnabled = config.conf[name][selectedDemod->getName()]["squelchEnabled"]; int ctcssToneX10 = config.conf[name][selectedDemod->getName()]["ctcssTone"];
if (ctcssTones.keyExists(ctcssToneX10)) {
ctcssToneId = ctcssTones.keyId(ctcssToneX10);
}
}
if (config.conf[name][selectedDemod->getName()].contains("highPass")) {
highPass = config.conf[name][selectedDemod->getName()]["highPass"];
} }
if (config.conf[name][selectedDemod->getName()].contains("deempMode")) { if (config.conf[name][selectedDemod->getName()].contains("deempMode")) {
if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) { if (!config.conf[name][selectedDemod->getName()]["deempMode"].is_string()) {
@@ -434,16 +532,22 @@ private:
setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false); setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false);
// Configure squelch // Configure squelch
setSquelchMode(squelchAllowed ? squelchModes[squelchModeId] : SQUELCH_MODE_OFF);
setSquelchLevel(squelchLevel); setSquelchLevel(squelchLevel);
setSquelchEnabled(squelchEnabled); setCTCSSTone(ctcssTones[ctcssToneId]);
// Configure AF chain // Configure AF chain
if (postProcEnabled) { if (postProcEnabled) {
// Configure resampler // Configure resampler
afChain.stop(); afChain.stop();
resamp.setInSamplerate(selectedDemod->getAFSampleRate()); double afsr = selectedDemod->getAFSampleRate();
setAudioSampleRate(audioSampleRate); ctcss.setSamplerate(afsr);
resamp.setInSamplerate(afsr);
afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
setAudioSampleRate(audioSampleRate);
// Configure the HPF
setHighPass(highPass && highPassAllowed);
// Configure deemphasis // Configure deemphasis
setDeemphasisMode(deempModes[deempId]); setDeemphasisMode(deempModes[deempId]);
@@ -489,12 +593,32 @@ private:
// Configure resampler // Configure resampler
resamp.setOutSamplerate(audioSampleRate); resamp.setOutSamplerate(audioSampleRate);
// Configure the HPF sample rate
hpTaps = dsp::taps::highPass(300.0, 100.0, audioSampleRate);
hpf.setTaps(hpTaps);
// Configure deemphasis sample rate // Configure deemphasis sample rate
deemp.setSamplerate(audioSampleRate); deemp.setSamplerate(audioSampleRate);
afChain.start(); afChain.start();
} }
void setHighPass(bool enabled) {
// Update the state
highPass = enabled;
// Check if post-processing is enabled and that a demodulator is selected
if (!postProcEnabled || !selectedDemod) { return; }
// Set the state of the HPF in the AF chain
afChain.setBlockEnabled(&hpf, enabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["highPass"] = enabled;
config.release(true);
}
void setDeemphasisMode(DeemphasisMode mode) { void setDeemphasisMode(DeemphasisMode mode) {
deempId = deempModes.valueId(mode); deempId = deempModes.valueId(mode);
if (!postProcEnabled || !selectedDemod) { return; } if (!postProcEnabled || !selectedDemod) { return; }
@@ -522,6 +646,7 @@ private:
void setNBLevel(float level) { void setNBLevel(float level) {
nbLevel = std::clamp<float>(level, MIN_NB, MAX_NB); nbLevel = std::clamp<float>(level, MIN_NB, MAX_NB);
nb.setLevel(nbLevel); nb.setLevel(nbLevel);
if (!selectedDemod) { return; }
// Save config // Save config
config.acquire(); config.acquire();
@@ -529,20 +654,59 @@ private:
config.release(true); config.release(true);
} }
void setSquelchEnabled(bool enable) { void setSquelchMode(SquelchMode mode) {
squelchEnabled = enable; squelchModeId = squelchModes.valueId(mode);
if (!selectedDemod) { return; } if (!selectedDemod) { return; }
ifChain.setBlockEnabled(&squelch, squelchEnabled, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
// Disable all squelch blocks
ifChain.disableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
afChain.disableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
// Enable the block depending on the mode
switch (mode) {
case SQUELCH_MODE_OFF:
break;
case SQUELCH_MODE_POWER:
// Enable the power squelch block
ifChain.enableBlock(&powerSquelch, [=](dsp::stream<dsp::complex_t>* out){ selectedDemod->setInput(out); });
break;
case SQUELCH_MODE_SNR:
// TODO
break;
case SQUELCH_MODE_CTCSS_MUTE:
// Set the required tone and enable the CTCSS squelch block
ctcss.setRequiredTone(ctcssTones[ctcssToneId]);
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
break;
case SQUELCH_MODE_CTCSS_DECODE:
// Set the required tone to none and enable the CTCSS squelch block
ctcss.setRequiredTone(dsp::noise_reduction::CTCSS_TONE_NONE);
afChain.enableBlock(&ctcss, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); });
break;
case SQUELCH_MODE_DCS_MUTE:
// TODO
break;
case SQUELCH_MODE_DCS_DECODE:
// TODO
break;
}
// Save config // Save config
config.acquire(); config.acquire();
config.conf[name][selectedDemod->getName()]["squelchEnabled"] = squelchEnabled; config.conf[name][selectedDemod->getName()]["squelchMode"] = squelchModes.key(squelchModeId);
config.release(true); config.release(true);
} }
void setSquelchLevel(float level) { void setSquelchLevel(float level) {
squelchLevel = std::clamp<float>(level, MIN_SQUELCH, MAX_SQUELCH); squelchLevel = std::clamp<float>(level, MIN_SQUELCH, MAX_SQUELCH);
squelch.setLevel(squelchLevel); powerSquelch.setLevel(squelchLevel);
if (!selectedDemod) { return; }
// Save config // Save config
config.acquire(); config.acquire();
@@ -550,6 +714,24 @@ private:
config.release(true); config.release(true);
} }
void setCTCSSTone(dsp::noise_reduction::CTCSSTone tone) {
// Check for an invalid value
if (tone == dsp::noise_reduction::CTCSS_TONE_NONE) { return; }
// If not in CTCSS mute mode, do nothing
if (squelchModes[squelchModeId] != SQUELCH_MODE_CTCSS_MUTE) { return; }
// Set the tone
ctcssToneId = ctcssTones.valueId(tone);
ctcss.setRequiredTone(tone);
if (!selectedDemod) { return; }
// Save config
config.acquire();
config.conf[name][selectedDemod->getName()]["ctcssTone"] = ctcssTones.key(ctcssToneId);
config.release(true);
}
void setFMIFNREnabled(bool enabled) { void setFMIFNREnabled(bool enabled) {
FMIFNREnabled = enabled; FMIFNREnabled = enabled;
if (!selectedDemod) { return; } if (!selectedDemod) { return; }
@@ -597,14 +779,16 @@ private:
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) { static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
RadioModule* _this = (RadioModule*)ctx; RadioModule* _this = (RadioModule*)ctx;
if (!_this->enabled || !_this->selectedDemod) { return; }
// If no demod is selected, reject the command
if (!_this->selectedDemod) { return; }
// Execute commands // Execute commands
if (code == RADIO_IFACE_CMD_GET_MODE && out) { if (code == RADIO_IFACE_CMD_GET_MODE && out) {
int* _out = (int*)out; int* _out = (int*)out;
*_out = _this->selectedDemodID; *_out = _this->selectedDemodID;
} }
else if (code == RADIO_IFACE_CMD_SET_MODE && in) { else if (code == RADIO_IFACE_CMD_SET_MODE && in && _this->enabled) {
int* _in = (int*)in; int* _in = (int*)in;
_this->selectDemodByID((DemodID)*_in); _this->selectDemodByID((DemodID)*_in);
} }
@@ -612,27 +796,43 @@ private:
float* _out = (float*)out; float* _out = (float*)out;
*_out = _this->bandwidth; *_out = _this->bandwidth;
} }
else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in) { else if (code == RADIO_IFACE_CMD_SET_BANDWIDTH && in && _this->enabled) {
float* _in = (float*)in; float* _in = (float*)in;
if (_this->bandwidthLocked) { return; } if (_this->bandwidthLocked) { return; }
_this->setBandwidth(*_in); _this->setBandwidth(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_ENABLED && out) { else if (code == RADIO_IFACE_CMD_GET_SQUELCH_MODE && out) {
bool* _out = (bool*)out; SquelchMode* _out = (SquelchMode*)out;
*_out = _this->squelchEnabled; *_out = _this->squelchModes[_this->squelchModeId];
} }
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_ENABLED && in) { else if (code == RADIO_IFACE_CMD_SET_SQUELCH_MODE && in && _this->enabled) {
bool* _in = (bool*)in; SquelchMode* _in = (SquelchMode*)in;
_this->setSquelchEnabled(*_in); _this->setSquelchMode(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_SQUELCH_LEVEL && out) { else if (code == RADIO_IFACE_CMD_GET_SQUELCH_LEVEL && out) {
float* _out = (float*)out; float* _out = (float*)out;
*_out = _this->squelchLevel; *_out = _this->squelchLevel;
} }
else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in) { else if (code == RADIO_IFACE_CMD_SET_SQUELCH_LEVEL && in && _this->enabled) {
float* _in = (float*)in; float* _in = (float*)in;
_this->setSquelchLevel(*_in); _this->setSquelchLevel(*_in);
} }
else if (code == RADIO_IFACE_CMD_GET_CTCSS_TONE && out) {
dsp::noise_reduction::CTCSSTone* _out = (dsp::noise_reduction::CTCSSTone*)out;
*_out = _this->ctcssTones[_this->ctcssToneId];
}
else if (code == RADIO_IFACE_CMD_SET_CTCSS_TONE && in && _this->enabled) {
dsp::noise_reduction::CTCSSTone* _in = (dsp::noise_reduction::CTCSSTone*)in;
_this->setCTCSSTone(*_in);
}
else if (code == RADIO_IFACE_CMD_GET_HIGHPASS && out) {
bool* _out = (bool*)out;
*_out = _this->highPass;
}
else if (code == RADIO_IFACE_CMD_SET_HIGHPASS && in && _this->enabled) {
bool* _in = (bool*)in;
_this->setHighPass(*_in);
}
else { else {
return; return;
} }
@@ -653,12 +853,15 @@ private:
dsp::chain<dsp::complex_t> ifChain; dsp::chain<dsp::complex_t> ifChain;
dsp::noise_reduction::NoiseBlanker nb; dsp::noise_reduction::NoiseBlanker nb;
dsp::noise_reduction::FMIF fmnr; dsp::noise_reduction::FMIF fmnr;
dsp::noise_reduction::Squelch squelch; dsp::noise_reduction::PowerSquelch powerSquelch;
// Audio chain // Audio chain
dsp::stream<dsp::stereo_t> dummyAudioStream; dsp::stream<dsp::stereo_t> dummyAudioStream;
dsp::chain<dsp::stereo_t> afChain; dsp::chain<dsp::stereo_t> afChain;
dsp::noise_reduction::CTCSSSquelch ctcss;
dsp::multirate::RationalResampler<dsp::stereo_t> resamp; dsp::multirate::RationalResampler<dsp::stereo_t> resamp;
dsp::tap<float> hpTaps;
dsp::filter::FIR<dsp::stereo_t, float> hpf;
dsp::filter::Deemphasis<dsp::stereo_t> deemp; dsp::filter::Deemphasis<dsp::stereo_t> deemp;
SinkManager::Stream stream; SinkManager::Stream stream;
@@ -667,6 +870,8 @@ private:
OptionList<std::string, DeemphasisMode> deempModes; OptionList<std::string, DeemphasisMode> deempModes;
OptionList<std::string, IFNRPreset> ifnrPresets; OptionList<std::string, IFNRPreset> ifnrPresets;
OptionList<std::string, SquelchMode> squelchModes;
OptionList<int, dsp::noise_reduction::CTCSSTone> ctcssTones;
double audioSampleRate = 48000.0; double audioSampleRate = 48000.0;
float minBandwidth; float minBandwidth;
@@ -677,8 +882,13 @@ private:
int selectedDemodID = 1; int selectedDemodID = 1;
bool postProcEnabled; bool postProcEnabled;
bool squelchEnabled = false; int squelchModeId = 0;
float squelchLevel; float squelchLevel;
int ctcssToneId = 0;
bool squelchAllowed = false;
bool highPass = false;
bool highPassAllowed = false;
int deempId = 0; int deempId = 0;
bool deempAllowed; bool deempAllowed;

View File

@@ -3,6 +3,8 @@
#include <map> #include <map>
#include <algorithm> #include <algorithm>
#include <utils/flog.h>
namespace rds { namespace rds {
std::map<uint16_t, BlockType> SYNDROMES = { std::map<uint16_t, BlockType> SYNDROMES = {
{ 0b1111011000, BLOCK_TYPE_A }, { 0b1111011000, BLOCK_TYPE_A },
@@ -20,6 +22,98 @@ namespace rds {
{ BLOCK_TYPE_D, 0b0110110100 } { 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 // 9876543210
const uint16_t LFSR_POLY = 0b0110111001; const uint16_t LFSR_POLY = 0b0110111001;
const uint16_t IN_POLY = 0b1100011011; const uint16_t IN_POLY = 0b1100011011;
@@ -28,7 +122,7 @@ namespace rds {
const int DATA_LEN = 16; const int DATA_LEN = 16;
const int POLY_LEN = 10; 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++) { for (int i = 0; i < count; i++) {
// Shift in the bit // Shift in the bit
shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1); shiftReg = ((shiftReg << 1) & 0x3FFFFFF) | (symbols[i] & 1);
@@ -54,18 +148,26 @@ namespace rds {
type = (BlockType)((lastType + 1) % _BLOCK_TYPE_COUNT); 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]); blocks[type] = correctErrors(shiftReg, type, blockAvail[type]);
// Update continous group count // If block type is A, decode it directly, otherwise, update continous count
if (type == BLOCK_TYPE_A) { contGroup = 1; } if (type == BLOCK_TYPE_A) {
else if (type == BLOCK_TYPE_B && lastType == BLOCK_TYPE_A) { contGroup++; } 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_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 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 we've got an entire group, process it
if (contGroup >= 4) { if (contGroup >= 3) {
contGroup = 0; contGroup = 0;
decodeGroup(); 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; uint16_t syn = 0;
// Calculate the syndrome using a LFSR // Calculate the syndrome using a LFSR
@@ -95,7 +197,7 @@ namespace rds {
return syn; 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 // Subtract the offset from block
block ^= (uint32_t)OFFSETS[type]; block ^= (uint32_t)OFFSETS[type];
uint32_t out = block; uint32_t out = block;
@@ -124,29 +226,48 @@ namespace rds {
return out; return out;
} }
void RDSDecoder::decodeGroup() { void Decoder::decodeBlockA() {
std::lock_guard<std::mutex> lck(groupMtx); // Acquire lock
auto now = std::chrono::high_resolution_clock::now(); std::lock_guard<std::mutex> lck(blockAMtx);
anyGroupLastUpdate = now;
// Make sure blocks A and B are available // If it didn't decode properly return
if (!blockAvail[BLOCK_TYPE_A] || !blockAvail[BLOCK_TYPE_B]) { return; } if (!blockAvail[BLOCK_TYPE_A]) { return; }
// Decode PI code // Decode PI code
piCode = (blocks[BLOCK_TYPE_A] >> 10) & 0xFFFF;
countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF; countryCode = (blocks[BLOCK_TYPE_A] >> 22) & 0xF;
programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF); programCoverage = (AreaCoverage)((blocks[BLOCK_TYPE_A] >> 18) & 0xF);
programRefNumber = (blocks[BLOCK_TYPE_A] >> 10) & 0xFF; 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 // Decode group type and version
uint8_t groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF; groupType = (blocks[BLOCK_TYPE_B] >> 22) & 0xF;
GroupVersion groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1); groupVer = (GroupVersion)((blocks[BLOCK_TYPE_B] >> 21) & 1);
// Decode traffic program and program type // Decode traffic program and program type
trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1; trafficProgram = (blocks[BLOCK_TYPE_B] >> 20) & 1;
programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F); programType = (ProgramType)((blocks[BLOCK_TYPE_B] >> 15) & 0x1F);
if (groupType == 0) { // Update timeout
group0LastUpdate = now; blockBLastUpdate = std::chrono::high_resolution_clock::now();
}
void Decoder::decodeGroup0() {
// Acquire lock
std::lock_guard<std::mutex> lck(group0Mtx);
// Decode Block B data
trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1; trafficAnnouncement = (blocks[BLOCK_TYPE_B] >> 14) & 1;
music = (blocks[BLOCK_TYPE_B] >> 13) & 1; music = (blocks[BLOCK_TYPE_B] >> 13) & 1;
uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1; uint8_t diBit = (blocks[BLOCK_TYPE_B] >> 12) & 1;
@@ -154,6 +275,7 @@ namespace rds {
uint8_t diOffset = 3 - offset; uint8_t diOffset = 3 - offset;
uint8_t psOffset = offset * 2; uint8_t psOffset = offset * 2;
// Decode Block C data
if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) { if (groupVer == GROUP_VER_A && blockAvail[BLOCK_TYPE_C]) {
alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF; alternateFrequency = (blocks[BLOCK_TYPE_C] >> 10) & 0xFFFF;
} }
@@ -167,9 +289,15 @@ namespace rds {
programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF; programServiceName[psOffset] = (blocks[BLOCK_TYPE_D] >> 18) & 0xFF;
programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; programServiceName[psOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF;
} }
// Update timeout
group0LastUpdate = std::chrono::high_resolution_clock::now();
} }
else if (groupType == 2) {
group2LastUpdate = now; void Decoder::decodeGroup2() {
// Acquire lock
std::lock_guard<std::mutex> lck(group2Mtx);
// Get char offset and write chars in the Radiotext // Get char offset and write chars in the Radiotext
bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1; bool nAB = (blocks[BLOCK_TYPE_B] >> 14) & 1;
uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF; uint8_t offset = (blocks[BLOCK_TYPE_B] >> 10) & 0xF;
@@ -199,21 +327,163 @@ namespace rds {
radioText[rtOffset + 1] = (blocks[BLOCK_TYPE_D] >> 10) & 0xFF; radioText[rtOffset + 1] = (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;
} }
} }
bool RDSDecoder::anyGroupValid() { // Update timeout
auto now = std::chrono::high_resolution_clock::now(); group10LastUpdate = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - anyGroupLastUpdate)).count() < 5000.0;
} }
bool RDSDecoder::group0Valid() { void Decoder::decodeGroup() {
auto now = std::chrono::high_resolution_clock::now(); // Make sure blocks B is available
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - group0LastUpdate)).count() < 5000.0; 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;
}
} }
bool RDSDecoder::group2Valid() { 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 {
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 Decoder::blockAValid() {
auto now = std::chrono::high_resolution_clock::now(); 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 - blockALastUpdate)).count() < RDS_BLOCK_A_TIMEOUT_MS;
}
bool Decoder::blockBValid() {
auto now = std::chrono::high_resolution_clock::now();
return (std::chrono::duration_cast<std::chrono::milliseconds>(now - blockBLastUpdate)).count() < RDS_BLOCK_B_TIMEOUT_MS;
}
bool Decoder::group0Valid() {
auto now = std::chrono::high_resolution_clock::now();
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 <chrono>
#include <mutex> #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 { namespace rds {
enum BlockType { enum BlockType {
BLOCK_TYPE_A, BLOCK_TYPE_A,
@@ -20,22 +26,42 @@ namespace rds {
}; };
enum AreaCoverage { enum AreaCoverage {
AREA_COVERAGE_LOCAL, AREA_COVERAGE_INVALID = -1,
AREA_COVERAGE_INTERNATIONAL, AREA_COVERAGE_LOCAL = 0,
AREA_COVERAGE_NATIONAL, AREA_COVERAGE_INTERNATIONAL = 1,
AREA_COVERAGE_SUPRA_NATIONAL, AREA_COVERAGE_NATIONAL = 2,
AREA_COVERAGE_REGIONAL1, AREA_COVERAGE_SUPRA_NATIONAL = 3,
AREA_COVERAGE_REGIONAL2, AREA_COVERAGE_REGIONAL1 = 4,
AREA_COVERAGE_REGIONAL3, AREA_COVERAGE_REGIONAL2 = 5,
AREA_COVERAGE_REGIONAL4, AREA_COVERAGE_REGIONAL3 = 6,
AREA_COVERAGE_REGIONAL5, AREA_COVERAGE_REGIONAL4 = 7,
AREA_COVERAGE_REGIONAL6, AREA_COVERAGE_REGIONAL5 = 8,
AREA_COVERAGE_REGIONAL7, AREA_COVERAGE_REGIONAL6 = 9,
AREA_COVERAGE_REGIONAL8, AREA_COVERAGE_REGIONAL7 = 10,
AREA_COVERAGE_REGIONAL9, AREA_COVERAGE_REGIONAL8 = 11,
AREA_COVERAGE_REGIONAL10, AREA_COVERAGE_REGIONAL9 = 12,
AREA_COVERAGE_REGIONAL11, AREA_COVERAGE_REGIONAL10 = 13,
AREA_COVERAGE_REGIONAL12 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 { enum ProgramType {
@@ -108,6 +134,76 @@ namespace rds {
PROGRAM_TYPE_EU_ALARM = 31 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 { enum DecoderIdentification {
DECODER_IDENT_STEREO = (1 << 0), DECODER_IDENT_STEREO = (1 << 0),
DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1), DECODER_IDENT_ARTIFICIAL_HEAD = (1 << 1),
@@ -115,35 +211,49 @@ namespace rds {
DECODER_IDENT_VARIABLE_PTY = (1 << 0) DECODER_IDENT_VARIABLE_PTY = (1 << 0)
}; };
class RDSDecoder { class Decoder {
public: public:
void process(uint8_t* symbols, int count); void process(uint8_t* symbols, int count);
bool countryCodeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } bool piCodeValid() { std::lock_guard<std::mutex> lck(blockAMtx); return blockAValid(); }
uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(groupMtx); return countryCode; } uint16_t getPICode() { std::lock_guard<std::mutex> lck(blockAMtx); return piCode; }
bool programCoverageValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } uint8_t getCountryCode() { std::lock_guard<std::mutex> lck(blockAMtx); return countryCode; }
uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(groupMtx); return programCoverage; } uint8_t getProgramCoverage() { std::lock_guard<std::mutex> lck(blockAMtx); return programCoverage; }
bool programRefNumberValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); } uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(blockAMtx); return programRefNumber; }
uint8_t getProgramRefNumber() { std::lock_guard<std::mutex> lck(groupMtx); return programRefNumber; } std::string getCallsign() { std::lock_guard<std::mutex> lck(blockAMtx); return callsign; }
bool programTypeValid() { std::lock_guard<std::mutex> lck(groupMtx); return anyGroupValid(); }
ProgramType getProgramType() { std::lock_guard<std::mutex> lck(groupMtx); return programType; }
bool musicValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); } bool programTypeValid() { std::lock_guard<std::mutex> lck(blockBMtx); return blockBValid(); }
bool getMusic() { std::lock_guard<std::mutex> lck(groupMtx); return music; } ProgramType getProgramType() { std::lock_guard<std::mutex> lck(blockBMtx); return programType; }
bool PSNameValid() { std::lock_guard<std::mutex> lck(groupMtx); return group0Valid(); }
std::string getPSName() { std::lock_guard<std::mutex> lck(groupMtx); return programServiceName; }
bool radioTextValid() { std::lock_guard<std::mutex> lck(groupMtx); return group2Valid(); } bool musicValid() { std::lock_guard<std::mutex> lck(group0Mtx); return group0Valid(); }
std::string getRadioText() { std::lock_guard<std::mutex> lck(groupMtx); return radioText; } 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: private:
static uint16_t calcSyndrome(uint32_t block); static uint16_t calcSyndrome(uint32_t block);
static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered); static uint32_t correctErrors(uint32_t block, BlockType type, bool& recovered);
void decodeBlockA();
void decodeBlockB();
void decodeGroup0();
void decodeGroup2();
void decodeGroup10();
void decodeGroup(); 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 group0Valid();
bool group2Valid(); bool group2Valid();
bool group10Valid();
// State machine // State machine
uint32_t shiftReg = 0; uint32_t shiftReg = 0;
@@ -154,17 +264,26 @@ namespace rds {
uint32_t blocks[_BLOCK_TYPE_COUNT]; uint32_t blocks[_BLOCK_TYPE_COUNT];
bool blockAvail[_BLOCK_TYPE_COUNT]; bool blockAvail[_BLOCK_TYPE_COUNT];
// All groups // Block A (All groups)
std::mutex groupMtx; std::mutex blockAMtx;
std::chrono::time_point<std::chrono::high_resolution_clock> anyGroupLastUpdate; std::chrono::time_point<std::chrono::high_resolution_clock> blockALastUpdate{}; // 1970-01-01
uint16_t piCode;
uint8_t countryCode; uint8_t countryCode;
AreaCoverage programCoverage; AreaCoverage programCoverage;
uint8_t programRefNumber; 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; bool trafficProgram;
ProgramType programType; ProgramType programType;
// Group type 0 // 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 trafficAnnouncement;
bool music; bool music;
uint8_t decoderIdent; uint8_t decoderIdent;
@@ -172,9 +291,16 @@ namespace rds {
std::string programServiceName = " "; std::string programServiceName = " ";
// Group type 2 // 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; bool rtAB = false;
std::string radioText = " "; 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,8 @@
cmake_minimum_required(VERSION 3.13)
project(ryfi_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
include(${SDRPP_MODULE_CMAKE})
target_include_directories(ryfi_decoder PRIVATE "src/")

View File

@@ -0,0 +1,139 @@
#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 <filesystem>
#include <dsp/routing/splitter.h>
#include <dsp/buffer/reshaper.h>
#include <dsp/sink/handler_sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/constellation_diagram.h>
#include "ryfi/receiver.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "ryfi_decoder",
/* Description: */ "RyFi decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
#define INPUT_BANDWIDTH 600e3
#define INPUT_SAMPLE_RATE 1000e3
#define INPUT_BAUDRATE 500e3
#define SYMBOL_DIAG_RATE 30
#define SYMBOL_DIAG_COUNT 1024
class RyFiDecoderModule : public ModuleManager::Instance {
public:
RyFiDecoderModule(std::string name) {
this->name = name;
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
rx.init(vfo->output, INPUT_BAUDRATE, INPUT_SAMPLE_RATE);
reshape.init(rx.softOut, SYMBOL_DIAG_COUNT, (INPUT_BAUDRATE / SYMBOL_DIAG_RATE) - SYMBOL_DIAG_COUNT);
symSink.init(&reshape.out, symSinkHandler, this);
rx.onPacket.bind(&RyFiDecoderModule::packetHandler, this);
rx.start();
reshape.start();
symSink.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~RyFiDecoderModule() {
rx.stop();
reshape.stop();
symSink.stop();
sigpath::vfoManager.deleteVFO(vfo);
gui::menu.removeEntry(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), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true);
rx.setInput(vfo->output);
rx.start();
reshape.start();
symSink.start();
enabled = true;
}
void disable() {
rx.stop();
reshape.stop();
symSink.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
void packetHandler(ryfi::Packet pkt) {
flog::debug("Got a {} byte packet!", pkt.size());
}
static void menuHandler(void* ctx) {
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->constDiagram.draw();
if (!_this->enabled) { style::endDisabled(); }
}
static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) {
RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx;
dsp::complex_t* buf = _this->constDiagram.acquireBuffer();
memcpy(buf, data, 1024 * sizeof(dsp::complex_t));
_this->constDiagram.releaseBuffer();
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
ryfi::Receiver rx;
dsp::buffer::Reshaper<dsp::complex_t> reshape;
dsp::sink::Handler<dsp::complex_t> symSink;
ImGui::ConstellationDiagram constDiagram;
};
MOD_EXPORT void _INIT_() {
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new RyFiDecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (RyFiDecoderModule*)instance;
}
MOD_EXPORT void _END_() {
}

View File

@@ -0,0 +1,74 @@
#include "conv_codec.h"
namespace ryfi {
ConvEncoder::ConvEncoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
// Init the base class
base_type::init(in);
}
ConvEncoder::~ConvEncoder() {
// Destroy the convolutional encoder instance
correct_convolutional_destroy(conv);
}
int ConvEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
// Run convolutional encoder on the data
return correct_convolutional_encode(conv, in, count, out);
}
int ConvEncoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
ConvDecoder::ConvDecoder(dsp::stream<dsp::complex_t>* in) {
// Create the convolutional encoder instance
conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial);
// Allocate the soft symbol buffer
soft = dsp::buffer::alloc<uint8_t>(STREAM_BUFFER_SIZE);
// Init the base class
base_type::init(in);
}
ConvDecoder::~ConvDecoder() {
// Destroy the convolutional encoder instance
correct_convolutional_destroy(conv);
// Free the soft symbol buffer
dsp::buffer::free(soft);
}
int ConvDecoder::decode(const dsp::complex_t* in, uint8_t* out, int count) {
// Convert to uint8
const float* _in = (const float*)in;
count *= 2;
for (int i = 0; i < count; i++) {
soft[i] = std::clamp<int>((_in[i] * 127.0f) + 128.0f, 0, 255);
}
// Run convolutional decoder on the data
return correct_convolutional_decode_soft(conv, soft, count, out);
}
int ConvDecoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
}

View File

@@ -0,0 +1,71 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "dsp/processor.h"
extern "C" {
#include "correct.h"
}
namespace ryfi {
/**
* RyFi Convolutional Encoder.
*/
class ConvEncoder : public dsp::Processor<uint8_t, uint8_t> {
using base_type = dsp::Processor<uint8_t, uint8_t>;
public:
/**
* Create a convolutional encoder specifying an input stream.
* @param in Input stream.
*/
ConvEncoder(dsp::stream<uint8_t>* in = NULL);
// Destructor
~ConvEncoder();
/**
* Encode data.
* @param in Input bytes.
* @param out Output bits.
* @param count Number of input bytes.
* @return Number of output bits.
*/
int encode(const uint8_t* in, uint8_t* out, int count);
private:
int run();
correct_convolutional* conv;
};
/**
* RyFi Convolutional Decoder.
*/
class ConvDecoder : public dsp::Processor<dsp::complex_t, uint8_t> {
using base_type = dsp::Processor<dsp::complex_t, uint8_t>;
public:
/**
* Create a convolutional encoder specifying an input stream.
* @param in Input stream.
*/
ConvDecoder(dsp::stream<dsp::complex_t>* in = NULL);
// Destructor
~ConvDecoder();
/**
* Decode soft symbols.
* @param in Input soft symbols.
* @param out Output bytes.
* @param count Number of input bytes.
* @return Number of output bits.
*/
int decode(const dsp::complex_t* in, uint8_t* out, int count);
private:
int run();
correct_convolutional* conv;
uint8_t* soft = NULL;
};
}

View File

@@ -0,0 +1,37 @@
#include "frame.h"
namespace ryfi {
int Frame::serialize(uint8_t* bytes) const {
// Write the counter
bytes[0] = (counter >> 8) & 0xFF;
bytes[1] = counter & 0xFF;
// Write the first packet pointer
bytes[2] = (firstPacket >> 8) & 0xFF;
bytes[3] = firstPacket & 0xFF;
// Write the last packet pointer
bytes[4] = (lastPacket >> 8) & 0xFF;
bytes[5] = lastPacket & 0xFF;
// Write the data
memcpy(&bytes[6], content, FRAME_DATA_SIZE);
// Return the length of a serialized frame
return FRAME_SIZE;
}
void Frame::deserialize(const uint8_t* bytes, Frame& frame) {
// Read the counter
frame.counter = (((uint16_t)bytes[0]) << 8) | ((uint16_t)bytes[1]);
// Read the first packet pointer
frame.firstPacket = (((uint16_t)bytes[2]) << 8) | ((uint16_t)bytes[3]);
// Read the last packet pointer
frame.lastPacket = (((uint16_t)bytes[4]) << 8) | ((uint16_t)bytes[5]);
// Read the data
memcpy(frame.content, &bytes[6], FRAME_DATA_SIZE);
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <stdint.h>
#include "rs_codec.h"
namespace ryfi {
enum PacketOffset {
PKT_OFFS_NONE = 0xFFFF
};
struct Frame {
/**
* Serialize the frame to bytes.
* @param bytes Buffer to write the serialized frame to.
*/
int serialize(uint8_t* bytes) const;
/**
* Deserialize a frame from bytes.
* @param bytes Buffer to deserialize the frame from.
* @param frame Object that will contain the deserialize frame.
*/
static void deserialize(const uint8_t* bytes, Frame& frame);
// Size of a serialized frame
static inline const int FRAME_SIZE = RS_BLOCK_DEC_SIZE*RS_BLOCK_COUNT;
// Size of the data area of the frame
static inline const int FRAME_DATA_SIZE = FRAME_SIZE - 6;
// Steadily increasing counter.
uint16_t counter = 0;
// Byte offset of the first packet in the frame.
uint16_t firstPacket = 0;
// Byte offset of the last packet in the frame.
uint16_t lastPacket = 0;
// Data area of the frame.
uint8_t content[FRAME_DATA_SIZE];
};
}

View File

@@ -0,0 +1,137 @@
#include "framing.h"
namespace ryfi {
dsp::complex_t QPSK_SYMBOLS[4] = {
{ -0.070710678118f, -0.070710678118f },
{ -0.070710678118f, 0.070710678118f },
{ 0.070710678118f, -0.070710678118f },
{ 0.070710678118f, 0.070710678118f },
};
Framer::Framer(dsp::stream<uint8_t>* in) {
// Generate the sync symbols
int k = 0;
for (int i = 62; i >= 0; i -= 2) {
syncSyms[k++] = QPSK_SYMBOLS[(SYNC_WORD >> i) & 0b11];
}
// Initialize base class
base_type::init(in);
}
int Framer::encode(const uint8_t* in, dsp::complex_t* out, int count) {
// Copy sync symbols
memcpy(out, syncSyms, SYNC_SYMS*sizeof(dsp::complex_t));
// Modulate the rest of the bits
dsp::complex_t* dataOut = &out[SYNC_SYMS];
int dataSyms = count / 2;
for (int i = 0; i < dataSyms; i++) {
uint8_t bits = (in[i >> 2] >> (6 - 2*(i & 0b11))) & 0b11;
dataOut[i] = QPSK_SYMBOLS[bits];
}
// Compute and return the total number of symbols
return SYNC_SYMS + dataSyms;
}
int Framer::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
Deframer::Deframer(dsp::stream<dsp::complex_t> *in) {
// Compute sync word rotations
// 0: 00 01 11 10
// 90: 10 00 01 11
// 180: 11 10 00 01
// 270: 01 11 10 00
// For 0 and 180 it's the sync and its complement
syncRots[ROT_0_DEG] = SYNC_WORD;
syncRots[ROT_180_DEG] = ~SYNC_WORD;
// For 90 and 270 its the quadrature and its complement
uint64_t quad;
for (int i = 62; i >= 0; i -= 2) {
// Get the symbol
uint8_t sym = (SYNC_WORD >> i) & 0b11;
// Rotate it 90 degrees
uint8_t rsym;
switch (sym) {
case 0b00: rsym = 0b10; break;
case 0b01: rsym = 0b00; break;
case 0b11: rsym = 0b01; break;
case 0b10: rsym = 0b11; break;
}
// Push it into the quadrature
quad = (quad << 2) | rsym;
}
syncRots[ROT_90_DEG] = quad;
syncRots[ROT_270_DEG] = ~quad;
base_type::init(in);
}
int Deframer::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
dsp::complex_t* in = base_type::_in->readBuf;
for (int i = 0; i < count; i++) {
if (recv) {
// Copy the symbol to the output and rotate it approprieate
base_type::out.writeBuf[outCount++] = in[i] * symRot;
// Check if we're done receiving the frame, send it out
if (!(--recv)) {
if (!base_type::out.swap(outCount)) {
base_type::_in->flush();
return -1;
}
}
}
else {
// Get the raw symbol
dsp::complex_t fsym = in[i];
// Decode the symbol
uint8_t sym = ((fsym.re > 0) ? 0b10 : 0b00) | ((fsym.im > 0) ? 0b01 : 0b00);
// Push it to the shift register
shift = (shift << 2) | sym;
// Find the rotation starting with the last known one
for (int i = 0; i < 4; i++) {
// Get the test rotation
int testRot = (knownRot+i) & 0b11;
// Check if the hamming distance is close enough
int dist;
if (distance(shift, syncRots[testRot]) < 6) {
// Save the new rotation
knownRot = testRot;
// Start reading in symbols for the frame
symRot = symRots[knownRot];
recv = 8168; // TODO: Don't hardcode!
outCount = 0;
}
}
}
}
base_type::_in->flush();
return count;
}
}

View File

@@ -0,0 +1,87 @@
#pragma once
#include "dsp/processor.h"
#include <stdint.h>
#include <stddef.h>
namespace ryfi {
// Synchronization word.
inline const uint64_t SYNC_WORD = 0x341CC540819D8963;
// Number of synchronization bits.
inline const int SYNC_BITS = 64;
// Number of synchronization symbols.
inline const int SYNC_SYMS = SYNC_BITS / 2;
// Possible constellation rotations
enum {
ROT_0_DEG = 0,
ROT_90_DEG = 1,
ROT_180_DEG = 2,
ROT_270_DEG = 3
};
/**
* RyFi Framer.
*/
class Framer : public dsp::Processor<uint8_t, dsp::complex_t> {
using base_type = dsp::Processor<uint8_t, dsp::complex_t>;
public:
/**
* Create a framer specifying an input stream.
* @param in Input stream.
*/
Framer(dsp::stream<uint8_t>* in = NULL);
/**
* Encode a frame to symbols adding a sync word.
*/
int encode(const uint8_t* in, dsp::complex_t* out, int count);
private:
int run();
dsp::complex_t syncSyms[SYNC_SYMS];
};
class Deframer : public dsp::Processor<dsp::complex_t, dsp::complex_t> {
using base_type = dsp::Processor<dsp::complex_t, dsp::complex_t>;
public:
/**
* Create a deframer specifying an input stream.
* @param in Input stream.
*/
Deframer(dsp::stream<dsp::complex_t> *in = NULL);
private:
int run();
inline static constexpr int distance(uint64_t a, uint64_t b) {
int dist = 0;
for (int i = 0; i < 64; i++) {
dist += ((a & 1) != (b & 1));
a >>= 1;
b >>= 1;
}
return dist;
}
// Frame reading counters
int recv = 0;
int outCount = 0;
// Rotation handling
int knownRot = 0;
uint64_t syncRots[4];
dsp::complex_t symRot;
const dsp::complex_t symRots[4] = {
{ 1.0f, 0.0f }, // 0 deg
{ 0.0f, -1.0f }, // 90 deg
{ -1.0f, 0.0f }, // 180 deg
{ 0.0f, 1.0f }, // 270 deg
};
// Shift register
uint64_t shift;
};
}

View File

@@ -0,0 +1,126 @@
#include "packet.h"
#include "string.h"
#include <stdexcept>
namespace ryfi {
Packet::Packet() {}
Packet::Packet(uint8_t* content, int size) {
// Check that the size isn't too large
if (size > MAX_CONTENT_SIZE) {
throw std::runtime_error("Content size is too large to fit in a packet");
}
// Allocate the buffer
allocate(size);
// Copy over the content
memcpy(_content, content, size);
}
Packet::Packet(const Packet& b) {
// Reallocate the buffer
allocate(b._size);
// Copy over the content
memcpy(_content, b._content, b._size);
}
Packet::Packet(Packet&& b) {
// Move members
_content = b._content;
_size = b._size;
// Destroy old object
b._content = NULL;
b._size = 0;
}
Packet::~Packet() {
// Delete the content
if (_content) { delete[] _content; }
}
Packet& Packet::operator=(const Packet& b) {
// Reallocate the buffer
allocate(b._size);
// Copy over the content
memcpy(_content, b._content, b._size);
// Return self
return *this;
}
Packet& Packet::operator=(Packet&& b) {
// Move members
_content = b._content;
_size = b._size;
// Destroy old object
b._content = NULL;
b._size = 0;
// Return self
return *this;
}
Packet::operator bool() const {
return _size > 0;
}
int Packet::size() const {
// Return the size
return _size;
}
const uint8_t* Packet::data() const {
// Return the size
return _content;
}
void Packet::setContent(uint8_t* content, int size) {
// Check that the size isn't too large
if (size > MAX_CONTENT_SIZE) {
throw std::runtime_error("Content size is too large to fit in a packet");
}
// Reallocate the buffer
allocate(size);
// Copy over the content
memcpy(_content, content, size);
}
int Packet::serializedSize() const {
// Two size bytes + Size of the content
return _size + 2;
}
int Packet::serialize(uint8_t* bytes) const {
// Write the size in big-endian
bytes[0] = (_size >> 8) & 0xFF;
bytes[1] = _size & 0xFF;
// Copy the content of the packet
memcpy(&bytes[2], _content, _size);
// Return the serialized size
return serializedSize();
}
void Packet::allocate(int newSize) {
// If the size hasn't changed, do nothing
if (newSize == _size) { return; }
// Free the old buffer
if (_content) { delete[] _content; };
// Update the size
_size = newSize;
// Allocate the buffer
_content = new uint8_t[newSize];
}
}

View File

@@ -0,0 +1,89 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
namespace ryfi {
/**
* RyFi Protocol Packet.
*/
class Packet {
public:
// Default constructor
Packet();
/**
* Create a packet from its content.
* @param content Content of the packet.
* @param size Number of bytes of content.
*/
Packet(uint8_t* content, int size);
// Copy constructor
Packet(const Packet& b);
// Move constructor
Packet(Packet&& b);
// Destructor
~Packet();
// Copy assignment operator
Packet& operator=(const Packet& b);
// Move assignment operator
Packet& operator=(Packet&& b);
// Cast to bool operator
operator bool() const;
/**
* Get the size of the content of the packet.
* @return Size of the content of the packet.
*/
int size() const;
/**
* Get the content of the packet. The pointer is only valid until reallocation or deletion.
* @return Content of the packet.
*/
const uint8_t* data() const;
/**
* Set the content of the packet.
* @param content Content of the packet.
* @param size Number of bytes of content.
*/
void setContent(uint8_t* content, int size);
/**
* Get the size of the serialized packet.
* @return Size of the serialized packet.
*/
int serializedSize() const;
/**
* Serialize the packet to bytes.
* @param bytes Buffer to which to write the serialized packet.
* @return Size of the serialized packet.
*/
int serialize(uint8_t* bytes) const;
/**
* Deserialize a packet from bytes.
* TODO
*/
static bool deserialize(uint8_t* bytes, int size, Packet& pkt);
// Maximum size of the content of the packet.
static inline const int MAX_CONTENT_SIZE = 0xFFFF;
// Maximum size of the serialized packet.
static inline const int MAX_SERIALIZED_SIZE = MAX_CONTENT_SIZE + 2;
private:
void allocate(int newSize);
uint8_t* _content = NULL;
int _size = 0;
};
}

View File

@@ -0,0 +1,194 @@
#include "receiver.h"
#include "utils/flog.h"
namespace ryfi {
Receiver::Receiver() {}
Receiver::Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
init(in, baudrate, samplerate);
}
Receiver::~Receiver() {
// Stop everything
stop();
}
void Receiver::init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate) {
// Initialize the DSP
demod.init(in, baudrate, samplerate, 31, 0.6, 0.1f, 0.005f, 1e-6, 0.01);
doubler.init(&demod.out);
softOut = &doubler.outA;
deframer.setInput(&doubler.outB);
conv.setInput(&deframer.out);
rs.setInput(&conv.out);
}
void Receiver::setInput(dsp::stream<dsp::complex_t>* in) {
demod.setInput(in);
}
void Receiver::start() {
// Do nothing if already running
if (running) { return; }
// Start the worker thread
workerThread = std::thread(&Receiver::worker, this);
// Start the DSP
demod.start();
doubler.start();
deframer.start();
conv.start();
rs.start();
// Update the running state
running = true;
}
void Receiver::stop() {
// Do nothing if not running
if (!running) { return; }
// Stop the worker thread
rs.out.stopReader();
if (workerThread.joinable()) { workerThread.join(); }
rs.out.clearReadStop();
// Stop the DSP
demod.stop();
doubler.stop();
deframer.stop();
conv.stop();
rs.stop();
// Update the running state
running = false;
}
void Receiver::worker() {
Frame frame;
uint16_t lastCounter = 0;
uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE];
int pktExpected = 0;
int pktRead = 0;
int valid = 0;
while (true) {
// Read a frame
int count = rs.out.read();
if (count <= 0) { break; }
// Deserialize the frame
Frame::deserialize(rs.out.readBuf, frame);
valid++;
// Flush the stream
rs.out.flush();
//flog::info("Frame[{}]: FirstPacket={}, LastPacket={}", frame.counter, frame.firstPacket, frame.lastPacket);
// Compute the expected frame counter
uint16_t expectedCounter = lastCounter + 1;
lastCounter = frame.counter;
// If the frames aren't consecutive
int frameRead = 0;
if (frame.counter != expectedCounter) {
flog::warn("Lost at least {} frames after {} valid frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000, valid);
// Cancel the partial packet if there was one
pktExpected = 0;
pktRead = 0;
valid = 1;
// If this frame is not an idle frame or continuation frame
if (frame.firstPacket != PKT_OFFS_NONE) {
// If the offset of the first packet is not plausible
if (frame.firstPacket > Frame::FRAME_DATA_SIZE-2) {
flog::warn("Packet had non-plausible offset: {}", frameRead);
// Skip the frame
continue;
}
// Skip to the end of the packet
frameRead = frame.firstPacket;
}
}
// If there is no partial packet and the frame doesn't contain a packet start, skip it
if (!pktExpected && frame.firstPacket == PKT_OFFS_NONE) { continue; }
// Extract packets from the frame
bool firstPacket = true;
bool lastPacket = false;
while (frameRead < Frame::FRAME_DATA_SIZE) {
// If there is a partial packet read as much as possible from it
if (pktExpected) {
// Compute how many bytes of the packet are available in the frame
int readable = std::min<int>(pktExpected - pktRead, Frame::FRAME_DATA_SIZE - frameRead);
//flog::debug("Reading {} bytes", readable);
// Write them to the packet
memcpy(&pktBuffer[pktRead], &frame.content[frameRead], readable);
pktRead += readable;
frameRead += readable;
// If the packet is read entirely
if (pktRead >= pktExpected) {
// Create the packet object
Packet pkt(pktBuffer, pktExpected);
// Send off the packet
onPacket(pkt);
// Prepare for the next packet
pktRead = 0;
pktExpected = 0;
// If this was the last packet of the frame
if (lastPacket || frame.firstPacket == PKT_OFFS_NONE) {
// Skip the rest of the frame
frameRead = Frame::FRAME_DATA_SIZE;
continue;
}
}
// Go to next packet
continue;
}
// If the packet offset is not plausible
if (Frame::FRAME_DATA_SIZE - frameRead < 2) {
flog::warn("Packet had non-plausible offset: {}", frameRead);
// Skip the rest of the frame and the packet
frameRead = Frame::FRAME_DATA_SIZE;
pktExpected = 0;
pktRead = 0;
continue;
}
// If this is the first packet, use the frame info to skip possible left over data
if (firstPacket) {
frameRead = frame.firstPacket;
firstPacket = false;
}
// Check if this is the last packet
lastPacket = (frameRead == frame.lastPacket);
// Parse the packet size
pktExpected = ((uint16_t)frame.content[frameRead]) << 8;
pktExpected |= (uint16_t)frame.content[frameRead+1];
//flog::debug("Starting to read a {} byte packet at offset {}", pktExpected, frameRead);
// Skip to the packet content
frameRead += 2;
}
}
delete[] pktBuffer;
}
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include "utils/new_event.h"
#include "dsp/demod/psk.h"
#include "dsp/routing/doubler.h"
#include "packet.h"
#include "frame.h"
#include "rs_codec.h"
#include "conv_codec.h"
#include "framing.h"
#include <mutex>
namespace ryfi {
class Receiver {
public:
Receiver();
/**
* Create a transmitter.
* @param in Baseband input.
* @param baudrate Baudrate to use over the air.
* @param samplerate Samplerate of the baseband.
*/
Receiver(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
/**
* Create a transmitter.
* @param in Baseband input.
* @param baudrate Baudrate to use over the air.
* @param samplerate Samplerate of the baseband.
*/
void init(dsp::stream<dsp::complex_t>* in, double baudrate, double samplerate);
/**
* Set the input stream.
* @param in Baseband input.
*/
void setInput(dsp::stream<dsp::complex_t>* in);
// Destructor
~Receiver();
/**
* Start the transmitter's DSP.
*/
void start();
/**
* Stop the transmitter's DSP.
*/
void stop();
dsp::stream<dsp::complex_t>* softOut;
NewEvent<Packet> onPacket;
private:
void worker();
// DSP
dsp::demod::PSK<4> demod;
dsp::routing::Doubler<dsp::complex_t> doubler;
Deframer deframer;
ConvDecoder conv;
RSDecoder rs;
bool running = false;
std::thread workerThread;
};
}

View File

@@ -0,0 +1,169 @@
#include "rs_codec.h"
namespace ryfi {
RSEncoder::RSEncoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
// Init the base class
base_type::init(in);
}
RSEncoder::~RSEncoder() {
// Destroy the convolutional encoder instance
correct_reed_solomon_destroy(rs);
}
int RSEncoder::encode(const uint8_t* in, uint8_t* out, int count) {
// Check the size
assert(count == RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE);
// Go through each block
uint8_t block[RS_BLOCK_ENC_SIZE];
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
// Encode block
correct_reed_solomon_encode(rs, &in[i*RS_BLOCK_DEC_SIZE], RS_BLOCK_DEC_SIZE, block);
// Interleave into the frame
int k = 0;
for (int j = i; j < RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT; j += RS_BLOCK_COUNT) {
out[j] = block[k++];
}
}
// Scramble
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
out[i] ^= RS_SCRAMBLER_SEQ[i];
}
return RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE;
}
int RSEncoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (!out.swap(count)) { return -1; }
return count;
}
RSDecoder::RSDecoder(dsp::stream<uint8_t>* in) {
// Create the convolutional encoder instance
rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32);
// Init the base class
base_type::init(in);
}
RSDecoder::~RSDecoder() {
// Destroy the convolutional encoder instance
correct_reed_solomon_destroy(rs);
}
int RSDecoder::decode(uint8_t* in, uint8_t* out, int count) {
// Check the size
assert(count == RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE);
// Descramble (TODO: Don't do it in-place)
for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) {
in[i] ^= RS_SCRAMBLER_SEQ[i];
}
// Go through each block
uint8_t block[RS_BLOCK_ENC_SIZE];
for (int i = 0; i < RS_BLOCK_COUNT; i++) {
// Deinterleave out of the frame
int k = 0;
for (int j = i; j < count; j += RS_BLOCK_COUNT) {
block[k++] = in[j];
}
// Decode block and return if decoding fails
int res = correct_reed_solomon_decode(rs, block, RS_BLOCK_ENC_SIZE, &out[i*RS_BLOCK_DEC_SIZE]);
if (res < 0) { return 0; }
}
return RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE;
}
int RSDecoder::run() {
int count = base_type::_in->read();
if (count < 0) { return -1; }
count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count);
base_type::_in->flush();
if (count && !out.swap(count)) { return -1; }
return count;
}
const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT] = {
0x75, 0x05, 0x7C, 0xCE, 0xF1, 0xD0, 0x6C, 0xF6, 0xFA, 0x65, 0xF6, 0xFC, 0xE0, 0x0A, 0x82, 0x17,
0x6C, 0xBE, 0x76, 0xA0, 0xD6, 0x46, 0x12, 0x2E, 0xDE, 0xB5, 0xF7, 0xAD, 0xCB, 0x51, 0x63, 0x47,
0x27, 0x30, 0x7E, 0x43, 0xD1, 0xA1, 0xCB, 0x10, 0x08, 0x49, 0xDF, 0x86, 0xD4, 0xC4, 0xD7, 0x3C,
0x6D, 0x03, 0x07, 0x37, 0x5B, 0xB3, 0xCD, 0x79, 0x6F, 0x1E, 0xBA, 0xC5, 0x6E, 0xC3, 0x8C, 0x7A,
0x25, 0x99, 0x61, 0x54, 0x5A, 0x96, 0x57, 0x9B, 0xE0, 0x60, 0x5B, 0x09, 0x6D, 0x8B, 0x2D, 0x9D,
0x15, 0x9D, 0x0E, 0xBF, 0x57, 0xFB, 0x9C, 0x49, 0x82, 0x2C, 0x48, 0x59, 0x92, 0x47, 0x79, 0x17,
0x16, 0x74, 0xEA, 0xEA, 0xBB, 0xC5, 0x72, 0x32, 0x17, 0xD1, 0xB3, 0xDE, 0xEB, 0x15, 0xC7, 0x55,
0x8A, 0xF2, 0x88, 0xC2, 0x33, 0xA6, 0x17, 0x8B, 0xD4, 0x77, 0x22, 0x00, 0x63, 0x47, 0x45, 0x5F,
0x36, 0x35, 0x58, 0x8B, 0x88, 0xEC, 0xCA, 0xC4, 0x60, 0x53, 0x9E, 0xBD, 0xB2, 0xF5, 0x51, 0x46,
0x34, 0x9A, 0x07, 0x25, 0x3F, 0xF5, 0x65, 0x63, 0x77, 0x3C, 0x5A, 0xFA, 0x4E, 0x0C, 0xF7, 0x1B,
0x82, 0xAB, 0x73, 0x06, 0x7F, 0xB7, 0xC6, 0x6B, 0xBF, 0xB1, 0x46, 0xF3, 0x01, 0x91, 0xB1, 0xFF,
0x5C, 0x6F, 0xF9, 0x43, 0x0E, 0x6A, 0x70, 0x89, 0x0B, 0xEA, 0x8C, 0xD4, 0x1B, 0x51, 0x01, 0x31,
0x71, 0x2E, 0xDF, 0x24, 0xC1, 0xD5, 0xDB, 0x0E, 0xF5, 0xEB, 0x78, 0x79, 0x39, 0x5B, 0xAD, 0xC3,
0xA9, 0xA6, 0x60, 0x30, 0xA2, 0x9A, 0x7B, 0xA0, 0xF4, 0xAA, 0xC5, 0x57, 0xB3, 0x16, 0xF9, 0xB5,
0x79, 0x20, 0xC1, 0x88, 0x9A, 0x00, 0x43, 0xB2, 0xC6, 0x84, 0x8D, 0x03, 0xF2, 0xD8, 0x90, 0x7A,
0x21, 0x37, 0x7E, 0xF7, 0x75, 0xE5, 0xFB, 0xC9, 0xDC, 0xAB, 0x4B, 0xBC, 0x35, 0x38, 0xB9, 0x3A,
0x53, 0x89, 0x7E, 0xD5, 0x94, 0x12, 0x2D, 0x9B, 0x91, 0x90, 0x1D, 0x4D, 0x0E, 0xE0, 0x93, 0xF3,
0xC1, 0xA1, 0x9B, 0x73, 0x27, 0x22, 0x41, 0x27, 0xEE, 0x2A, 0xD7, 0x45, 0xBC, 0x8F, 0x9B, 0xA2,
0x36, 0x11, 0x16, 0x37, 0x1A, 0xF1, 0x2E, 0x71, 0xCF, 0x86, 0x89, 0x83, 0x5A, 0xF1, 0x24, 0x6C,
0x56, 0x71, 0x53, 0xE4, 0xD2, 0xCB, 0xCA, 0x86, 0x1E, 0xA0, 0xD5, 0x83, 0x3B, 0xEF, 0x09, 0x09,
0xC2, 0x07, 0x53, 0x86, 0xE6, 0x8A, 0xC6, 0x70, 0xFB, 0x91, 0x43, 0xCB, 0x91, 0x6E, 0xA9, 0xBC,
0x31, 0x42, 0x61, 0x0C, 0x88, 0xB8, 0x2C, 0xED, 0xD8, 0xE6, 0xA3, 0xEC, 0xAC, 0xB9, 0x45, 0x5E,
0x2C, 0x73, 0x3F, 0x2E, 0x06, 0xE0, 0xBF, 0x73, 0xDD, 0x2E, 0x45, 0x50, 0x6C, 0x53, 0x55, 0xF0,
0x7F, 0x6E, 0x61, 0xFA, 0xA0, 0x7A, 0x1C, 0xF0, 0xBD, 0xAC, 0x48, 0x61, 0x03, 0x6B, 0xED, 0x54,
0x2A, 0x27, 0x94, 0xF6, 0xF9, 0x6A, 0x04, 0x08, 0x0B, 0x3C, 0xC3, 0x30, 0x66, 0x01, 0xFB, 0xDC,
0xC9, 0x65, 0x03, 0x83, 0x7D, 0x0A, 0xDF, 0xA5, 0x04, 0x14, 0xE4, 0xF2, 0x4C, 0x01, 0xDF, 0x04,
0xD2, 0x80, 0xB9, 0x9B, 0xD9, 0x5E, 0xF8, 0x2A, 0x93, 0x8D, 0x8C, 0x09, 0x9B, 0x38, 0xEC, 0x3B,
0xC4, 0x29, 0x90, 0x7C, 0x65, 0x3A, 0xF2, 0x4B, 0x69, 0xD3, 0x63, 0x9B, 0x40, 0x95, 0xC3, 0xFB,
0x67, 0x54, 0x40, 0x9B, 0x26, 0x9F, 0x52, 0xFE, 0xD8, 0xD0, 0x24, 0x9C, 0x5C, 0xD4, 0xEF, 0xDE,
0x28, 0x66, 0x75, 0x04, 0xCB, 0xA4, 0xC0, 0xB9, 0x4B, 0xC9, 0x20, 0x4B, 0x56, 0xC7, 0x86, 0xC5,
0x39, 0x45, 0x18, 0xA7, 0x48, 0x14, 0x1A, 0x51, 0xCA, 0xD0, 0xC0, 0x15, 0xDD, 0xC1, 0x28, 0x4A,
0x7A, 0xD2, 0x10, 0xEA, 0x83, 0xD3, 0x3A, 0xEF, 0x48, 0x29, 0x41, 0xA4, 0xD4, 0x57, 0xA6, 0x1D,
0x76, 0x24, 0x93, 0x58, 0x7E, 0xB7, 0xDD, 0x0B, 0xF2, 0xCE, 0x71, 0x55, 0xF5, 0xAB, 0x8C, 0xC8,
0x70, 0x59, 0x73, 0x69, 0x9D, 0x29, 0x5E, 0x59, 0xF4, 0xB2, 0xC4, 0x97, 0x75, 0xF0, 0x65, 0x1B,
0x66, 0x5F, 0xA4, 0x33, 0x5C, 0xC7, 0xBF, 0x45, 0xE6, 0x20, 0xC0, 0xBD, 0xAD, 0xAE, 0x9F, 0x97,
0x05, 0xD8, 0x04, 0x2B, 0x0A, 0x46, 0xE8, 0xB8, 0xCB, 0x00, 0xE2, 0x7C, 0x70, 0x1B, 0x49, 0xDE,
0x81, 0xEB, 0x24, 0xAC, 0x1B, 0x3E, 0x09, 0xFB, 0xAC, 0xB7, 0xF2, 0xD1, 0xB2, 0x78, 0xF3, 0xAC,
0xC7, 0x6A, 0xA2, 0x07, 0x4C, 0xED, 0x61, 0xAD, 0x04, 0x7F, 0x45, 0x83, 0x59, 0x31, 0x27, 0xF0,
0x16, 0x6B, 0x0C, 0xAA, 0xD4, 0xD1, 0xCB, 0x1C, 0x51, 0x41, 0x0D, 0x2F, 0x8F, 0xF9, 0xF9, 0x7F,
0x22, 0x89, 0x46, 0xF4, 0xB8, 0x93, 0x98, 0x9E, 0x3E, 0x23, 0xF1, 0x6E, 0x64, 0x08, 0xB6, 0xC9,
0x6E, 0x53, 0x53, 0xED, 0xAD, 0x21, 0xCD, 0x1A, 0xF0, 0x45, 0xFC, 0x14, 0x00, 0xEA, 0xF7, 0x42,
0xEE, 0xDA, 0x58, 0x0D, 0x85, 0xBC, 0x74, 0xFB, 0x73, 0x78, 0xB5, 0x5E, 0x5E, 0x6F, 0x6F, 0x7E,
0x39, 0xC2, 0x05, 0x50, 0xDB, 0x3D, 0xB8, 0xF3, 0x8F, 0x80, 0xEC, 0x46, 0x29, 0x39, 0x89, 0xF3,
0x55, 0x9C, 0x6A, 0x5F, 0x7C, 0xD9, 0x7C, 0x13, 0xE4, 0x56, 0x5E, 0xE9, 0x60, 0x19, 0xE2, 0x7D,
0xC4, 0x41, 0x92, 0x8D, 0xDA, 0x21, 0x58, 0x20, 0xE9, 0xA8, 0x4C, 0x16, 0x34, 0x99, 0xAC, 0xB7,
0x30, 0xBD, 0x39, 0x19, 0xAC, 0x9B, 0x4B, 0x27, 0xFA, 0x32, 0xC1, 0x48, 0xA1, 0x80, 0x34, 0x36,
0x1E, 0xFB, 0x92, 0x43, 0x35, 0x72, 0x2D, 0xEF, 0xD2, 0xF2, 0xFC, 0xC2, 0x85, 0xAB, 0x59, 0x40,
0x8D, 0x9D, 0x1A, 0x1F, 0xE2, 0x92, 0x87, 0xA2, 0xF9, 0x2C, 0x78, 0xE4, 0xC3, 0x26, 0x56, 0x07,
0xB3, 0x78, 0xAF, 0x79, 0x3D, 0x88, 0xF4, 0xAD, 0x66, 0x7C, 0x07, 0x58, 0x98, 0x82, 0x1A, 0x26,
0xF7, 0xFD, 0xCE, 0xFF, 0x75, 0xED, 0xAB, 0xBD, 0xAE, 0x6D, 0x5C, 0x28, 0x91, 0xF3, 0xB7, 0x5C,
0x27, 0x05, 0xEC, 0x3B, 0xE3, 0xDD, 0x93, 0x24, 0x7F, 0xAD, 0x14, 0xAA, 0x49, 0x61, 0x8F, 0x96,
0x1F, 0xAA, 0xB2, 0xEE, 0xA8, 0x24, 0x41, 0x7C, 0xDC, 0xF1, 0x28, 0x26, 0xE6, 0x7F, 0x98, 0x20,
0x50, 0x5F, 0x90, 0x21, 0x8A, 0x09, 0x26, 0x59, 0xD0, 0x07, 0x2F, 0xE1, 0x35, 0x4D, 0x0B, 0x20,
0xB2, 0xD5, 0xDD, 0xB5, 0xAC, 0x1B, 0xFE, 0xD9, 0xE3, 0x35, 0xF1, 0xB8, 0x3F, 0x3D, 0xFC, 0x0B,
0x5A, 0x57, 0xA9, 0x92, 0x2B, 0xC8, 0x3E, 0xC2, 0xAA, 0xEF, 0xB9, 0x98, 0x2C, 0xA8, 0xAB, 0xF6,
0xA1, 0xBF, 0xBC, 0x8D, 0x97, 0xA2, 0x74, 0xD9, 0xE5, 0x99, 0x85, 0x81, 0x15, 0xB0, 0xE7, 0x8B,
0x48, 0x86, 0xF4, 0x94, 0x9C, 0x62, 0x82, 0xD1, 0x2C, 0x24, 0x4B, 0xAC, 0x7A, 0xB8, 0x4E, 0x4A,
0xD2, 0xF6, 0xAA, 0xED, 0xE0, 0x9C, 0x98, 0xD2, 0xDF, 0xC1, 0xBC, 0xBF, 0x55, 0x7D, 0x40, 0xB5,
0xDE, 0xD4, 0x25, 0xBB, 0x81, 0xF4, 0x07, 0x1D, 0xE7, 0x3C, 0xB4, 0x62, 0xC9, 0x55, 0x0A, 0x3A,
0xD5, 0xCE, 0x97, 0xED, 0x30, 0x76, 0x76, 0x51, 0xBC, 0x8C, 0xE4, 0x54, 0xBE, 0xB7, 0xB5, 0xCD,
0xF8, 0x76, 0x37, 0x53, 0x2C, 0x9F, 0xE4, 0xC7, 0xEB, 0xF5, 0x8D, 0x23, 0x8A, 0xDA, 0xD1, 0xA9,
0xD8, 0x4C, 0x53, 0xF3, 0x49, 0xA7, 0x1A, 0x5D, 0xE5, 0x03, 0x49, 0x52, 0xD3, 0xE2, 0x1F, 0xA5,
0x35, 0x9C, 0xBB, 0x0B, 0xC7, 0x0D, 0xA4, 0x65, 0x54, 0x8B, 0x39, 0xF1, 0x3B, 0x67, 0x21, 0x71,
0x10, 0xE7, 0x76, 0xC4, 0xA8, 0xC2, 0x9D, 0x93, 0xC6, 0x51, 0xBA, 0x23
};
}

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