234 Commits

Author SHA1 Message Date
330598ddf2 use check implemented in python 2021-09-29 17:23:23 +02:00
8f36355817 update changelog 2021-09-29 15:43:48 +02:00
ccf741da00 add nmux back to recommended packages 2021-09-29 15:42:49 +02:00
c3917c61d3 fix audio compression switching 2021-09-28 17:46:18 +02:00
d869c5ee2d restore live switching capability for fft compression 2021-09-28 16:55:17 +02:00
c89394822a Merge branch 'develop' into pycsdr 2021-09-28 16:44:50 +02:00
1836344fab update to wsjt-x 2.5.0 2021-09-28 16:42:52 +02:00
25e2a8013e parser typing 2021-09-28 00:27:01 +02:00
757ec01ea4 clientReader is not used any more 2021-09-28 00:14:53 +02:00
a07480fd9a remove old csdr code 2021-09-27 18:53:49 +02:00
e77b0f4a67 fix the secondary fft display 2021-09-27 18:18:31 +02:00
909a969e04 restore wfm deemphasis tau functionality 2021-09-27 17:46:19 +02:00
edace3d451 work on some todos 2021-09-27 17:29:51 +02:00
5b1000df87 Merge branch 'develop' into pycsdr 2021-09-24 23:08:37 +02:00
fae281a507 update codecserver in docker 2021-09-24 22:34:53 +02:00
cbcba5807f restore PSK decoding 2021-09-23 18:43:41 +02:00
3fa3aac766 introduce defaults layer to fix codecserver when empty 2021-09-23 15:17:46 +02:00
981948b708 update components in docker build 2021-09-22 18:15:47 +02:00
c41b303130 update dependencies 2021-09-22 17:22:00 +02:00
6589c9dbe1 add a feature check for js8py since it's optional now 2021-09-22 13:11:27 +02:00
acc70b6449 re-implement format conversion with pycsdr 2021-09-20 18:36:24 +02:00
81925986a6 update dependencies 2021-09-20 18:04:24 +02:00
83d01553e3 use "Optional" in typing 2021-09-20 17:24:10 +02:00
f2a97415b9 more localized imports 2021-09-20 16:55:17 +02:00
95b4510c3a more localized imports 2021-09-20 16:53:00 +02:00
81ed1a9ebb abstract chain features; use local imports to avoid hard dependencies 2021-09-20 16:14:23 +02:00
b2e15c559e refactor 2021-09-20 15:32:26 +02:00
c10fdd2a53 move 2021-09-20 15:16:06 +02:00
9efe41a2b1 move the pump mechanism, allowing the old output code to be removed 2021-09-20 15:09:26 +02:00
4b36aca6fc update wording to direct users to the feature report 2021-09-20 14:45:00 +02:00
cb29fc251c update dependencies 2021-09-17 18:58:48 +02:00
78dcdd5715 add support for DMR locations 2021-09-17 18:24:33 +02:00
6fbe6b4983 restore automatic config application for direwolf 2021-09-15 15:37:09 +02:00
284059a920 clean up direwolf config file after use 2021-09-15 15:04:12 +02:00
0403ebff5c improve handling of source processes 2021-09-15 15:03:11 +02:00
6129b92277 avoid duplicate method 2021-09-15 15:01:36 +02:00
1ff3c174c2 limit freedv to 4kHz since it's audio input is only 8kHz 2021-09-13 16:58:45 +02:00
e5b120311d get freedv back by modeling a corresponding module and chain 2021-09-13 16:58:02 +02:00
1c937e147e use a generic unpickler 2021-09-13 00:14:38 +02:00
1d2ee127e0 Merge branch 'develop' into pycsdr 2021-09-12 23:31:44 +02:00
ee9b602e4f catch http errors 2021-09-12 23:31:33 +02:00
72f925e537 receive pocsag messages in pickled form 2021-09-10 15:38:36 +02:00
bf37dee78b receive metadata in pickled form 2021-09-09 22:25:45 +02:00
72920135e9 fix initial setup of dial frequency 2021-09-09 22:24:41 +02:00
66cf940523 refactor the metaparser into a modules and use accordingly 2021-09-09 15:11:33 +02:00
ca0f7af1d0 use stereo downmix; disable squelch for DRM 2021-09-08 13:48:11 +02:00
d9db74e565 fix reading from process 2021-09-08 13:47:46 +02:00
3218e0b8aa update hpsdrconnector to 0.6.0 2021-09-08 12:54:34 +02:00
c8ebbb505a restore dmr filter 2021-09-07 17:37:32 +02:00
9ca5e0ebd6 restore DRM functionality 2021-09-07 17:31:32 +02:00
f3b05c6318 re-add m17 2021-09-07 14:45:52 +02:00
f9f0bdde12 restore js8 functionality 2021-09-06 22:50:57 +02:00
6014ce8921 restore pocsag functionality 2021-09-06 20:00:14 +02:00
b9f43654cd restore aprs functionality 2021-09-06 15:05:33 +02:00
7c43c78c4b refactor aprs stuff 2021-09-02 11:00:57 +02:00
efa7faaa2a correctly shutdown resampler 2021-09-02 10:53:05 +02:00
f9df35ffd4 rebuilt the resampler using pycsdr 2021-09-01 15:58:39 +02:00
01260d66c8 create a base class for python-implemented modules 2021-09-01 15:08:28 +02:00
51453662e2 fix dial frequencies 2021-08-31 22:46:11 +02:00
120328ce12 restore background services 2021-08-31 21:53:15 +02:00
869f971ced add the remaining modes 2021-08-31 17:01:52 +02:00
73d326037c restore audio chopper decoding 2021-08-31 16:54:37 +02:00
4a4901fa38 restore secondary fft 2021-08-28 00:10:46 +02:00
47e78579d4 handle unparseable utf meta data 2021-08-27 18:31:10 +02:00
54a1cae352 fix hd audio 2021-08-27 18:30:46 +02:00
4c1777dc19 refactor 2021-08-27 17:34:48 +02:00
42b315ef86 handle empty converter chain 2021-08-27 16:11:03 +02:00
3bb4f48faf fix errors on shutdown (duplicate calls) 2021-08-26 17:22:10 +02:00
ee3d934529 fix thread leak 2021-08-26 17:21:52 +02:00
aecb79a4d4 restore demodulation of digital voice modes 2021-08-26 15:58:02 +02:00
5032f4b66d first steps at rewiring the dsp stuff 2021-08-23 14:25:28 +02:00
0f1feb9d47 return to the simpler API 2021-08-16 16:41:18 +02:00
be6f533437 re-structure client audio conversion 2021-08-12 18:01:03 +02:00
c3d393252b parse metadata as UTF-8 2021-08-12 16:51:21 +02:00
bb56eb8db2 don't highlight for data 2021-08-11 14:10:52 +02:00
1e8527da68 add YSF chain; re-introduce RRC filters 2021-08-10 14:03:49 +02:00
2b3123c7cb dmr tdma slot filter control 2021-08-07 00:09:40 +02:00
8e945d4149 update metadata asynchronously when download finishes 2021-08-06 21:23:44 +02:00
3ccb4a11d2 add new DMR chain 2021-08-06 20:02:59 +02:00
dd7255a9d2 display talker alias (if no radioid data is available) 2021-08-06 20:02:30 +02:00
307e944911 split metadata into lines (if more than one was received) 2021-08-06 20:01:35 +02:00
175e140f86 Merge branch 'develop' into pycsdr 2021-08-04 00:01:11 +02:00
ad59b1b3b7 add codecserver help text 2021-08-03 19:52:49 +02:00
5256409ddf update m17-cxx-demod to 2.2 in docker 2021-08-03 19:51:03 +02:00
65950565b6 bump develop to next version (1.2.0) 2021-08-03 15:57:36 +02:00
66492ff40a make sure we send all the data 2021-08-03 15:03:20 +02:00
52df289230 update tools to release tags 2021-08-02 21:54:11 +02:00
1845fa3f39 prepare release 1.1.0 2021-08-02 18:26:16 +02:00
fa49e59200 increase required connector version to 0.5 (0.4 would work but cannot be
parsed)
2021-08-02 18:11:45 +02:00
d0d3e67174 add missing digiham components 2021-08-02 17:39:18 +02:00
c5a314810e read metadata from pipeline decoders 2021-08-01 00:49:20 +02:00
f8f2740c77 implement nxdn chain using new digiham components 2021-07-31 00:10:10 +02:00
1c91c6dcc1 start building digiham chains 2021-07-30 00:06:21 +02:00
11a3606070 use the new cutoff parameter to compensate the fractional decimator 2021-07-25 23:38:24 +02:00
75aac5969a implement WFM with the new chain elements 2021-07-25 22:44:53 +02:00
459a99cbf8 backport the sync implementation from the csdr++ branch 2021-07-25 20:06:14 +02:00
c07d9ecf92 use the right rates 2021-07-25 19:36:03 +02:00
99c7093a1a pack the client audio processing into its own chain 2021-07-25 19:31:56 +02:00
223c2d1709 BufferReader won't return bytes 2021-07-25 17:36:32 +02:00
6db80ec51a clarify s-meter interval calculations 2021-07-25 00:17:27 +02:00
207ada70fd restore s-meter display 2021-07-25 00:05:48 +02:00
c50da15bfd apply all decimation in comples to simplify the chain 2021-07-24 22:25:41 +02:00
ab99b8e476 don't wrap the module, it's not necessary 2021-07-24 22:11:41 +02:00
7d7cec1ec3 update to match pycsdr chaanges 2021-07-24 18:50:30 +02:00
de14fa4b93 don't compile the tests (they're optional by now) 2021-07-23 11:44:14 +02:00
355b47760c update m17 in docker to v2.0 2021-07-23 10:52:21 +02:00
aeca8265c3 fine-tune agc 2021-07-20 17:58:32 +02:00
b242f09d5d Merge branch 'develop' into pycsdr 2021-07-20 13:33:52 +02:00
ad396fa970 remove "unvoiced quality" setting 2021-07-20 13:33:26 +02:00
2bcb62e706 add ssb chain 2021-07-20 00:57:43 +02:00
be093b8b05 implement a method to replace chain members 2021-07-20 00:44:41 +02:00
eb76ec4a9f add am demodulator chain 2021-07-19 23:32:03 +02:00
f03a6c127e fix initial demodulator parameters 2021-07-19 19:48:18 +02:00
5bb14a8997 first working nfm chain using pycsdr 2021-07-19 19:04:14 +02:00
bb77d2ce0a fix subscription 2021-07-18 14:57:50 +02:00
8531d5e4ab properly shutdown and unblock the final buffer 2021-07-18 14:56:48 +02:00
320521a74a adopt to updated api 2021-07-16 16:12:16 +02:00
5e7a0a38aa Merge branch 'develop' into pycsdr 2021-07-15 18:09:39 +02:00
e6dd1e0fde disable squelch for DRM, too 2021-07-15 12:54:21 +02:00
0277ae8722 fix plutosdr soapy module url 2021-07-15 12:53:48 +02:00
12c032112b handle errors caused by values that don't fit into json 2021-07-09 13:52:59 +02:00
4e61ed3645 handle errors while parsing dprs data 2021-07-09 13:52:33 +02:00
6a59369c62 update remote device input field behaviour 2021-07-05 12:58:16 +02:00
0039d5fdcb update codecserver in docker 2021-07-04 16:13:45 +02:00
40075c1adb check if id contained in radioid data matches request 2021-06-28 13:04:47 +02:00
7a4ed3b383 update path accordingly 2021-06-19 20:23:26 +02:00
2479c2207a update soapysdrplay3 2021-06-19 20:16:21 +02:00
48eb754170 collapse empty meta lines 2021-06-18 09:31:02 +02:00
ddcdd550fd update dependencies 2021-06-17 15:01:10 +02:00
a48a5e366b improve variable usage 2021-06-17 14:58:16 +02:00
8b34e6c689 remove dsd 2021-06-17 14:57:59 +02:00
e71cd01522 always update to ensure removal of old state 2021-06-17 14:57:24 +02:00
2ecefcecd5 update dependency handling for nxdn 2021-06-17 14:13:17 +02:00
5d8fd9ae95 update codecserver and digiham in docker 2021-06-15 23:09:32 +02:00
f5c2525f22 switch NXDN to use digiham decoder; add meta panel 2021-06-15 22:50:30 +02:00
34065e455f parse NMEA coordinates from metadata 2021-06-14 23:39:18 +02:00
b142233d4e report as DPRS on the map 2021-06-11 16:43:28 +02:00
e9b2007863 fix DPRS parsing and display 2021-06-11 14:36:11 +02:00
455b2ce1f1 adapt D-Star terminology in the labels 2021-06-09 23:28:07 +02:00
d0ee6f7d3e update changelogs 2021-06-08 23:08:06 +02:00
5b0aa274eb increase required digiham version to 0.5 2021-06-08 23:06:25 +02:00
234cbf0fa8 update dependency system to use digiham for d-star 2021-06-08 23:01:49 +02:00
4fd5a62980 fix flexbox display of metadata panels 2021-06-08 23:01:03 +02:00
ba97f76737 add parsing of DPMR data 2021-06-08 18:38:53 +02:00
f3d1084b60 make the location icon less jumpy 2021-06-08 17:55:56 +02:00
6c2ba7bc1d only work with header field if sync is available 2021-06-08 14:05:17 +02:00
2be58503c6 adapt protocol / mode string 2021-06-08 14:04:54 +02:00
17a78ffa79 fix typo 2021-06-08 14:04:28 +02:00
322582d29b add dstar metadata panel 2021-06-08 13:37:13 +02:00
5fd303f4a2 replace dsd with dstar_decoder from the digiham package 2021-06-08 13:36:08 +02:00
46d7fa7347 improve error message 2021-06-03 15:23:28 +02:00
845f937fa3 make property deletions evaluate to false for convenience 2021-06-01 11:37:51 +02:00
85a58eefa9 update codecserver and digiham 2021-05-31 21:06:13 +02:00
8923b90b3e check if AMBE is available at codecserver 2021-05-31 20:41:37 +02:00
a9d9206d2e update WSJT-X to 2.4.0 2021-05-31 19:54:11 +02:00
a374e93ee8 replace mbelib with codecserver in docker 2021-05-31 18:40:45 +02:00
bcf05e00f7 actually, digiham should recommend the codecserver 2021-05-31 00:44:52 +02:00
b35d1908c7 fix user in postinst script 2021-05-31 00:44:28 +02:00
d824cc375e add codecserver as a recommended dependency 2021-05-30 23:38:58 +02:00
d04ab43977 add ability to configure codecserver 2021-05-29 18:50:17 +02:00
9cd730dc9a allow digiham binaries to return their own name 2021-05-28 00:02:20 +02:00
a16ad952c4 change digiham / ambe integration 2021-05-27 21:35:55 +02:00
3f7a93acfc drop the scheduler from the config if it's empty 2021-05-19 16:02:44 +02:00
bdf1ed4709 update changelogs 2021-05-19 15:41:04 +02:00
ebc935c1a9 improve message 2021-05-18 20:46:33 +02:00
5b92c317c1 improve connection timeout handling 2021-05-18 20:44:05 +02:00
48dc75c728 improve handshake handling 2021-05-18 16:00:15 +02:00
3e7eb09f3e introduce a websocket handler interface 2021-05-18 15:42:30 +02:00
9baebf444d update connectors in docker 2021-05-18 00:34:06 +02:00
83feb2c0e0 parse individual connector versions 2021-05-17 23:57:37 +02:00
33a942707c allow slots to be off in the daylight scheduler 2021-05-17 23:23:25 +02:00
e206b83e74 move the error overlay so it doesn't block the title menu buttons 2021-05-17 21:14:00 +02:00
970be58e9c invent a new icon for continuous auto waterfall mode 2021-05-17 20:25:25 +02:00
2e326573d0 sync favicons 2021-05-17 17:19:12 +02:00
49a069f0ee add more icon options for other weird browsers 2021-05-17 17:14:14 +02:00
a580989639 update favicon 2021-05-17 16:47:00 +02:00
c46b3275a9 allow negative frequencies in exponential display, closes #247 2021-05-17 15:08:44 +02:00
0258a75650 replace meta panel images with svg (inlining does not work due to
filters)
2021-05-16 22:47:35 +02:00
979f11f40a move play button to svg-defs 2021-05-16 17:47:02 +02:00
a04f198ade inline the google maps pin svg 2021-05-16 17:30:34 +02:00
8a54ef4cd0 clean up
* remove sprites and corresponding styles
* remove base pngs
* remove other unused images
2021-05-16 16:10:00 +02:00
7ec592ce3d replace up and down arrows with svgs 2021-05-16 15:59:21 +02:00
6e0e271294 replace bookmark button with svg 2021-05-16 01:06:57 +02:00
7427a65f18 replace edit icon with svg 2021-05-16 00:56:50 +02:00
39d49ca991 replace trashcan icon with svg 2021-05-16 00:39:53 +02:00
31a30532a7 replace waterfall default button with svg 2021-05-16 00:22:11 +02:00
2190fd7c5a modified inkscape, too 2021-05-16 00:08:21 +02:00
981053a7c7 correct opacity 2021-05-16 00:07:30 +02:00
2d8ae33542 replace squelch button with svg 2021-05-15 23:57:24 +02:00
ced6153aa7 replace waterfall auto button with svg 2021-05-15 23:40:53 +02:00
4a1676bb81 replace speaker icons with svg 2021-05-15 23:02:06 +02:00
6af115f4f0 replace top logo with svg 2021-05-15 21:42:04 +02:00
8550f10d88 allow svg to be gzipped 2021-05-14 23:10:17 +02:00
3b8961c8c6 replace zoom in / out total with svg 2021-05-14 23:01:27 +02:00
eb55167add remove the prefix 2021-05-14 22:31:23 +02:00
ae8061ee77 replace zoom in and out icons with svg 2021-05-14 20:00:07 +02:00
4f0d4983ca introduce http timeout; single router instance 2021-05-14 18:36:30 +02:00
d34ac58e73 use placeholder for device and profile in tabs as well 2021-05-14 16:00:29 +02:00
ee8688345e display a placeholder if device name is empty 2021-05-14 15:52:18 +02:00
4aba612760 replace settings icon with svg 2021-05-14 01:30:59 +02:00
e709ca0e77 replace map icon with svg 2021-05-13 18:38:02 +02:00
6bd3fdf6f4 replace receiver icon with svg 2021-05-13 15:46:55 +02:00
4b969fa3b2 Merge pull request #235 from jancona/hpsdr_config
Set proper config options for HPSDR connector
2021-05-12 21:13:37 +02:00
1020c9bac9 improve form validation
* don't ingore errors in optional fields
* don't attempt parsing if key is not present in upload
* force display of fields with errors
2021-05-12 16:22:45 +02:00
002827cbf4 move openwebrx-panel-status to svg 2021-05-12 00:43:01 +02:00
a676e203c7 first svg for openwebrx-panel-log 2021-05-11 23:36:30 +02:00
87b9a52fcb Don't filter inputs, add a validator for RF Gain 2021-05-11 11:21:52 -04:00
2d2f9bed40 add empty __init__.py (PEP420 doesn't always work...) 2021-05-11 14:28:35 +02:00
e37bc0573d Set proper config options for HPSDR connector 2021-05-10 20:35:49 -04:00
301b3b59a5 fix linter issues 2021-05-10 23:02:43 +02:00
40c78940ef don't close twice 2021-05-10 21:27:40 +02:00
a006d8c125 bumb develop to the next minor version 2021-05-09 16:06:03 +02:00
faad38f72d Merge branch 'develop' into pycsdr 2021-01-24 00:37:58 +01:00
e11bbbf494 remove fft stuff from csdr 2021-01-23 19:40:05 +01:00
4b94126dc3 use the fft chain directly without csdr dsp classes 2021-01-23 19:27:01 +01:00
4e429d047d Merge branch 'develop' into pycsdr 2021-01-23 17:17:44 +01:00
ee8d896d60 implement output buffer shutdown 2021-01-17 21:01:54 +01:00
db83256bcf Merge branch 'develop' into pycsdr 2021-01-17 20:58:02 +01:00
297d6b540d Merge branch 'develop' into pycsdr 2021-01-17 18:16:32 +01:00
f4629804ff explicitly unset chain since automatic garbage collection is broken 2021-01-04 00:24:06 +01:00
2783091cea unset buffer since it can't be reused 2021-01-04 00:23:29 +01:00
91be89e44e Merge branch 'develop' into pycsdr 2021-01-03 15:59:27 +01:00
a2d731503f update api 2021-01-02 03:12:21 +01:00
3e69c71ed5 Merge branch 'develop' into pycsdr 2021-01-02 03:11:41 +01:00
ca183c7c5a Merge branch 'develop' into pycsdr 2020-12-27 20:22:42 +01:00
fa3b5cd7e6 implement new buffer input / output api 2020-12-25 20:27:30 +01:00
2df527ed20 wrap averager (prepare to make it switchable) 2020-12-21 00:33:48 +01:00
2c7c41cded move fft calculations into fft chain 2020-12-20 22:55:10 +01:00
1083d51e18 update fft parameters without restarting 2020-12-19 17:13:36 +01:00
40c07ebb57 move fft calculations to dsp class 2020-12-19 16:41:48 +01:00
efe80a75f5 put the socketclient on the source so it can be shared 2020-12-19 16:28:18 +01:00
1bd6aa73f3 encapsulate fft chain in its own class 2020-12-16 18:52:00 +01:00
4b61192b36 add a feature flag 2020-12-16 10:18:47 +01:00
664c6e049f pycsdr based ffd (baby steps) 2020-12-15 23:02:12 +01:00
160 changed files with 9106 additions and 2170 deletions

View File

@ -1,3 +1,13 @@
**unreleased**
- Major rewrite of all demodulation components to make use of the new csdr/pycsdr and digiham/pydigiham demodulator
modules
**1.1.0**
- Reworked most graphical elements as SVGs for faster loadtimes and crispier display on hi-dpi displays
- Updated pipelines to match changes in digiham
- Changed D-Star and NXDN integrations to use new decoders from digiham
- Added D-Star and NXDN metadata display
**1.0.0**
- Introduced `squelch_auto_margin` config option that allows configuring the auto squelch level
- Removed `port` configuration option; `rtltcp_compat` takes the port number with the new connectors

View File

@ -13,8 +13,7 @@ It has the following features:
- it works in Google Chrome, Chromium and Mozilla Firefox
- supports a wide range of [SDR hardware](https://github.com/jketterl/openwebrx/wiki/Supported-Hardware#sdr-devices)
- Multiple SDR devices can be used simultaneously
- [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag)
- [dsd](https://github.com/f4exb/dsdcc) based demodulators (D-Star, NXDN)
- [digiham](https://github.com/jketterl/digiham) based demodularors (DMR, YSF, Pocsag, D-Star, NXDN)
- [wsjt-x](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html) based demodulators (FT8, FT4, WSPR, JT65, JT9, FST4,
FST4W)
- [direwolf](https://github.com/wb2osz/direwolf) based demodulation of APRS packets

View File

@ -104,9 +104,6 @@ version = 7
#digimodes_fft_size = 2048
# determines the quality, and thus the cpu usage, for the ambe codec used by digital voice modes
# if you're running on a Raspi (up to 3B+) you'll want to leave this on 1
#digital_voice_unvoiced_quality = 1
# enables lookup of DMR ids using the radioid api
#digital_voice_dmr_id_lookup = True

View File

@ -1,836 +0,0 @@
"""
OpenWebRX csdr plugin: do the signal processing with csdr
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
Copyright (c) 2019-2021 by Jakob Ketterl <dd5jfk@darc.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import subprocess
import os
import signal
import threading
import math
from functools import partial
from csdr.output import Output
from owrx.kiss import KissClient, DirewolfConfig, DirewolfConfigSubscriber
from owrx.audio.chopper import AudioChopper
from csdr.pipe import Pipe
import logging
logger = logging.getLogger(__name__)
class Dsp(DirewolfConfigSubscriber):
def __init__(self, output: Output):
self.samp_rate = 250000
self.output_rate = 11025
self.hd_output_rate = 44100
self.fft_size = 1024
self.fft_fps = 5
self.center_freq = 0
self.offset_freq = 0
self.low_cut = -4000
self.high_cut = 4000
self.bpf_transition_bw = 320 # Hz, and this is a constant
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
self.running = False
self.secondary_processes_running = False
self.audio_compression = "none"
self.fft_compression = "none"
self.demodulator = "nfm"
self.name = "csdr"
self.base_bufsize = 512
self.decimation = None
self.last_decimation = None
self.nc_port = None
self.squelch_level = -150
self.fft_averages = 50
self.wfm_deemphasis_tau = 50e-6
self.iqtee = False
self.iqtee2 = False
self.secondary_demodulator = None
self.secondary_fft_size = 1024
self.secondary_process_fft = None
self.secondary_process_demod = None
self.pipe_names = {
"bpf_pipe": Pipe.WRITE,
"shift_pipe": Pipe.WRITE,
"squelch_pipe": Pipe.WRITE,
"smeter_pipe": Pipe.READ,
"meta_pipe": Pipe.READ,
"iqtee_pipe": Pipe.NONE,
"iqtee2_pipe": Pipe.NONE,
"dmr_control_pipe": Pipe.WRITE,
}
self.pipes = {}
self.secondary_pipe_names = {"secondary_shift_pipe": Pipe.WRITE}
self.secondary_offset_freq = 1000
self.unvoiced_quality = 1
self.modification_lock = threading.Lock()
self.output = output
self.temporary_directory = None
self.pipe_base_path = None
self.set_temporary_directory("/tmp")
self.is_service = False
self.direwolf_config = None
self.direwolf_config_path = None
self.process = None
def set_service(self, flag=True):
self.is_service = flag
def set_temporary_directory(self, what):
self.temporary_directory = what
self.pipe_base_path = "{tmp_dir}/openwebrx_pipe_".format(tmp_dir=self.temporary_directory)
def chain(self, which):
chain = ["nc -v 127.0.0.1 {nc_port}"]
if which == "fft":
chain += [
"csdr fft_cc {fft_size} {fft_block_size}",
"csdr logpower_cf -70"
if self.fft_averages == 0
else "csdr logaveragepower_cf -70 {fft_size} {fft_averages}",
"csdr fft_exchange_sides_ff {fft_size}",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {fft_size}"]
return chain
chain += ["csdr shift_addfast_cc --fifo {shift_pipe}"]
if self.decimation > 1:
chain += ["csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING"]
chain += ["csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING"]
if self.output.supports_type("smeter"):
chain += [
"csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 {smeter_report_every}"
]
if self.secondary_demodulator:
if self.output.supports_type("secondary_fft"):
chain += ["csdr tee {iqtee_pipe}"]
chain += ["csdr tee {iqtee2_pipe}"]
# early exit if we don't want audio
if not self.output.supports_type("audio"):
return chain
# safe some cpu cycles... no need to decimate if decimation factor is 1
last_decimation_block = []
if self.last_decimation >= 2.0:
# activate prefilter if signal has been oversampled, e.g. WFM
last_decimation_block = ["csdr fractional_decimator_ff {last_decimation} 12 --prefilter"]
elif self.last_decimation != 1.0:
last_decimation_block = ["csdr fractional_decimator_ff {last_decimation}"]
if which == "nfm":
chain += ["csdr fmdemod_quadri_cf", "csdr limit_ff"]
chain += last_decimation_block
chain += [
"csdr deemphasis_nfm_ff {audio_rate}",
"csdr agc_ff --profile slow --max 3",
]
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
elif which == "wfm":
chain += [
"csdr fmdemod_quadri_cf",
"csdr limit_ff",
]
chain += last_decimation_block
chain += ["csdr deemphasis_wfm_ff {audio_rate} {wfm_deemphasis_tau}", "csdr convert_f_s16"]
elif self.isDigitalVoice(which):
chain += ["csdr fmdemod_quadri_cf"]
chain += last_decimation_block
# dsd modes
if which in ["dstar", "nxdn"]:
chain += ["dc_block", "csdr limit_ff", "csdr convert_f_s16"]
if which == "dstar":
chain += ["dsd -fd -i - -o - -u {unvoiced_quality} -g -1 "]
elif which == "nxdn":
chain += ["dsd -fi -i - -o - -u {unvoiced_quality} -g -1 "]
chain += [
"digitalvoice_filter",
"CSDR_FIXED_BUFSIZE=32 csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
# m17
elif which == "m17":
chain += [
"dc_block",
"csdr limit_ff",
"csdr convert_f_s16",
"m17-demod",
"CSDR_FIXED_BUFSIZE=32 csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
# digiham modes
else:
chain += ["dc_block", "rrc_filter", "gfsk_demodulator"]
if which == "dmr":
chain += [
"dmr_decoder --fifo {meta_pipe} --control-fifo {dmr_control_pipe}",
"mbe_synthesizer -f -u {unvoiced_quality}",
]
elif which == "ysf":
chain += ["ysf_decoder --fifo {meta_pipe}", "mbe_synthesizer -y -f -u {unvoiced_quality}"]
max_gain = 0.005
chain += [
"digitalvoice_filter -f",
"CSDR_FIXED_BUFSIZE=32 csdr agc_ff --max 0.005 --initial 0.0005",
"sox -t raw -r 8000 -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif which == "am":
chain += ["csdr amdemod_cf", "csdr fastdcblock_ff"]
chain += last_decimation_block
chain += [
"csdr agc_ff --profile slow --initial 200",
"csdr convert_f_s16",
]
elif self.isFreeDV(which):
chain += ["csdr realpart_cf"]
chain += last_decimation_block
chain += [
"csdr agc_ff",
"csdr convert_f_s16",
"freedv_rx 1600 - -",
"csdr agc_s16 --max 30 --initial 3",
"sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif self.isDrm(which):
if self.last_decimation != 1.0:
# we are still dealing with complex samples here, so the regular last_decimation_block doesn't fit
chain += ["csdr fractional_decimator_cc {last_decimation}"]
chain += [
"csdr convert_f_s16",
"dream -c 6 --sigsrate 48000 --audsrate 48000 -I - -O -",
"sox -t raw -r 48000 -e signed-integer -b 16 -c 2 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - ",
]
elif which == "ssb":
chain += ["csdr realpart_cf"]
chain += last_decimation_block
chain += ["csdr agc_ff"]
# fixed sample rate necessary for the wsjt-x tools. fix with sox...
if self.get_audio_rate() != self.get_output_rate():
chain += [
"sox -t raw -r {audio_rate} -e floating-point -b 32 -c 1 --buffer 32 - -t raw -r {output_rate} -e signed-integer -b 16 -c 1 - "
]
else:
chain += ["csdr convert_f_s16"]
if self.audio_compression == "adpcm":
chain += ["csdr encode_ima_adpcm_i16_u8"]
return chain
def secondary_chain(self, which):
chain = ["cat {input_pipe}"]
if which == "fft":
chain += [
"csdr fft_cc {secondary_fft_input_size} {secondary_fft_block_size}",
"csdr logpower_cf -70"
if self.fft_averages == 0
else "csdr logaveragepower_cf -70 {secondary_fft_size} {fft_averages}",
"csdr fft_exchange_sides_ff {secondary_fft_input_size}",
]
if self.fft_compression == "adpcm":
chain += ["csdr compress_fft_adpcm_f_u8 {secondary_fft_size}"]
return chain
elif which == "bpsk31" or which == "bpsk63":
return chain + [
"csdr shift_addfast_cc --fifo {secondary_shift_pipe}",
"csdr bandpass_fir_fft_cc -{secondary_bpf_cutoff} {secondary_bpf_cutoff} {secondary_bpf_cutoff}",
"csdr simple_agc_cc 0.001 0.5",
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q",
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8",
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8",
]
elif self.isWsjtMode(which) or self.isJs8(which):
chain += ["csdr realpart_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr agc_ff", "csdr convert_f_s16"]
elif which == "packet":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["csdr convert_f_s16", "direwolf -c {direwolf_config} -r {audio_rate} -t 0 -q d -q h 1>&2"]
elif which == "pocsag":
chain += ["csdr fmdemod_quadri_cf"]
if self.last_decimation != 1.0:
chain += ["csdr fractional_decimator_ff {last_decimation}"]
return chain + ["fsk_demodulator -i", "pocsag_decoder"]
def set_secondary_demodulator(self, what):
if self.get_secondary_demodulator() == what:
return
self.secondary_demodulator = what
self.calculate_decimation()
self.restart()
def secondary_fft_block_size(self):
base = (self.samp_rate / self.decimation) / (self.fft_fps * 2)
if self.fft_averages == 0:
return base
return base / self.fft_averages
def secondary_decimation(self):
return 1 # currently unused
def secondary_bpf_cutoff(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_bpf_transition_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25 / self.if_samp_rate()
elif self.secondary_demodulator == "bpsk63":
return 62.5 / self.if_samp_rate()
return 0
def secondary_samples_per_bits(self):
if self.secondary_demodulator == "bpsk31":
return int(round(self.if_samp_rate() / 31.25)) & ~3
elif self.secondary_demodulator == "bpsk63":
return int(round(self.if_samp_rate() / 62.5)) & ~3
return 0
def secondary_bw(self):
if self.secondary_demodulator == "bpsk31":
return 31.25
elif self.secondary_demodulator == "bpsk63":
return 62.5
def start_secondary_demodulator(self):
if not self.secondary_demodulator:
return
logger.debug("starting secondary demodulator from IF input sampled at %d" % self.if_samp_rate())
secondary_command_demod = " | ".join(self.secondary_chain(self.secondary_demodulator))
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod)
self.try_create_configs(secondary_command_demod)
secondary_command_demod = secondary_command_demod.format(
input_pipe=self.pipes["iqtee2_pipe"],
secondary_shift_pipe=self.pipes["secondary_shift_pipe"],
secondary_decimation=self.secondary_decimation(),
secondary_samples_per_bits=self.secondary_samples_per_bits(),
secondary_bpf_cutoff=self.secondary_bpf_cutoff(),
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(),
if_samp_rate=self.if_samp_rate(),
last_decimation=self.last_decimation,
audio_rate=self.get_audio_rate(),
direwolf_config=self.direwolf_config_path,
)
logger.debug("secondary command (demod) = %s", secondary_command_demod)
if self.output.supports_type("secondary_fft"):
secondary_command_fft = " | ".join(self.secondary_chain("fft"))
secondary_command_fft = secondary_command_fft.format(
input_pipe=self.pipes["iqtee_pipe"],
secondary_fft_input_size=self.secondary_fft_size,
secondary_fft_size=self.secondary_fft_size,
secondary_fft_block_size=self.secondary_fft_block_size(),
fft_averages=self.fft_averages,
)
logger.debug("secondary command (fft) = %s", secondary_command_fft)
self.secondary_process_fft = subprocess.Popen(
secondary_command_fft, stdout=subprocess.PIPE, shell=True, start_new_session=True
)
self.output.send_output(
"secondary_fft",
partial(self.secondary_process_fft.stdout.read, int(self.get_secondary_fft_bytes_to_read())),
)
# direwolf does not provide any meaningful data on stdout
# more specifically, it doesn't provide any data. if however, for any strange reason, it would start to do so,
# it would block if not read. by piping it to devnull, we avoid a potential pitfall here.
secondary_output = subprocess.DEVNULL if self.isPacket() else subprocess.PIPE
self.secondary_process_demod = subprocess.Popen(
secondary_command_demod, stdout=secondary_output, shell=True, start_new_session=True
)
self.secondary_processes_running = True
if self.isWsjtMode() or self.isJs8():
chopper = AudioChopper(self, self.get_secondary_demodulator())
chopper.send_output("audio", self.secondary_process_demod.stdout.read)
output_type = "js8_demod" if self.isJs8() else "wsjt_demod"
self.output.send_output(output_type, chopper.read)
elif self.isPacket():
# we best get the ax25 packets from the kiss socket
kiss = KissClient(self.direwolf_config.getPort())
self.output.send_output("packet_demod", kiss.read)
elif self.isPocsag():
self.output.send_output("pocsag_demod", self.secondary_process_demod.stdout.readline)
else:
self.output.send_output("secondary_demod", partial(self.secondary_process_demod.stdout.read, 1))
# open control pipes for csdr and send initialization data
if self.has_pipe("secondary_shift_pipe"): # TODO digimodes
self.set_secondary_offset_freq(self.secondary_offset_freq) # TODO digimodes
def set_secondary_offset_freq(self, value):
self.secondary_offset_freq = value
if self.secondary_processes_running and self.has_pipe("secondary_shift_pipe"):
self.pipes["secondary_shift_pipe"].write(
"%g\n" % (-float(self.secondary_offset_freq) / self.if_samp_rate())
)
def stop_secondary_demodulator(self):
if not self.secondary_processes_running:
return
self.try_delete_pipes(self.secondary_pipe_names)
self.try_delete_configs()
if self.secondary_process_fft:
try:
os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.secondary_process_fft.communicate()
self.secondary_process_fft = None
except ProcessLookupError:
# been killed by something else, ignore
pass
if self.secondary_process_demod:
try:
os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.secondary_process_demod.communicate()
self.secondary_process_demod = None
except ProcessLookupError:
# been killed by something else, ignore
pass
self.secondary_processes_running = False
def get_secondary_demodulator(self):
return self.secondary_demodulator
def set_secondary_fft_size(self, secondary_fft_size):
if self.secondary_fft_size == secondary_fft_size:
return
self.secondary_fft_size = secondary_fft_size
self.restart()
def set_audio_compression(self, what):
if self.audio_compression == what:
return
self.audio_compression = what
self.restart()
def get_audio_bytes_to_read(self):
# desired latency: 5ms
# uncompressed audio has 16 bits = 2 bytes per sample
base = self.output_rate * 0.005 * 2
# adpcm compresses the bitstream by 4
if self.audio_compression == "adpcm":
base = base / 4
return int(base)
def set_fft_compression(self, what):
if self.fft_compression == what:
return
self.fft_compression = what
self.restart()
def get_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.fft_size * 4
if self.fft_compression == "adpcm":
return int((self.fft_size / 2) + (10 / 2))
def get_secondary_fft_bytes_to_read(self):
if self.fft_compression == "none":
return self.secondary_fft_size * 4
if self.fft_compression == "adpcm":
return (self.secondary_fft_size / 2) + (10 / 2)
def set_samp_rate(self, samp_rate):
self.samp_rate = samp_rate
self.calculate_decimation()
if self.running:
self.restart()
def calculate_decimation(self):
(self.decimation, self.last_decimation) = self.get_decimation(self.samp_rate, self.get_audio_rate())
def get_decimation(self, input_rate, output_rate):
if output_rate <= 0:
raise ValueError("invalid output rate: {rate}".format(rate=output_rate))
decimation = 1
target_rate = output_rate
# wideband fm has a much higher frequency deviation (75kHz).
# we cannot cover this if we immediately decimate to the sample rate the audio will have later on, so we need
# to compensate here.
if self.get_demodulator() == "wfm" and output_rate < 200000:
target_rate = 200000
while input_rate / (decimation + 1) >= target_rate:
decimation += 1
fraction = float(input_rate / decimation) / output_rate
return decimation, fraction
def if_samp_rate(self):
return self.samp_rate / self.decimation
def get_name(self):
return self.name
def get_output_rate(self):
return self.output_rate
def get_hd_output_rate(self):
return self.hd_output_rate
def get_audio_rate(self):
if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isDrm():
return 48000
elif self.isWsjtMode() or self.isJs8():
return 12000
elif self.isFreeDV():
return 8000
elif self.isHdAudio():
return self.get_hd_output_rate()
return self.get_output_rate()
def isDigitalVoice(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator in ["dmr", "dstar", "nxdn", "ysf", "m17"]
def isWsjtMode(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator in ["ft8", "wspr", "jt65", "jt9", "ft4", "fst4", "fst4w", "q65"]
def isJs8(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "js8"
def isPacket(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "packet"
def isPocsag(self, demodulator=None):
if demodulator is None:
demodulator = self.get_secondary_demodulator()
return demodulator == "pocsag"
def isFreeDV(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "freedv"
def isHdAudio(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "wfm"
def isDrm(self, demodulator=None):
if demodulator is None:
demodulator = self.get_demodulator()
return demodulator == "drm"
def set_output_rate(self, output_rate):
if self.output_rate == output_rate:
return
self.output_rate = output_rate
self.calculate_decimation()
self.restart()
def set_hd_output_rate(self, hd_output_rate):
if self.hd_output_rate == hd_output_rate:
return
self.hd_output_rate = hd_output_rate
self.calculate_decimation()
self.restart()
def set_demodulator(self, demodulator):
if demodulator in ["usb", "lsb", "cw"]:
demodulator = "ssb"
if self.demodulator == demodulator:
return
self.demodulator = demodulator
self.calculate_decimation()
self.restart()
def get_demodulator(self):
return self.demodulator
def set_fft_size(self, fft_size):
if self.fft_size == fft_size:
return
self.fft_size = fft_size
self.restart()
def set_fft_fps(self, fft_fps):
self.fft_fps = fft_fps
self.restart()
def set_fft_averages(self, fft_averages):
self.fft_averages = fft_averages
self.restart()
def fft_block_size(self):
if self.fft_averages == 0:
return self.samp_rate / self.fft_fps
else:
return self.samp_rate / self.fft_fps / self.fft_averages
def set_offset_freq(self, offset_freq):
if offset_freq is None:
return
self.offset_freq = offset_freq
if self.running:
self.pipes["shift_pipe"].write("%g\n" % (-float(self.offset_freq) / self.samp_rate))
def set_center_freq(self, center_freq):
# dsp only needs to know this to be able to pass it to decoders in the form of get_operating_freq()
self.center_freq = center_freq
def get_operating_freq(self):
return self.center_freq + self.offset_freq
def set_bandpass(self, bandpass):
self.set_bpf(bandpass.low_cut, bandpass.high_cut)
def set_bpf(self, low_cut, high_cut):
self.low_cut = low_cut
self.high_cut = high_cut
if self.running:
self.pipes["bpf_pipe"].write(
"%g %g\n" % (float(self.low_cut) / self.if_samp_rate(), float(self.high_cut) / self.if_samp_rate())
)
def get_bpf(self):
return [self.low_cut, self.high_cut]
def convertToLinear(self, db):
return float(math.pow(10, db / 10))
def set_squelch_level(self, squelch_level):
self.squelch_level = squelch_level
# no squelch required on digital voice modes
actual_squelch = (
-150
if self.isDigitalVoice() or self.isPacket() or self.isPocsag() or self.isFreeDV()
else self.squelch_level
)
if self.running:
self.pipes["squelch_pipe"].write("%g\n" % (self.convertToLinear(actual_squelch)))
def set_unvoiced_quality(self, q):
self.unvoiced_quality = q
self.restart()
def get_unvoiced_quality(self):
return self.unvoiced_quality
def set_dmr_filter(self, filter):
if self.has_pipe("dmr_control_pipe"):
self.pipes["dmr_control_pipe"].write("{0}\n".format(filter))
def set_wfm_deemphasis_tau(self, tau):
if self.wfm_deemphasis_tau == tau:
return
self.wfm_deemphasis_tau = tau
self.restart()
def ddc_transition_bw(self):
return self.ddc_transition_bw_rate * (self.if_samp_rate() / float(self.samp_rate))
def try_create_pipes(self, pipe_names, command_base):
for pipe_name, pipe_type in pipe_names.items():
if self.has_pipe(pipe_name):
logger.warning("{pipe_name} is still in use", pipe_name=pipe_name)
self.pipes[pipe_name].close()
if "{" + pipe_name + "}" in command_base:
p = self.pipe_base_path + pipe_name
encoding = None
# TODO make digiham output unicode and then change this here
# the whole pipe enoding feature onlye exists because of this
if pipe_name == "meta_pipe":
encoding = "cp437"
self.pipes[pipe_name] = Pipe.create(p, pipe_type, encoding=encoding)
else:
self.pipes[pipe_name] = None
def has_pipe(self, name):
return name in self.pipes and self.pipes[name] is not None
def try_delete_pipes(self, pipe_names):
for pipe_name in pipe_names:
if self.has_pipe(pipe_name):
self.pipes[pipe_name].close()
self.pipes[pipe_name] = None
def try_create_configs(self, command):
if "{direwolf_config}" in command:
self.direwolf_config_path = "{tmp_dir}/openwebrx_direwolf_{myid}.conf".format(
tmp_dir=self.temporary_directory, myid=id(self)
)
self.direwolf_config = DirewolfConfig()
self.direwolf_config.wire(self)
file = open(self.direwolf_config_path, "w")
file.write(self.direwolf_config.getConfig(self.is_service))
file.close()
else:
self.direwolf_config = None
self.direwolf_config_path = None
def try_delete_configs(self):
if self.direwolf_config is not None:
self.direwolf_config.unwire(self)
self.direwolf_config = None
if self.direwolf_config_path is not None:
try:
os.unlink(self.direwolf_config_path)
except FileNotFoundError:
# result suits our expectations. fine :)
pass
except Exception:
logger.exception("try_delete_configs()")
self.direwolf_config_path = None
def onConfigChanged(self):
self.restart()
def start(self):
with self.modification_lock:
if self.running:
return
self.running = True
command_base = " | ".join(self.chain(self.demodulator))
# create control pipes for csdr
self.try_create_pipes(self.pipe_names, command_base)
# send initial config through the pipes
if self.has_pipe("bpf_pipe"):
self.set_bpf(self.low_cut, self.high_cut)
if self.has_pipe("shift_pipe"):
self.set_offset_freq(self.offset_freq)
if self.has_pipe("squelch_pipe"):
self.set_squelch_level(self.squelch_level)
if self.has_pipe("dmr_control_pipe"):
self.set_dmr_filter(3)
# run the command
command = command_base.format(
bpf_pipe=self.pipes["bpf_pipe"],
shift_pipe=self.pipes["shift_pipe"],
squelch_pipe=self.pipes["squelch_pipe"],
smeter_pipe=self.pipes["smeter_pipe"],
meta_pipe=self.pipes["meta_pipe"],
iqtee_pipe=self.pipes["iqtee_pipe"],
iqtee2_pipe=self.pipes["iqtee2_pipe"],
dmr_control_pipe=self.pipes["dmr_control_pipe"],
decimation=self.decimation,
last_decimation=self.last_decimation,
fft_size=self.fft_size,
fft_block_size=self.fft_block_size(),
fft_averages=self.fft_averages,
bpf_transition_bw=float(self.bpf_transition_bw) / self.if_samp_rate(),
ddc_transition_bw=self.ddc_transition_bw(),
flowcontrol=int(self.samp_rate * 2),
start_bufsize=self.base_bufsize * self.decimation,
nc_port=self.nc_port,
output_rate=self.get_output_rate(),
smeter_report_every=int(self.if_samp_rate() / 6000),
unvoiced_quality=self.get_unvoiced_quality(),
audio_rate=self.get_audio_rate(),
wfm_deemphasis_tau=self.wfm_deemphasis_tau,
)
logger.debug("Command = %s", command)
out = subprocess.PIPE if self.output.supports_type("audio") else subprocess.DEVNULL
self.process = subprocess.Popen(command, stdout=out, shell=True, start_new_session=True)
def watch_thread():
rc = self.process.wait()
logger.debug("dsp thread ended with rc=%d", rc)
if rc == 0 and self.running and not self.modification_lock.locked():
logger.debug("restarting since rc = 0, self.running = true, and no modification")
self.restart()
threading.Thread(target=watch_thread, name="csdr_watch_thread").start()
audio_type = "hd_audio" if self.isHdAudio() else "audio"
if self.output.supports_type(audio_type):
self.output.send_output(
audio_type,
partial(
self.process.stdout.read,
self.get_fft_bytes_to_read() if self.demodulator == "fft" else self.get_audio_bytes_to_read(),
),
)
self.start_secondary_demodulator()
if self.has_pipe("smeter_pipe"):
def read_smeter():
raw = self.pipes["smeter_pipe"].readline()
if len(raw) == 0:
return None
else:
return float(raw.rstrip("\n"))
self.output.send_output("smeter", read_smeter)
if self.has_pipe("meta_pipe"):
def read_meta():
raw = self.pipes["meta_pipe"].readline()
if len(raw) == 0:
return None
else:
return raw.rstrip("\n")
self.output.send_output("meta", read_meta)
def stop(self):
with self.modification_lock:
self.running = False
if self.process is not None:
try:
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
# drain any leftover data to free file descriptors
self.process.communicate()
self.process = None
except ProcessLookupError:
# been killed by something else, ignore
pass
self.stop_secondary_demodulator()
self.try_delete_pipes(self.pipe_names)
self.try_delete_configs()
def restart(self):
if not self.running:
return
self.stop()
self.start()

142
csdr/chain/__init__.py Normal file
View File

@ -0,0 +1,142 @@
from csdr.module import Module
from pycsdr.modules import Buffer
from pycsdr.types import Format
from typing import Union, Callable, Optional
class Chain(Module):
def __init__(self, workers):
super().__init__()
self.workers = workers
for i in range(1, len(self.workers)):
self._connect(self.workers[i - 1], self.workers[i])
def empty(self):
return not self.workers
def _connect(self, w1, w2, buffer: Optional[Buffer] = None) -> None:
if buffer is None:
buffer = Buffer(w1.getOutputFormat())
w1.setWriter(buffer)
w2.setReader(buffer.getReader())
def setReader(self, reader):
if self.reader is reader:
return
super().setReader(reader)
if self.workers:
self.workers[0].setReader(reader)
def setWriter(self, writer):
if self.writer is writer:
return
super().setWriter(writer)
if self.workers:
self.workers[-1].setWriter(writer)
def indexOf(self, search: Union[Callable, object]) -> int:
def searchFn(x):
if callable(search):
return search(x)
else:
return x is search
try:
return next(i for i, v in enumerate(self.workers) if searchFn(v))
except StopIteration:
return -1
def replace(self, index, newWorker):
if index >= len(self.workers):
raise IndexError("Index {} does not exist".format(index))
self.workers[index].stop()
self.workers[index] = newWorker
error = None
if index == 0:
if self.reader is not None:
newWorker.setReader(self.reader)
else:
try:
previousWorker = self.workers[index - 1]
self._connect(previousWorker, newWorker)
except ValueError as e:
# store error for later raising, but still attempt the second connection
error = e
if index == len(self.workers) - 1:
if self.writer is not None:
newWorker.setWriter(self.writer)
else:
try:
nextWorker = self.workers[index + 1]
self._connect(newWorker, nextWorker)
except ValueError as e:
error = e
if error is not None:
raise error
def append(self, newWorker):
previousWorker = None
if self.workers:
previousWorker = self.workers[-1]
self.workers.append(newWorker)
if previousWorker:
self._connect(previousWorker, newWorker)
elif self.reader is not None:
newWorker.setReader(self.reader)
if self.writer is not None:
newWorker.setWriter(self.writer)
def insert(self, newWorker):
nextWorker = None
if self.workers:
nextWorker = self.workers[0]
self.workers.insert(0, newWorker)
if nextWorker:
self._connect(newWorker, nextWorker)
elif self.writer is not None:
newWorker.setWriter(self.writer)
if self.reader is not None:
newWorker.setReader(self.reader)
def remove(self, index):
removedWorker = self.workers[index]
self.workers.remove(removedWorker)
removedWorker.stop()
if index == 0:
if self.reader is not None and len(self.workers):
self.workers[0].setReader(self.reader)
elif index == len(self.workers):
if self.writer is not None:
self.workers[-1].setWriter(self.writer)
else:
previousWorker = self.workers[index - 1]
nextWorker = self.workers[index]
self._connect(previousWorker, nextWorker)
def stop(self):
for w in self.workers:
w.stop()
def getInputFormat(self) -> Format:
if self.workers:
return self.workers[0].getInputFormat()
else:
raise BufferError("getInputFormat on empty chain")
def getOutputFormat(self) -> Format:
if self.workers:
return self.workers[-1].getOutputFormat()
else:
raise BufferError("getOutputFormat on empty chain")

76
csdr/chain/analog.py Normal file
View File

@ -0,0 +1,76 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, HdAudio, DeemphasisTauChain
from pycsdr.modules import AmDemod, DcBlock, FmDemod, Limit, NfmDeemphasis, Agc, WfmDeemphasis, FractionalDecimator, RealPart
from pycsdr.types import Format, AgcProfile
class Am(BaseDemodulatorChain):
def __init__(self):
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setInitialGain(200)
workers = [
AmDemod(),
DcBlock(),
agc,
]
super().__init__(workers)
class NFm(BaseDemodulatorChain):
def __init__(self, sampleRate: int):
self.sampleRate = sampleRate
agc = Agc(Format.FLOAT)
agc.setProfile(AgcProfile.SLOW)
agc.setMaxGain(3)
workers = [
FmDemod(),
Limit(),
NfmDeemphasis(sampleRate),
agc,
]
super().__init__(workers)
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
self.replace(2, NfmDeemphasis(sampleRate))
class WFm(BaseDemodulatorChain, FixedIfSampleRateChain, DeemphasisTauChain, HdAudio):
def __init__(self, sampleRate: int, tau: float):
self.sampleRate = sampleRate
self.tau = tau
workers = [
FmDemod(),
Limit(),
FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True),
WfmDeemphasis(self.sampleRate, self.tau),
]
super().__init__(workers)
def getFixedIfSampleRate(self):
return 200000
def setDeemphasisTau(self, tau: float) -> None:
if tau == self.tau:
return
self.tau = tau
self.replace(3, WfmDeemphasis(self.sampleRate, self.tau))
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
self.replace(2, FractionalDecimator(Format.FLOAT, 200000.0 / self.sampleRate, prefilter=True))
self.replace(3, WfmDeemphasis(self.sampleRate, self.tau))
class Ssb(BaseDemodulatorChain):
def __init__(self):
workers = [
RealPart(),
Agc(Format.FLOAT),
]
super().__init__(workers)

72
csdr/chain/clientaudio.py Normal file
View File

@ -0,0 +1,72 @@
from csdr.chain import Chain
from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit
from pycsdr.types import Format
class Converter(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int):
workers = []
if inputRate != clientRate:
# we only have an audio resampler for float ATM so if we need to resample, we need to convert
if format != Format.FLOAT:
workers += [Convert(format, Format.FLOAT)]
workers += [AudioResampler(inputRate, clientRate), Limit(), Convert(Format.FLOAT, Format.SHORT)]
elif format != Format.SHORT:
workers += [Convert(format, Format.SHORT)]
super().__init__(workers)
class ClientAudioChain(Chain):
def __init__(self, format: Format, inputRate: int, clientRate: int, compression: str):
self.format = format
self.inputRate = inputRate
self.clientRate = clientRate
workers = []
converter = self._buildConverter()
if not converter.empty():
workers += [converter]
if compression == "adpcm":
workers += [AdpcmEncoder(sync=True)]
super().__init__(workers)
def _buildConverter(self):
return Converter(self.format, self.inputRate, self.clientRate)
def _updateConverter(self):
converter = self._buildConverter()
index = self.indexOf(lambda x: isinstance(x, Converter))
if converter.empty():
if index >= 0:
self.remove(index)
else:
if index >= 0:
self.replace(index, converter)
else:
self.insert(converter)
def setFormat(self, format: Format) -> None:
if format == self.format:
return
self.format = format
self._updateConverter()
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self._updateConverter()
def setClientRate(self, clientRate: int) -> None:
if clientRate == self.clientRate:
return
self.clientRate = clientRate
self._updateConverter()
def setAudioCompression(self, compression: str) -> None:
index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder))
if compression == "adpcm":
if index < 0:
self.append(AdpcmEncoder(sync=True))
else:
if index >= 0:
self.remove(index)

69
csdr/chain/demodulator.py Normal file
View File

@ -0,0 +1,69 @@
from csdr.chain import Chain
from abc import ABC, ABCMeta, abstractmethod
from pycsdr.modules import Writer
class FixedAudioRateChain(ABC):
@abstractmethod
def getFixedAudioRate(self) -> int:
pass
class FixedIfSampleRateChain(ABC):
@abstractmethod
def getFixedIfSampleRate(self) -> int:
pass
class DialFrequencyReceiver(ABC):
@abstractmethod
def setDialFrequency(self, frequency: int) -> None:
pass
# marker interface
class HdAudio:
pass
class MetaProvider(ABC):
@abstractmethod
def setMetaWriter(self, writer: Writer) -> None:
pass
class SlotFilterChain(ABC):
@abstractmethod
def setSlotFilter(self, filter: int) -> None:
pass
class SecondarySelectorChain(ABC):
def getBandwidth(self) -> float:
pass
class DeemphasisTauChain(ABC):
@abstractmethod
def setDeemphasisTau(self, tau: float) -> None:
pass
class BaseDemodulatorChain(Chain):
def supportsSquelch(self) -> bool:
return True
def setSampleRate(self, sampleRate: int) -> None:
pass
class SecondaryDemodulator(Chain):
def supportsSquelch(self) -> bool:
return True
def setSampleRate(self, sampleRate: int) -> None:
pass
class ServiceDemodulator(SecondaryDemodulator, FixedAudioRateChain, metaclass=ABCMeta):
pass

105
csdr/chain/digiham.py Normal file
View File

@ -0,0 +1,105 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedAudioRateChain, FixedIfSampleRateChain, DialFrequencyReceiver, MetaProvider, SlotFilterChain
from pycsdr.modules import FmDemod, Agc, Writer, Buffer
from pycsdr.types import Format
from digiham.modules import DstarDecoder, DcBlock, FskDemodulator, GfskDemodulator, DigitalVoiceFilter, MbeSynthesizer, NarrowRrcFilter, NxdnDecoder, DmrDecoder, WideRrcFilter, YsfDecoder
from digiham.ambe import Modes
from owrx.meta import MetaParser
class DigihamChain(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, DialFrequencyReceiver, MetaProvider):
def __init__(self, fskDemodulator, decoder, mbeMode, filter=None, codecserver: str = ""):
self.decoder = decoder
if codecserver is None:
codecserver = ""
agc = Agc(Format.SHORT)
agc.setMaxGain(30)
agc.setInitialGain(3)
workers = [FmDemod(), DcBlock()]
if filter is not None:
workers += [filter]
workers += [
fskDemodulator,
decoder,
MbeSynthesizer(mbeMode, codecserver),
DigitalVoiceFilter(),
agc
]
self.metaParser = None
self.dialFrequency = None
super().__init__(workers)
def getFixedIfSampleRate(self):
return 48000
def getFixedAudioRate(self):
return 8000
def setMetaWriter(self, writer: Writer) -> None:
if self.metaParser is None:
self.metaParser = MetaParser()
buffer = Buffer(Format.CHAR)
self.decoder.setMetaWriter(buffer)
self.metaParser.setReader(buffer.getReader())
if self.dialFrequency is not None:
self.metaParser.setDialFrequency(self.dialFrequency)
self.metaParser.setWriter(writer)
def supportsSquelch(self):
return False
def setDialFrequency(self, frequency: int) -> None:
self.dialFrequency = frequency
if self.metaParser is None:
return
self.metaParser.setDialFrequency(frequency)
def stop(self):
if self.metaParser is not None:
self.metaParser.stop()
super().stop()
class Dstar(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=FskDemodulator(samplesPerSymbol=10),
decoder=DstarDecoder(),
mbeMode=Modes.DStarMode,
codecserver=codecserver
)
class Nxdn(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=20),
decoder=NxdnDecoder(),
mbeMode=Modes.NxdnMode,
filter=NarrowRrcFilter(),
codecserver=codecserver
)
class Dmr(DigihamChain, SlotFilterChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=10),
decoder=DmrDecoder(),
mbeMode=Modes.DmrMode,
filter=WideRrcFilter(),
codecserver=codecserver,
)
def setSlotFilter(self, slotFilter: int) -> None:
self.decoder.setSlotFilter(slotFilter)
class Ysf(DigihamChain):
def __init__(self, codecserver: str = ""):
super().__init__(
fskDemodulator=GfskDemodulator(samplesPerSymbol=10),
decoder=YsfDecoder(),
mbeMode=Modes.YsfMode,
filter=WideRrcFilter(),
codecserver=codecserver
)

87
csdr/chain/digimodes.py Normal file
View File

@ -0,0 +1,87 @@
from csdr.chain.demodulator import ServiceDemodulator, SecondaryDemodulator, DialFrequencyReceiver, SecondarySelectorChain
from owrx.audio.chopper import AudioChopper, AudioChopperParser
from owrx.aprs.kiss import KissDeframer
from owrx.aprs import Ax25Parser, AprsParser
from pycsdr.modules import Convert, FmDemod, Agc, TimingRecovery, DBPskDecoder, VaricodeDecoder
from pycsdr.types import Format
from owrx.aprs.module import DirewolfModule
from digiham.modules import FskDemodulator, PocsagDecoder
from owrx.pocsag import PocsagParser
class AudioChopperDemodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self, mode: str, parser: AudioChopperParser):
self.chopper = AudioChopper(mode, parser)
workers = [Convert(Format.FLOAT, Format.SHORT), self.chopper]
super().__init__(workers)
def getFixedAudioRate(self):
return 12000
def setDialFrequency(self, frequency: int) -> None:
self.chopper.setDialFrequency(frequency)
class PacketDemodulator(ServiceDemodulator, DialFrequencyReceiver):
def __init__(self, service: bool = False):
self.parser = AprsParser()
workers = [
FmDemod(),
Convert(Format.FLOAT, Format.SHORT),
DirewolfModule(service=service),
KissDeframer(),
Ax25Parser(),
self.parser,
]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedAudioRate(self) -> int:
return 48000
def setDialFrequency(self, frequency: int) -> None:
self.parser.setDialFrequency(frequency)
class PocsagDemodulator(ServiceDemodulator):
def __init__(self):
workers = [
FmDemod(),
FskDemodulator(samplesPerSymbol=40, invert=True),
PocsagDecoder(),
PocsagParser(),
]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedAudioRate(self) -> int:
return 48000
class PskDemodulator(SecondaryDemodulator, SecondarySelectorChain):
def __init__(self, baudRate: float):
self.baudRate = baudRate
# this is an assumption, we will adjust in setSampleRate
self.sampleRate = 12000
secondary_samples_per_bits = int(round(self.sampleRate / self.baudRate)) & ~3
workers = [
Agc(Format.COMPLEX_FLOAT),
TimingRecovery(secondary_samples_per_bits, 0.5, 2, useQ=True),
DBPskDecoder(),
VaricodeDecoder(),
]
super().__init__(workers)
def getBandwidth(self):
return self.baudRate
def setSampleRate(self, sampleRate: int) -> None:
if sampleRate == self.sampleRate:
return
self.sampleRate = sampleRate
secondary_samples_per_bits = int(round(self.sampleRate / self.baudRate)) & ~3
self.replace(1, TimingRecovery(secondary_samples_per_bits, 0.5, 2, useQ=True))

19
csdr/chain/drm.py Normal file
View File

@ -0,0 +1,19 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain
from pycsdr.modules import Convert, Downmix
from pycsdr.types import Format
from csdr.module.drm import DrmModule
class Drm(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
def __init__(self):
workers = [Convert(Format.COMPLEX_FLOAT, Format.COMPLEX_SHORT), DrmModule(), Downmix()]
super().__init__(workers)
def supportsSquelch(self) -> bool:
return False
def getFixedIfSampleRate(self) -> int:
return 48000
def getFixedAudioRate(self) -> int:
return 48000

96
csdr/chain/fft.py Normal file
View File

@ -0,0 +1,96 @@
from csdr.chain import Chain
from pycsdr.modules import Fft, LogPower, LogAveragePower, FftSwap, FftAdpcm
class FftAverager(Chain):
def __init__(self, fft_size, fft_averages):
self.fftSize = fft_size
self.fftAverages = fft_averages
workers = [self._getWorker()]
super().__init__(workers)
def setFftAverages(self, fft_averages):
if self.fftAverages == fft_averages:
return
self.fftAverages = fft_averages
self.replace(0, self._getWorker())
def _getWorker(self):
if self.fftAverages == 0:
return LogPower(add_db=-70)
else:
return LogAveragePower(add_db=-70, fft_size=self.fftSize, avg_number=self.fftAverages)
class FftChain(Chain):
def __init__(self, samp_rate, fft_size, fft_v_overlap_factor, fft_fps, fft_compression):
self.sampleRate = samp_rate
self.vOverlapFactor = fft_v_overlap_factor
self.fps = fft_fps
self.size = fft_size
self.blockSize = 0
self.fft = Fft(size=self.size, every_n_samples=self.blockSize)
self.averager = FftAverager(fft_size=self.size, fft_averages=10)
self.fftExchangeSides = FftSwap(fft_size=self.size)
workers = [
self.fft,
self.averager,
self.fftExchangeSides,
]
self.compressFftAdpcm = None
if fft_compression == "adpcm":
self.compressFftAdpcm = FftAdpcm(fft_size=self.size)
workers += [self.compressFftAdpcm]
self._updateParameters()
super().__init__(workers)
def _setBlockSize(self, fft_block_size):
if self.blockSize == int(fft_block_size):
return
self.blockSize = int(fft_block_size)
self.fft.setEveryNSamples(self.blockSize)
def setVOverlapFactor(self, fft_v_overlap_factor):
if self.vOverlapFactor == fft_v_overlap_factor:
return
self.vOverlapFactor = fft_v_overlap_factor
self._updateParameters()
def setFps(self, fft_fps):
if self.fps == fft_fps:
return
self.fps = fft_fps
self._updateParameters()
def setSampleRate(self, samp_rate):
if self.sampleRate == samp_rate:
return
self.sampleRate = samp_rate
self._updateParameters()
def _updateParameters(self):
fftAverages = 0
if self.vOverlapFactor > 0:
fftAverages = int(round(1.0 * self.sampleRate / self.size / self.fps / (1.0 - self.vOverlapFactor)))
self.averager.setFftAverages(fftAverages)
if fftAverages == 0:
self._setBlockSize(self.sampleRate / self.fps)
else:
self._setBlockSize(self.sampleRate / self.fps / fftAverages)
def setCompression(self, compression: str) -> None:
if compression == "adpcm" and not self.compressFftAdpcm:
self.compressFftAdpcm = FftAdpcm(self.size)
# should always be at the end
self.append(self.compressFftAdpcm)
elif compression == "none" and self.compressFftAdpcm:
self.compressFftAdpcm.stop()
self.compressFftAdpcm = None
# should always be at that position (right?)
self.remove(3)

28
csdr/chain/freedv.py Normal file
View File

@ -0,0 +1,28 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain
from csdr.module.freedv import FreeDVModule
from pycsdr.modules import RealPart, Agc, Convert
from pycsdr.types import Format
class FreeDV(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
def __init__(self):
agc = Agc(Format.SHORT)
agc.setMaxGain(30)
agc.setInitialGain(3)
workers = [
RealPart(),
Agc(Format.FLOAT),
Convert(Format.FLOAT, Format.SHORT),
FreeDVModule(),
agc,
]
super().__init__(workers)
def getFixedIfSampleRate(self) -> int:
return 8000
def getFixedAudioRate(self) -> int:
return 8000
def supportsSquelch(self) -> bool:
return False

26
csdr/chain/m17.py Normal file
View File

@ -0,0 +1,26 @@
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain
from csdr.module.m17 import M17Module
from pycsdr.modules import FmDemod, Limit, Convert
from pycsdr.types import Format
from digiham.modules import DcBlock
class M17(BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain):
def __init__(self):
workers = [
FmDemod(),
DcBlock(),
Limit(),
Convert(Format.FLOAT, Format.SHORT),
M17Module(),
]
super().__init__(workers)
def getFixedIfSampleRate(self) -> int:
return 48000
def getFixedAudioRate(self) -> int:
return 8000
def supportsSquelch(self) -> bool:
return False

160
csdr/chain/selector.py Normal file
View File

@ -0,0 +1,160 @@
from csdr.chain import Chain
from pycsdr.modules import Shift, FirDecimate, Bandpass, Squelch, FractionalDecimator, Writer
from pycsdr.types import Format
import math
class Decimator(Chain):
def __init__(self, inputRate: int, outputRate: int):
if outputRate > inputRate:
raise ValueError("impossible decimation: cannot upsample {} to {}".format(inputRate, outputRate))
self.inputRate = inputRate
self.outputRate = outputRate
decimation, fraction = self._getDecimation(outputRate)
transition = 0.15 * (outputRate / float(self.inputRate))
# set the cutoff on the fist decimation stage lower so that the resulting output
# is already prepared for the second (fractional) decimation stage.
# this spares us a second filter.
cutoff = 0.5 * decimation / (self.inputRate / outputRate)
workers = [
FirDecimate(decimation, transition, cutoff),
]
if fraction != 1.0:
workers += [FractionalDecimator(Format.COMPLEX_FLOAT, fraction)]
super().__init__(workers)
def _getDecimation(self, outputRate: int) -> (int, float):
d = self.inputRate / outputRate
dInt = int(d)
dFloat = float(self.inputRate / dInt) / outputRate
return dInt, dFloat
def _reconfigure(self):
decimation, fraction = self._getDecimation(self.outputRate)
transition = 0.15 * (self.outputRate / float(self.inputRate))
cutoff = 0.5 * decimation / (self.inputRate / self.outputRate)
self.replace(0, FirDecimate(decimation, transition, cutoff))
index = self.indexOf(lambda x: isinstance(x, FractionalDecimator))
if fraction != 1.0:
decimator = FractionalDecimator(Format.COMPLEX_FLOAT, fraction)
if index >= 0:
self.replace(index, decimator)
else:
self.append(decimator)
elif index >= 0:
self.remove(index)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self._reconfigure()
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self._reconfigure()
class Selector(Chain):
def __init__(self, inputRate: int, outputRate: int, withSquelch: bool = True):
self.inputRate = inputRate
self.outputRate = outputRate
self.frequencyOffset = 0
self.shift = Shift(0.0)
self.decimation = Decimator(inputRate, outputRate)
self.bandpass = self._buildBandpass()
self.bandpassCutoffs = None
self.setBandpass(-4000, 4000)
workers = [self.shift, self.decimation, self.bandpass]
if withSquelch:
self.readings_per_second = 4
# s-meter readings are available every 1024 samples
# the reporting interval is measured in those 1024-sample blocks
self.squelch = Squelch(5, int(outputRate / (self.readings_per_second * 1024)))
workers += [self.squelch]
super().__init__(workers)
def _buildBandpass(self) -> Bandpass:
bp_transition = 320.0 / self.outputRate
return Bandpass(transition=bp_transition, use_fft=True)
def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
self._updateShift()
def _updateShift(self):
shift = -self.frequencyOffset / self.inputRate
self.shift.setRate(shift)
def _convertToLinear(self, db: float) -> float:
return float(math.pow(10, db / 10))
def setSquelchLevel(self, level: float) -> None:
self.squelch.setSquelchLevel(self._convertToLinear(level))
def setBandpass(self, lowCut: float, highCut: float) -> None:
self.bandpassCutoffs = [lowCut, highCut]
scaled = [x / self.outputRate for x in self.bandpassCutoffs]
self.bandpass.setBandpass(*scaled)
def setLowCut(self, lowCut: float) -> None:
self.bandpassCutoffs[0] = lowCut
self.setBandpass(*self.bandpassCutoffs)
def setHighCut(self, highCut: float) -> None:
self.bandpassCutoffs[1] = highCut
self.setBandpass(*self.bandpassCutoffs)
def setPowerWriter(self, writer: Writer) -> None:
self.squelch.setPowerWriter(writer)
def setOutputRate(self, outputRate: int) -> None:
if outputRate == self.outputRate:
return
self.outputRate = outputRate
self.decimation.setOutputRate(outputRate)
self.squelch.setReportInterval(int(outputRate / (self.readings_per_second * 1024)))
self.bandpass = self._buildBandpass()
self.setBandpass(*self.bandpassCutoffs)
self.replace(2, self.bandpass)
def setInputRate(self, inputRate: int) -> None:
if inputRate == self.inputRate:
return
self.inputRate = inputRate
self.decimation.setInputRate(inputRate)
self._updateShift()
class SecondarySelector(Chain):
def __init__(self, sampleRate: int, bandwidth: float):
self.sampleRate = sampleRate
self.frequencyOffset = 0
self.shift = Shift(0.0)
cutoffRate = bandwidth / sampleRate
self.bandpass = Bandpass(-cutoffRate, cutoffRate, cutoffRate, use_fft=True)
workers = [self.shift, self.bandpass]
super().__init__(workers)
def setFrequencyOffset(self, offset: int) -> None:
if offset == self.frequencyOffset:
return
self.frequencyOffset = offset
if self.frequencyOffset is None:
return
self.shift.setRate(-offset / self.sampleRate)

129
csdr/module/__init__.py Normal file
View File

@ -0,0 +1,129 @@
from pycsdr.modules import Module as BaseModule
from pycsdr.modules import Reader, Writer
from pycsdr.types import Format
from abc import ABCMeta, abstractmethod
from threading import Thread
from io import BytesIO
from subprocess import Popen, PIPE
from functools import partial
import pickle
class Module(BaseModule, metaclass=ABCMeta):
def __init__(self):
self.reader = None
self.writer = None
super().__init__()
def setReader(self, reader: Reader) -> None:
self.reader = reader
def setWriter(self, writer: Writer) -> None:
self.writer = writer
@abstractmethod
def getInputFormat(self) -> Format:
pass
@abstractmethod
def getOutputFormat(self) -> Format:
pass
def pump(self, read, write):
def copy():
while True:
data = None
try:
data = read()
except ValueError:
pass
if data is None or isinstance(data, bytes) and len(data) == 0:
break
write(data)
return copy
class AutoStartModule(Module, metaclass=ABCMeta):
def _checkStart(self) -> None:
if self.reader is not None and self.writer is not None:
self.start()
def setReader(self, reader: Reader) -> None:
super().setReader(reader)
self._checkStart()
def setWriter(self, writer: Writer) -> None:
super().setWriter(writer)
self._checkStart()
@abstractmethod
def start(self):
pass
class ThreadModule(AutoStartModule, Thread, metaclass=ABCMeta):
def __init__(self):
self.doRun = True
super().__init__()
Thread.__init__(self)
@abstractmethod
def run(self):
pass
def stop(self):
self.doRun = False
self.reader.stop()
def start(self):
Thread.start(self)
class PickleModule(ThreadModule):
def getInputFormat(self) -> Format:
return Format.CHAR
def getOutputFormat(self) -> Format:
return Format.CHAR
def run(self):
while self.doRun:
data = self.reader.read()
if data is None:
self.doRun = False
break
io = BytesIO(data.tobytes())
try:
while True:
output = self.process(pickle.load(io))
if output is not None:
self.writer.write(pickle.dumps(output))
except EOFError:
pass
@abstractmethod
def process(self, input):
pass
class PopenModule(AutoStartModule, metaclass=ABCMeta):
def __init__(self):
self.process = None
super().__init__()
@abstractmethod
def getCommand(self):
pass
def start(self):
self.process = Popen(self.getCommand(), stdin=PIPE, stdout=PIPE)
Thread(target=self.pump(self.reader.read, self.process.stdin.write)).start()
Thread(target=self.pump(partial(self.process.stdout.read, 1024), self.writer.write)).start()
def stop(self):
if self.process is not None:
self.process.terminate()
self.process.wait()
self.process = None
self.reader.stop()

14
csdr/module/drm.py Normal file
View File

@ -0,0 +1,14 @@
from csdr.module import PopenModule
from pycsdr.types import Format
class DrmModule(PopenModule):
def getInputFormat(self) -> Format:
return Format.COMPLEX_FLOAT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
# dream -c 6 --sigsrate 48000 --audsrate 48000 -I - -O -
return ["dream", "-c", "6", "--sigsrate", "48000", "--audsrate", "48000", "-I", "-", "-O", "-"]

13
csdr/module/freedv.py Normal file
View File

@ -0,0 +1,13 @@
from pycsdr.types import Format
from csdr.module import PopenModule
class FreeDVModule(PopenModule):
def getInputFormat(self) -> Format:
return Format.SHORT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
return ["freedv_rx", "1600", "-", "-"]

13
csdr/module/m17.py Normal file
View File

@ -0,0 +1,13 @@
from csdr.module import PopenModule
from pycsdr.types import Format
class M17Module(PopenModule):
def getInputFormat(self) -> Format:
return Format.SHORT
def getOutputFormat(self) -> Format:
return Format.SHORT
def getCommand(self):
return ["m17-demod"]

View File

@ -1,36 +0,0 @@
import threading
import logging
logger = logging.getLogger(__name__)
class Output(object):
def send_output(self, t, read_fn):
if not self.supports_type(t):
# TODO rewrite the output mechanism in a way that avoids producing unnecessary data
logger.warning("dumping output of type %s since it is not supported.", t)
threading.Thread(target=self.pump(read_fn, lambda x: None), name="csdr_pump_thread").start()
return
self.receive_output(t, read_fn)
def receive_output(self, t, read_fn):
pass
def pump(self, read, write):
def copy():
run = True
while run:
data = None
try:
data = read()
except ValueError:
pass
if data is None or (isinstance(data, bytes) and len(data) == 0):
run = False
else:
write(data)
return copy
def supports_type(self, t):
return True

View File

@ -1,156 +0,0 @@
import os
import select
import time
import threading
import logging
logger = logging.getLogger(__name__)
class Pipe(object):
READ = "r"
WRITE = "w"
NONE = None
@staticmethod
def create(path, t, encoding=None):
if t == Pipe.READ:
return ReadingPipe(path, encoding=encoding)
elif t == Pipe.WRITE:
return WritingPipe(path, encoding=encoding)
elif t == Pipe.NONE:
return Pipe(path, None, encoding=encoding)
def __init__(self, path, direction, encoding=None):
self.doOpen = True
self.path = "{base}_{myid}".format(base=path, myid=id(self))
self.direction = direction
self.encoding = encoding
self.file = None
os.mkfifo(self.path)
def open(self):
"""
this method opens the file descriptor with an added O_NONBLOCK flag. This gives us a special behaviour for
FIFOS, when they are not opened by the opposing side:
- opening a pipe for writing will throw an OSError with errno = 6 (ENXIO). This is handled specially in the
WritingPipe class.
- opening a pipe for reading will pass through this method instantly, even if the opposing end has not been
opened yet, but the resulting file descriptor will behave as if O_NONBLOCK is set (even if we remove it
immediately here), resulting in empty reads until data is available. This is handled specially in the
ReadingPipe class.
"""
def opener(path, flags):
fd = os.open(path, flags | os.O_NONBLOCK)
os.set_blocking(fd, True)
return fd
self.file = open(self.path, self.direction, encoding=self.encoding, opener=opener)
def close(self):
self.doOpen = False
try:
if self.file is not None:
self.file.close()
os.unlink(self.path)
except FileNotFoundError:
# it seems like we keep calling this twice. no idea why, but we don't need the resulting error.
pass
except Exception:
logger.exception("Pipe.close()")
def __str__(self):
return self.path
class WritingPipe(Pipe):
def __init__(self, path, encoding=None):
self.queue = []
self.queueLock = threading.Lock()
super().__init__(path, "w", encoding=encoding)
self.open()
def open_and_dequeue(self):
"""
This method implements a retry loop that can be interrupted in case the Pipe gets shutdown before actually
being connected.
After the pipe is opened successfully, all data that has been queued is sent in the order it was passed into
write().
"""
retries = 0
while self.file is None and self.doOpen and retries < 10:
try:
super().open()
except OSError as error:
# ENXIO = FIFO has not been opened for reading
if error.errno == 6:
time.sleep(0.1)
retries += 1
else:
raise
# if doOpen is false, opening has been canceled, so no warning in that case.
if self.file is None:
if self.doOpen:
logger.warning("could not open FIFO %s", self.path)
return
with self.queueLock:
for i in self.queue:
self.file.write(i)
self.file.flush()
self.queue = None
def open(self):
"""
This sends the opening operation off to a background thread. If we were to block the thread here, another pipe
may be waiting in the queue to be opened on the opposing side, resulting in a deadlock
"""
threading.Thread(target=self.open_and_dequeue, name="csdr_pipe_thread").start()
def write(self, data):
"""
This method queues all data to be written until the file is actually opened. As soon as a file is available,
it becomes a passthrough.
"""
if self.file is None:
with self.queueLock:
self.queue.append(data)
return
r = self.file.write(data)
self.file.flush()
return r
class ReadingPipe(Pipe):
def __init__(self, path, encoding=None):
super().__init__(path, "r", encoding=encoding)
def open(self):
"""
This method implements an interruptible loop that waits for the file descriptor to be opened and the first
batch of data coming in using repeated select() calls.
:return:
"""
if not self.doOpen:
return
super().open()
while self.doOpen:
(read, _, _) = select.select([self.file], [], [], 1)
if self.file in read:
break
def read(self):
if self.file is None:
self.open()
return self.file.read()
def readline(self):
if self.file is None:
self.open()
return self.file.readline()

17
debian/changelog vendored
View File

@ -1,3 +1,20 @@
openwebrx (1.2.0) UNRELEASED; urgency=low
* Major rewrite of all demodulation components to make use of the new
csdr/pycsdr and digiham/pydigiham demodulator modules
-- Jakob Ketterl <jakob.ketterl@gmx.de> Tue, 03 Aug 2021 13:54:00 +0000
openwebrx (1.1.0) buster hirsute; urgency=low
* Reworked most graphical elements as SVGs for faster loadtimes and crispier
display on hi-dpi displays
* Updated pipelines to match changes in digiham
* Changed D-Star and NXDN integrations to use new decoder from digiham
* Added D-Star and NXDN metadata display
-- Jakob Ketterl <jakob.ketterl@gmx.de> Mon, 02 Aug 2021 16:24:00 +0000
openwebrx (1.0.0) buster hirsute; urgency=low
* Introduced `squelch_auto_margin` config option that allows configuring the
auto squelch level

4
debian/control vendored
View File

@ -10,7 +10,7 @@ Vcs-Git: https://github.com/jketterl/openwebrx.git
Package: openwebrx
Architecture: all
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, csdr (>= 0.17), netcat, owrx-connector (>= 0.4), soapysdr-tools, python3-js8py (>= 0.1), ${python3:Depends}, ${misc:Depends}
Recommends: digiham (>= 0.4), dsd (>= 1.7), sox, direwolf (>= 1.4), wsjtx, runds-connector, hpsdrconnector, aprs-symbols, m17-demod, js8call
Depends: adduser, python3 (>= 3.5), python3-pkg-resources, owrx-connector (>= 0.5), soapysdr-tools, python3-csdr (>= 0.18), ${python3:Depends}, ${misc:Depends}
Recommends: python3-digiham (>= 0.6), direwolf (>= 1.4), wsjtx, runds-connector (>= 0.2), hpsdrconnector, aprs-symbols, m17-demod, js8call, python3-js8py (>= 0.1), nmux (>= 0.18)
Description: multi-user web sdr
Open source, multi-user SDR receiver with a web interface

View File

@ -12,7 +12,7 @@ OWRX_BOOKMARKS_FILE="${OWRX_DATADIR}/bookmarks.json"
case "$1" in
configure|reconfigure)
adduser --system --group --no-create-home --home /nonexistent --quiet "${OWRX_USER}"
usermod -aG plugdev openwebrx
usermod -aG plugdev "${OWRX_USER}"
# create OpenWebRX data directory and set the correct permissions
if [ ! -d "${OWRX_DATADIR}" ] && [ ! -L "${OWRX_DATADIR}" ]; then mkdir "${OWRX_DATADIR}"; fi

View File

@ -5,7 +5,7 @@ ARCH=$(uname -m)
IMAGES="openwebrx-rtlsdr openwebrx-sdrplay openwebrx-hackrf openwebrx-airspy openwebrx-rtlsdr-soapy openwebrx-plutosdr openwebrx-limesdr openwebrx-soapyremote openwebrx-perseus openwebrx-fcdpp openwebrx-radioberry openwebrx-uhd openwebrx-rtltcp openwebrx-runds openwebrx-hpsdr openwebrx-full openwebrx"
ALL_ARCHS="x86_64 armv7l aarch64"
TAG=${TAG:-"latest"}
ARCHTAG="$TAG-$ARCH"
ARCHTAG="${TAG}-${ARCH}"
usage () {
echo "Usage: ${0} [command]"
@ -36,7 +36,7 @@ build () {
push () {
for image in ${IMAGES}; do
docker push jketterl/$image:$ARCHTAG
docker push jketterl/${image}:${ARCHTAG}
done
}
@ -45,11 +45,11 @@ manifest () {
# there's no docker manifest rm command, and the create --amend does not work, so we have to clean up manually
rm -rf "${HOME}/.docker/manifests/docker.io_jketterl_${image}-${TAG}"
IMAGE_LIST=""
for a in $ALL_ARCHS; do
IMAGE_LIST="$IMAGE_LIST jketterl/$image:$TAG-$a"
for a in ${ALL_ARCHS}; do
IMAGE_LIST="${IMAGE_LIST} jketterl/${image}:${TAG}-${a}"
done
docker manifest create jketterl/$image:$TAG $IMAGE_LIST
docker manifest push --purge jketterl/$image:$TAG
docker manifest create jketterl/${image}:${TAG} ${IMAGE_LIST}
docker manifest push --purge jketterl/${image}:${TAG}
done
}

View File

@ -13,6 +13,8 @@ COPY docker/scripts/install-owrx-tools.sh /
RUN /install-owrx-tools.sh && \
rm /install-owrx-tools.sh
COPY docker/files/services/codecserver /etc/services.d/codecserver
ENTRYPOINT ["/init"]
WORKDIR /opt/openwebrx

View File

@ -0,0 +1,2 @@
#!/usr/bin/execlineb -P
/usr/local/bin/codecserver

View File

@ -1,5 +1,5 @@
--- CMakeLists.txt.orig 2021-03-30 15:28:36.956587995 +0200
+++ CMakeLists.txt 2021-03-30 15:29:45.719326832 +0200
--- CMakeLists.txt.orig 2021-09-28 14:33:14.329598412 +0200
+++ CMakeLists.txt 2021-09-28 14:34:23.052345270 +0200
@@ -106,24 +106,6 @@

View File

@ -1,15 +1,6 @@
diff -ur wsjtx-orig/CMake/Modules/Findhamlib.cmake wsjtx/CMake/Modules/Findhamlib.cmake
--- wsjtx-orig/CMake/Modules/Findhamlib.cmake 2021-02-01 20:38:00.947536514 +0100
+++ wsjtx/CMake/Modules/Findhamlib.cmake 2021-02-01 20:39:06.273680932 +0100
@@ -85,4 +85,4 @@
# Handle the QUIETLY and REQUIRED arguments and set HAMLIB_FOUND to
# TRUE if all listed variables are TRUE
include (FindPackageHandleStandardArgs)
-find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES hamlib_LIBRARY_DIRS)
+find_package_handle_standard_args (hamlib DEFAULT_MSG hamlib_INCLUDE_DIRS hamlib_LIBRARIES)
diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
--- wsjtx-orig/CMakeLists.txt 2021-02-01 20:38:00.947536514 +0100
+++ wsjtx/CMakeLists.txt 2021-02-01 23:02:22.503027275 +0100
--- wsjtx-orig/CMakeLists.txt 2021-09-28 14:36:01.731488130 +0200
+++ wsjtx/CMakeLists.txt 2021-09-28 15:51:30.136197625 +0200
@@ -122,7 +122,7 @@
option (WSJT_QDEBUG_TO_FILE "Redirect Qt debuging messages to a trace file.")
option (WSJT_SOFT_KEYING "Apply a ramp to CW keying envelope to reduce transients." ON)
@ -19,18 +10,117 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
option (WSJT_RIG_NONE_CAN_SPLIT "Allow split operation with \"None\" as rig.")
option (WSJT_TRACE_UDP "Debugging option that turns on UDP message protocol diagnostics.")
option (WSJT_BUILD_UTILS "Build simulators and code demonstrators." ON)
@@ -856,7 +856,7 @@
@@ -169,76 +169,7 @@
)
set (wsjt_qt_CXXSRCS
- helper_functions.cpp
- qt_helpers.cpp
- widgets/MessageBox.cpp
- MetaDataRegistry.cpp
- Network/NetworkServerLookup.cpp
revision_utils.cpp
- L10nLoader.cpp
- WFPalette.cpp
- Radio.cpp
- RadioMetaType.cpp
- NonInheritingProcess.cpp
- models/IARURegions.cpp
- models/Bands.cpp
- models/Modes.cpp
- models/FrequencyList.cpp
- models/StationList.cpp
- widgets/FrequencyLineEdit.cpp
- widgets/FrequencyDeltaLineEdit.cpp
- item_delegates/CandidateKeyFilter.cpp
- item_delegates/ForeignKeyDelegate.cpp
- validators/LiveFrequencyValidator.cpp
- GetUserId.cpp
- Audio/AudioDevice.cpp
- Transceiver/Transceiver.cpp
- Transceiver/TransceiverBase.cpp
- Transceiver/EmulateSplitTransceiver.cpp
- Transceiver/TransceiverFactory.cpp
- Transceiver/PollingTransceiver.cpp
- Transceiver/HamlibTransceiver.cpp
- Transceiver/HRDTransceiver.cpp
- Transceiver/DXLabSuiteCommanderTransceiver.cpp
- Network/NetworkMessage.cpp
- Network/MessageClient.cpp
- widgets/LettersSpinBox.cpp
- widgets/HintedSpinBox.cpp
- widgets/RestrictedSpinBox.cpp
- widgets/HelpTextWindow.cpp
- SampleDownloader.cpp
- SampleDownloader/DirectoryDelegate.cpp
- SampleDownloader/Directory.cpp
- SampleDownloader/FileNode.cpp
- SampleDownloader/RemoteFile.cpp
- DisplayManual.cpp
- MultiSettings.cpp
- validators/MaidenheadLocatorValidator.cpp
- validators/CallsignValidator.cpp
- widgets/SplashScreen.cpp
- EqualizationToolsDialog.cpp
- widgets/DoubleClickablePushButton.cpp
- widgets/DoubleClickableRadioButton.cpp
- Network/LotWUsers.cpp
- models/DecodeHighlightingModel.cpp
- widgets/DecodeHighlightingListView.cpp
- models/FoxLog.cpp
- widgets/AbstractLogWindow.cpp
- widgets/FoxLogWindow.cpp
- widgets/CabrilloLogWindow.cpp
- item_delegates/CallsignDelegate.cpp
- item_delegates/MaidenheadLocatorDelegate.cpp
- item_delegates/FrequencyDelegate.cpp
- item_delegates/FrequencyDeltaDelegate.cpp
- item_delegates/SQLiteDateTimeDelegate.cpp
- models/CabrilloLog.cpp
- logbook/AD1CCty.cpp
- logbook/WorkedBefore.cpp
- logbook/Multiplier.cpp
- Network/NetworkAccessManager.cpp
- widgets/LazyFillComboBox.cpp
- widgets/CheckableItemComboBox.cpp
- widgets/BandComboBox.cpp
)
set (wsjt_qtmm_CXXSRCS
@@ -884,8 +815,6 @@
check_type_size (CACHE_ALL HAMLIB_OLD_CACHING)
check_symbol_exists (rig_set_cache_timeout_ms "hamlib/rig.h" HAVE_HAMLIB_CACHING)
-find_package (Portaudio REQUIRED)
-
find_package (Usb REQUIRED)
#
# libhamlib setup
#
-set (hamlib_STATIC 1)
+set (hamlib_STATIC 0)
find_package (hamlib 3 REQUIRED)
find_program (RIGCTL_EXE rigctl)
find_program (RIGCTLD_EXE rigctld)
@@ -1376,60 +1376,6 @@
target_link_libraries (jt9 wsjt_fort wsjt_cxx fort_qt)
endif (${OPENMP_FOUND} OR APPLE)
@@ -1081,9 +1010,6 @@
if (WSJT_GENERATE_DOCS)
add_subdirectory (doc)
endif (WSJT_GENERATE_DOCS)
-if (EXISTS ${CMAKE_SOURCE_DIR}/tests AND IS_DIRECTORY ${CMAKE_SOURCE_DIR}/tests)
- add_subdirectory (tests)
-endif ()
# build a library of package functionality (without and optionally with OpenMP support)
add_library (wsjt_cxx STATIC ${wsjt_CSRCS} ${wsjt_CXXSRCS})
@@ -1341,10 +1267,7 @@
add_library (wsjt_qt STATIC ${wsjt_qt_CXXSRCS} ${wsjt_qt_GENUISRCS} ${GENAXSRCS})
# set wsjtx_udp exports to static variants
target_compile_definitions (wsjt_qt PUBLIC UDP_STATIC_DEFINE)
-target_link_libraries (wsjt_qt Hamlib::Hamlib Boost::log qcp Qt5::Widgets Qt5::Network Qt5::Sql)
-if (WIN32)
- target_link_libraries (wsjt_qt Qt5::AxContainer Qt5::AxBase)
-endif (WIN32)
+target_link_libraries (wsjt_qt Qt5::Core)
# build a library of package Qt functionality used in Fortran utilities
add_library (fort_qt STATIC ${fort_qt_CXXSRCS})
@@ -1408,60 +1331,6 @@
add_subdirectory (map65)
endif ()
-# build the main application
-generate_version_info (wsjtx_VERSION_RESOURCES
@ -84,15 +174,38 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
- )
- endif ()
-endif ()
-target_link_libraries (wsjtx Qt5::SerialPort wsjt_cxx wsjt_qt wsjt_qtmm ${hamlib_LIBRARIES} ${FFTW3_LIBRARIES} ${LIBM_LIBRARIES})
-target_link_libraries (wsjtx Qt5::SerialPort wsjt_cxx wsjt_qt wsjt_qtmm ${FFTW3_LIBRARIES} ${LIBM_LIBRARIES})
-
# make a library for WSJT-X UDP servers
# add_library (wsjtx_udp SHARED ${UDP_library_CXXSRCS})
add_library (wsjtx_udp-static STATIC ${UDP_library_CXXSRCS})
@@ -1492,24 +1438,9 @@
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
endif (WSJT_CREATE_WINMAIN)
@@ -1501,47 +1370,9 @@
add_executable (wsjtx_app_version AppVersion/AppVersion.cpp ${wsjtx_app_version_VERSION_RESOURCES})
target_link_libraries (wsjtx_app_version wsjt_qt)
-generate_version_info (message_aggregator_VERSION_RESOURCES
- NAME message_aggregator
- BUNDLE ${PROJECT_BUNDLE_NAME}
- ICON ${WSJTX_ICON_FILE}
- FILE_DESCRIPTION "Example WSJT-X UDP Message Protocol application"
- )
-add_resources (message_aggregator_RESOURCES /qss ${message_aggregator_STYLESHEETS})
-configure_file (UDPExamples/message_aggregator.qrc.in message_aggregator.qrc @ONLY)
-qt5_add_resources (message_aggregator_RESOURCES_RCC
- ${CMAKE_CURRENT_BINARY_DIR}/message_aggregator.qrc
- contrib/QDarkStyleSheet/qdarkstyle/style.qrc
- )
-add_executable (message_aggregator
- ${message_aggregator_CXXSRCS}
- ${message_aggregator_RESOURCES_RCC}
- ${message_aggregator_VERSION_RESOURCES}
- )
-target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static)
-
-if (WSJT_CREATE_WINMAIN)
- set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
-endif (WSJT_CREATE_WINMAIN)
-
-if (UNIX)
- if (NOT WSJT_SKIP_MANPAGES)
- add_subdirectory (manpages)
@ -114,7 +227,7 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
# install (TARGETS wsjtx_udp EXPORT udp
# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -1528,12 +1459,7 @@
@@ -1560,12 +1391,7 @@
# DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/wsjtx
# )
@ -128,7 +241,7 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
@@ -1546,38 +1472,6 @@
@@ -1578,38 +1404,6 @@
)
endif(WSJT_BUILD_UTILS)
@ -167,7 +280,7 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
install (FILES
cty.dat
cty.dat_copyright.txt
@@ -1586,13 +1480,6 @@
@@ -1618,13 +1412,6 @@
#COMPONENT runtime
)
@ -181,3 +294,27 @@ diff -ur wsjtx-orig/CMakeLists.txt wsjtx/CMakeLists.txt
#
# Mac installer files
#
@@ -1676,22 +1463,6 @@
"${CMAKE_CURRENT_BINARY_DIR}/wsjtx_config.h"
)
-
-if (NOT WIN32 AND NOT APPLE)
- # install a desktop file so wsjtx appears in the application start
- # menu with an icon
- install (
- FILES wsjtx.desktop message_aggregator.desktop
- DESTINATION share/applications
- #COMPONENT runtime
- )
- install (
- FILES icons/Unix/wsjtx_icon.png
- DESTINATION share/pixmaps
- #COMPONENT runtime
- )
-endif (NOT WIN32 AND NOT APPLE)
-
if (APPLE)
set (CMAKE_POSTFLIGHT_SCRIPT
"${wsjtx_BINARY_DIR}/postflight.sh")
Only in wsjtx: .idea

View File

@ -24,7 +24,7 @@ apt-get update
apt-get -y install --no-install-recommends $BUILD_PACKAGES
git clone https://github.com/jketterl/owrx_connector.git
cmakebuild owrx_connector 0.4.0
cmakebuild owrx_connector 0.5.0
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -29,7 +29,7 @@ tar xfz $PACKAGE
git clone https://github.com/jancona/hpsdrconnector.git
pushd hpsdrconnector
git checkout v0.4.2
git checkout v0.6.0
/tmp/go/bin/go build
install -m 0755 hpsdrconnector /usr/local/bin

View File

@ -25,7 +25,7 @@ apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
git clone https://github.com/jketterl/runds_connector.git
cmakebuild runds_connector 0.1.0
cmakebuild runds_connector 0.2.0
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -48,9 +48,9 @@ cd ..
rm -rf sdrplay
rm $BINARY
git clone https://github.com/SDRplay/SoapySDRPlay.git
# latest from master as of 2020-09-04
cmakebuild SoapySDRPlay 105f8a6b3d449982d7ef860790c201aa066b8fa9
git clone https://github.com/pothosware/SoapySDRPlay3.git
# latest from master as of 2021-06-19 (reliability fixes)
cmakebuild SoapySDRPlay3 a869f25364a1f0d5b16169ff908aa21a2ace475d
SUDO_FORCE_REMOVE=yes apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

View File

@ -18,8 +18,8 @@ function cmakebuild() {
cd /tmp
STATIC_PACKAGES="sox libfftw3-bin python3 python3-setuptools netcat-openbsd libsndfile1 liblapack3 libusb-1.0-0 libqt5core5a libreadline7 libgfortran4 libgomp1 libasound2 libudev1 ca-certificates libqt5gui5 libqt5sql5 libqt5printsupport5 libpulse0 libfaad2 libopus0 libboost-program-options1.67.0 libboost-log1.67.0"
BUILD_PACKAGES="wget git libsndfile1-dev libfftw3-dev cmake make gcc g++ liblapack-dev texinfo gfortran libusb-1.0-0-dev qtbase5-dev qtmultimedia5-dev qttools5-dev libqt5serialport5-dev qttools5-dev-tools asciidoctor asciidoc libasound2-dev libudev-dev libhamlib-dev patch xsltproc qt5-default libfaad-dev libopus-dev libgtest-dev libboost-dev libboost-program-options-dev libboost-log-dev libboost-regex-dev"
STATIC_PACKAGES="libfftw3-bin python3 python3-setuptools netcat-openbsd libsndfile1 liblapack3 libusb-1.0-0 libqt5core5a libreadline7 libgfortran4 libgomp1 libasound2 libudev1 ca-certificates libpulse0 libfaad2 libopus0 libboost-program-options1.67.0 libboost-log1.67.0"
BUILD_PACKAGES="wget git libsndfile1-dev libfftw3-dev cmake make gcc g++ liblapack-dev texinfo gfortran libusb-1.0-0-dev qtbase5-dev qtmultimedia5-dev qttools5-dev libqt5serialport5-dev qttools5-dev-tools asciidoctor asciidoc libasound2-dev libudev-dev libhamlib-dev patch xsltproc qt5-default libfaad-dev libopus-dev libboost-dev libboost-program-options-dev libboost-log-dev libboost-regex-dev"
apt-get update
apt-get -y install auto-apt-proxy
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
@ -40,15 +40,6 @@ wget https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s
tar xzf s6-overlay-${PLATFORM}.tar.gz -C /
rm s6-overlay-${PLATFORM}.tar.gz
git clone https://git.code.sf.net/p/itpp/git itpp
cmakebuild itpp bb5c7e95f40e8fdb5c3f3d01a84bcbaf76f3676d
git clone https://github.com/szechyjs/mbelib.git
cmakebuild mbelib 9a04ed5c78176a9965f3d43f7aa1b1f5330e771f
git clone https://github.com/f4exb/dsd.git
cmakebuild dsd f6939f9edbbc6f66261833616391a4e59cb2b3d7
JS8CALL_VERSION=2.2.0
JS8CALL_DIR=js8call
JS8CALL_TGZ=js8call-${JS8CALL_VERSION}.tgz
@ -60,7 +51,7 @@ rm /js8call-hamlib.patch
CMAKE_ARGS="-D CMAKE_CXX_FLAGS=-DJS8_USE_HAMLIB_THREE" cmakebuild ${JS8CALL_DIR}
rm ${JS8CALL_TGZ}
WSJT_DIR=wsjtx-2.3.1
WSJT_DIR=wsjtx-2.5.0
WSJT_TGZ=${WSJT_DIR}.tgz
wget http://physics.princeton.edu/pulsar/k1jt/${WSJT_TGZ}
tar xfz ${WSJT_TGZ}
@ -111,8 +102,7 @@ rm -rf dream
rm dream-2.1.1-svn808.tar.gz
git clone https://github.com/mobilinkd/m17-cxx-demod.git
# latest master as of 2021-04-20
cmakebuild m17-cxx-demod c1d954fd5e5c53d28a2524e99484f832f9dcb826
cmakebuild m17-cxx-demod v2.2
git clone https://github.com/hessu/aprs-symbols /usr/share/aprs-symbols
pushd /usr/share/aprs-symbols

View File

@ -18,8 +18,8 @@ function cmakebuild() {
cd /tmp
STATIC_PACKAGES="libfftw3-bin"
BUILD_PACKAGES="git autoconf automake libtool libfftw3-dev pkg-config cmake make gcc g++"
STATIC_PACKAGES="libfftw3-bin libprotobuf17 libsamplerate0 libicu63"
BUILD_PACKAGES="git autoconf automake libtool libfftw3-dev pkg-config cmake make gcc g++ libprotobuf-dev protobuf-compiler libsamplerate-dev libicu-dev libpython3-dev"
apt-get update
apt-get -y install --no-install-recommends $STATIC_PACKAGES $BUILD_PACKAGES
@ -31,17 +31,34 @@ popd
rm -rf js8py
git clone https://github.com/jketterl/csdr.git
cd csdr
git checkout 0.17.0
autoreconf -i
./configure
make
make install
# latest develop as of 2021-09-22 (template fixes)
cmakebuild csdr 536f3b9eb7cfe5434e9a9f1e807c96115dc9ac10
git clone https://github.com/jketterl/pycsdr.git
cd pycsdr
# latest develop as of 2021-09-22 (first version)
git checkout 52da48a87ef97eb7d337f1b146db66ca453801e4
./setup.py install install_headers
cd ..
rm -rf csdr
rm -rf pycsdr
git clone https://github.com/jketterl/codecserver.git
mkdir -p /usr/local/etc/codecserver
cp codecserver/conf/codecserver.conf /usr/local/etc/codecserver
# latest develop as of 2021-09-24 (new parsing)
cmakebuild codecserver c51254323b32db5b169cdfc39e043eed6d613a77
git clone https://github.com/jketterl/digiham.git
cmakebuild digiham 0.4.0
# latest develop as of 2021-09-22 (post-merge)
cmakebuild digiham 62d2b4581025568263ae8c90d2450b65561b7ce8
git clone https://github.com/jketterl/pydigiham.git
cd pydigiham
# latest develop as of 2021-09-22 (split from digiham)
git checkout b0cc0c35d5ef2ae84c9bb1a02d56161d5bd5bf2f
./setup.py install
cd ..
rm -rf pydigiham
apt-get -y purge --autoremove $BUILD_PACKAGES
apt-get clean

BIN
htdocs/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -5,21 +5,3 @@ html, body
height: 100%;
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
}
.sprite {
background-image: url(../gfx/openwebrx-sprites.png);
display: inline-block;
}
.openwebrx-button.highlighted .sprite {
background-image: linear-gradient(rgba(255,127,0,0.5), rgba(255,127,0,0.5)), url(../gfx/openwebrx-sprites.png);
background-blend-mode: overlay;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and (min-device-pixel-ratio: 2) {
.sprite {
background-image: url(../gfx/openwebrx-sprites-2x.png);
background-size: 198px 77px;
}
}

View File

@ -56,7 +56,9 @@
}
.webrx-top-logo {
width: 261px;
padding: 12px;
filter: drop-shadow(0 0 2.5px rgba(0, 0, 0, .9));
/* overwritten by media queries */
display: none;
}
@ -100,18 +102,6 @@
font-size: 10pt;
}
.openwebrx-rx-details-arrow {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
margin: 0;
padding: 0;
line-height: 0;
display: block;
}
.openwebrx-main-buttons .button {
display: block;
width: 55px;
@ -123,8 +113,10 @@
display: none;
}
.openwebrx-main-buttons .button img {
.openwebrx-main-buttons .button img,
.openwebrx-main-buttons .button svg {
height: 38px;
filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.5));
}
.openwebrx-main-buttons a {
@ -203,47 +195,33 @@
}
/*
* Sprites (images)
* RX details arrow up/down switching
*/
.sprite-panel-status {
background-position: 0 0;
width: 44px;
height: 38px;
.openwebrx-rx-details-arrow {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
margin: 0;
padding: 0;
line-height: 0;
display: block;
}
.sprite-panel-log {
background-position: -44px 0;
width: 38px;
height: 38px;
}
.sprite-panel-receiver {
background-position: -82px 0;
width: 40px;
height: 38px;
}
.sprite-panel-map {
background-position: -122px 0;
width: 38px;
height: 38px;
}
.sprite-panel-settings {
background-position: -160px 0;
width: 38px;
height: 38px;
}
.openwebrx-rx-details-arrow--down .sprite-rx-details-arrow {
background-position: 0 -65px;
width: 43px;
.openwebrx-rx-details-arrow svg {
height: 12px;
}
.openwebrx-rx-details-arrow--up .sprite-rx-details-arrow {
background-position: -43px -65px;
width: 43px;
height: 12px;
.openwebrx-rx-details-arrow .up {
display: none;
}
.openwebrx-rx-details-arrow--up .down {
display: none;
}
.openwebrx-rx-details-arrow--up .up {
display: initial;
}

View File

@ -667,16 +667,6 @@ img.openwebrx-mirror-img
text-align: center;
}
#openwebrx-mute-on
{
color: lime;
}
#openwebrx-mute-off
{
color: white;
}
.openwebrx-panel-slider
{
position: relative;
@ -684,13 +674,6 @@ img.openwebrx-mirror-img
width: 95px;
}
.openwebrx-sliderbtn-img
{
width: 14px;
position:relative;
top: 1px;
}
.openwebrx-panel-line
{
padding-top: 5px;
@ -767,7 +750,7 @@ img.openwebrx-mirror-img
}
.openwebrx-overlay {
position: fixed;
position: absolute;
width: 100%;
height: 100%;
margin: 0;
@ -788,8 +771,7 @@ img.openwebrx-mirror-img
transition: opacity 0.3s linear;
}
#openwebrx-autoplay-overlay img
{
#openwebrx-autoplay-overlay svg {
width: 150px;
}
@ -1001,26 +983,22 @@ img.openwebrx-mirror-img
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.openwebrx-meta-slot > * {
flex: 0;
flex-basis: 1.2em;
flex: 1 0 0;
line-height: 1.2em;
}
.openwebrx-meta-slot, .openwebrx-meta-slot.muted:before {
.openwebrx-meta-slot, .openwebrx-meta-slot .mute {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.openwebrx-meta-slot.muted:before {
display: block;
content: "";
background-image: url("../gfx/openwebrx-mute.png");
background-position: center;
background-repeat: no-repeat;
.openwebrx-meta-slot .mute {
display: none;
cursor: pointer;
position: absolute;
@ -1031,6 +1009,17 @@ img.openwebrx-mirror-img
background-color: rgba(0,0,0,.3);
}
.openwebrx-meta-slot .mute svg {
position: absolute;
top: 50%;
left: 0;
transform: translate(0, -50%);
}
.openwebrx-meta-slot.muted .mute {
display: block;
}
.openwebrx-meta-slot.active {
background-color: #95bbdf;
}
@ -1047,18 +1036,29 @@ img.openwebrx-mirror-img
}
.openwebrx-meta-slot .openwebrx-meta-user-image {
flex: 1;
flex: 0 1 100%;
background-position: center;
background-repeat: no-repeat;
line-height: 0;
overflow: hidden;
}
.openwebrx-meta-slot.active.direct .openwebrx-meta-user-image,
#openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image {
background-image: url("../gfx/openwebrx-directcall.png");
.openwebrx-meta-slot .openwebrx-meta-user-image img {
max-width: 100%;
max-height: 100%;
display: none;
}
.openwebrx-meta-slot.active.group .openwebrx-meta-user-image {
background-image: url("../gfx/openwebrx-groupcall.png");
.openwebrx-meta-slot.active.direct .openwebrx-meta-user-image .directcall,
.openwebrx-meta-slot.active.individual .openwebrx-meta-user-image .directcall,
#openwebrx-panel-metadata-ysf .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall,
#openwebrx-panel-metadata-dstar .openwebrx-meta-slot.active .openwebrx-meta-user-image .directcall {
display: initial;
}
.openwebrx-meta-slot.active.group .openwebrx-meta-user-image .groupcall,
.openwebrx-meta-slot.active.conference .openwebrx-meta-user-image .groupcall {
display: initial;
}
.openwebrx-meta-slot.group .openwebrx-dmr-target:not(:empty):before {
@ -1086,14 +1086,30 @@ img.openwebrx-mirror-img
content: "Down: ";
}
.openwebrx-maps-pin {
background-image: url("../gfx/google_maps_pin.svg");
background-position: center;
background-repeat: no-repeat;
.openwebrx-dstar-yourcall:not(:empty):before {
content: "UR: ";
}
.openwebrx-dstar-departure:not(:empty):before {
content: "RPT1: ";
}
.openwebrx-dstar-destination:not(:empty):before {
content: "RPT2: ";
}
.openwebrx-meta-slot.individual .openwebrx-nxdn-destination:not(:empty):before {
content: "Direct: ";
}
.openwebrx-meta-slot.conference .openwebrx-nxdn-destination:not(:empty):before {
content: "Conference: ";
}
.openwebrx-maps-pin svg {
width: 15px;
height: 15px;
background-size: contain;
display: inline-block;
vertical-align: middle;
}
.openwebrx-message-panel {
@ -1298,75 +1314,51 @@ img.openwebrx-mirror-img
margin: -10px;
}
.sprite-zoom-in {
background-position: 0 -38px;
width: 27px;
.openwebrx-zoom-button svg {
height: 27px;
}
.sprite-zoom-out {
background-position: -27px -38px;
width: 27px;
height: 27px;
}
.sprite-zoom-in-total {
background-position: -54px -38px;
width: 24px;
height: 27px;
}
.sprite-zoom-out-total {
background-position: -78px -38px;
width: 25px;
height: 27px;
}
.sprite-edit {
background-position: -131px -51px;
width: 14px;
.openwebrx-slider-button svg {
position:relative;
top: 1px;
height: 14px;
}
.sprite-trashcan {
background-position: -145px -38px;
width: 14px;
.openwebrx-mute-button svg.muted {
display: none;
}
.openwebrx-mute-button.muted svg.muted {
display: initial;
}
.openwebrx-mute-button.muted svg.unmuted {
display: none;
}
.bookmark .bookmark-actions .openwebrx-button svg {
height: 14px;
}
.sprite-speaker {
width: 14px;
height: 15px;
#openwebrx-waterfall-colors-auto .continuous {
display: none;
}
.openwebrx-mute-button .sprite-speaker {
background-position: -103px -38px;
#openwebrx-waterfall-colors-auto.highlighted .continuous {
display: initial;
}
.openwebrx-mute-button.muted .sprite-speaker {
background-position: -117px -38px;
#openwebrx-waterfall-colors-auto.highlighted .auto {
display: none;
}
.sprite-squelch {
background-position: -131px -38px;
width: 14px;
height: 13px;
.openwebrx-waterfall-container {
flex-grow: 1;
display: flex;
flex-direction: column;
position: relative;
}
.sprite-waterfall-auto {
background-position: -103px -53px;
width: 14px;
height: 11px;
}
.sprite-waterfall-default {
background-position: -117px -53px;
width: 14px;
height: 12px;
}
.sprite-bookmark {
background-position: -159px -38px;
width: 21px;
height: 27px;
.openwebrx-waterfall-container > * {
flex: 0 0 auto;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
htdocs/gfx/favicon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
htdocs/gfx/favicon32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
htdocs/gfx/favicon44.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
htdocs/gfx/favicon64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
htdocs/gfx/favicon96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"><defs><filter id="a" x="-.25" y="-.25" width="1.5" height="1.5" color-interpolation-filters="sRGB"><feFlood flood-color="#000" flood-opacity=".4" result="flood"/><feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/><feGaussianBlur in="composite1" result="blur" stdDeviation="66.6"/><feOffset result="offset"/><feComposite in="SourceGraphic" in2="offset" result="composite2"/></filter></defs><path d="M550.98 541.91c-.99-28.904-4.377-57.939-9.421-86.393-6.111-34.469-13.889-85.002-43.983-107.46-17.404-12.988-39.941-17.249-59.865-25.081-9.697-3.81-18.384-7.594-26.537-11.901-27.518 30.176-63.4 45.962-105.19 45.964-41.774 0-77.652-15.786-105.17-45.964-8.153 4.308-16.84 8.093-26.537 11.901-19.924 7.832-42.461 12.092-59.863 25.081-30.096 22.463-37.873 72.996-43.983 107.46-5.045 28.454-8.433 57.489-9.422 86.393-.766 22.387 10.288 25.525 29.017 32.284 23.453 8.458 47.666 14.737 72.041 19.884 47.077 9.941 95.603 17.582 143.92 17.924 48.318-.343 96.844-7.983 143.92-17.924 24.375-5.145 48.59-11.424 72.041-19.884 18.736-6.757 29.789-9.895 29.023-32.284zM306 325.99c90.56-.01 123.15-90.68 131.68-165.17C448.19 69.06 404.8 0 306 0c-98.78 0-142.19 69.055-131.68 160.82C182.86 235.304 215.434 326 306 325.99z" filter="url(#a)" transform="matrix(.42446 0 0 .42484 70.12 69)" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1 @@
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"><defs><filter id="a" x="-.25" y="-.25" width="1.5" height="1.5" color-interpolation-filters="sRGB"><feFlood flood-color="#000" flood-opacity=".4" result="flood"/><feComposite in="flood" in2="SourceGraphic" operator="in" result="composite1"/><feGaussianBlur in="composite1" result="blur" stdDeviation="66.6"/><feOffset result="offset"/><feComposite in="SourceGraphic" in2="offset" result="composite2"/></filter></defs><g fill="#fff"><g transform="matrix(.42446 0 0 .42484 129.12 42)" filter="url(#a)"><path d="M27.982.002c-98.778 0-142.188 69.056-131.678 160.823 8.54 74.484 41.112 165.183 131.678 165.173 90.558-.01 123.148-90.682 131.678-165.173C170.17 69.063 126.781.002 27.982.002zm277.996 0C207.2.002 163.79 69.058 174.3 160.825c8.54 74.484 41.113 165.183 131.678 165.173 90.559-.01 123.148-90.682 131.678-165.173C448.166 69.063 404.777.002 305.978.002zM-77.209 311.093c-8.153 4.308-16.84 8.09-26.537 11.898-19.924 7.833-42.463 12.095-59.863 25.084-30.095 22.463-37.871 72.996-43.98 107.46-5.045 28.454-8.435 57.492-9.424 86.395-.766 22.388 10.288 25.523 29.015 32.284 23.453 8.458 47.665 14.737 72.04 19.884 47.075 9.941 95.6 17.583 143.916 17.925 46.64-.33 93.461-7.487 138.998-16.923 45.538 9.437 92.359 16.593 138.999 16.923 48.317-.343 96.841-7.984 143.917-17.925 24.374-5.145 48.593-11.424 72.043-19.884 18.736-6.757 29.786-9.894 29.02-32.284h.01c-.99-28.903-4.38-57.941-9.424-86.395-6.111-34.47-13.886-85.002-43.98-107.46-17.404-12.989-39.94-17.252-59.863-25.084-9.697-3.81-18.384-7.59-26.537-11.898-27.517 30.177-63.398 45.962-105.186 45.965-41.773 0-77.65-15.787-105.17-45.965-8.153 4.308-16.84 8.09-26.537 11.898-2.394.941-4.828 1.826-7.284 2.685-2.456-.859-4.89-1.744-7.284-2.685-9.697-3.81-18.383-7.59-26.537-11.898-27.517 30.177-63.397 45.962-105.186 45.965-41.773 0-77.65-15.787-105.17-45.965z"/></g><g transform="matrix(.42446 0 0 .42484 70.12 102)" filter="url(#a)"><path d="M550.98 541.91c-.99-28.904-4.377-57.939-9.421-86.393-6.111-34.469-13.889-85.002-43.983-107.46-17.404-12.988-39.941-17.249-59.865-25.081-9.697-3.81-18.384-7.594-26.537-11.901-27.518 30.176-63.4 45.962-105.19 45.964-41.774 0-77.652-15.786-105.17-45.964-8.153 4.308-16.84 8.093-26.537 11.901-19.924 7.832-42.461 12.092-59.863 25.081-30.096 22.463-37.873 72.996-43.983 107.46-5.045 28.454-8.433 57.489-9.422 86.393-.766 22.387 10.288 25.525 29.017 32.284 23.453 8.458 47.666 14.737 72.041 19.884 47.077 9.941 95.603 17.582 143.92 17.924 48.318-.343 96.844-7.983 143.92-17.924 24.375-5.145 48.59-11.424 72.041-19.884 18.736-6.757 29.789-9.895 29.023-32.284zM306 325.99c90.56-.01 123.15-90.68 131.68-165.17C448.19 69.06 404.8 0 306 0c-98.78 0-142.19 69.055-131.68 160.82C182.86 235.304 215.434 326 306 325.99z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1 +0,0 @@
<svg width="700" height="700" xmlns="http://www.w3.org/2000/svg"><g class="layer"><circle cx="350" cy="350" r="330" stroke="#fff" stroke-width="36" fill="none"/><path d="M195 211v278l366-139-366-139z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

28
htdocs/gfx/svg-defs.svg Normal file
View File

@ -0,0 +1,28 @@
<svg width="80" height="80" xmlns="http://www.w3.org/2000/svg">
<defs>
<g id="top-logo"><g fill="none" stroke="#fff" stroke-linecap="square" stroke-linejoin="round" stroke-width="6"><path d="M517.23 68.215a35.5 33.2 0 0 1-11.936-26.458 35.5 33.2 0 0 1 14.593-25.266M561.62 16.49a35.5 33.2 0 0 1 14.593 25.266 35.5 33.2 0 0 1-11.936 26.458M551.57 29.079a18.4 19 0 0 1 7.564 14.46 18.4 19 0 0 1-6.187 15.142M528.56 58.681a18.4 19 0 0 1-6.187-15.142 18.4 19 0 0 1 7.564-14.46"/></g><g fill="none"><path d="M540.75 40.25v41.5h-322v-.5h322" stroke="#fff" stroke-width="4.5"/><path d="M13 81.5h58zm76 0h128z" stroke="#ccc" stroke-width="5"/></g><g aria-label="OpenWebRX"><path d="M207.85 45.906c0-11.755-5.104-17.787-14.771-17.787-5.877 0-10.363 2.475-13.688 7.579v-6.574h-6.96v42.301h6.96V47.53c0-8.352 4.717-13.379 12.141-13.379 6.264 0 9.357 4.022 9.357 11.987v25.288h6.96zm-53.515.619h-24.747c.696-7.966 5.645-12.528 13.301-12.528 6.805 0 11.368 4.794 11.445 12.528zm6.96 2.01c0-12.683-7.192-20.416-18.483-20.416-12.605 0-20.725 8.662-20.725 22.581 0 13.301 8.352 21.808 21.963 21.808 5.336 0 10.595-1.082 15.621-3.248v-6.573c-4.872 2.63-9.821 3.944-15.235 3.944-9.203 0-14.461-5.104-15.08-14.693h31.939zm-54.907 1.78c0 10.44-4.64 16.394-11.832 16.394-7.192 0-11.832-5.955-11.832-16.395s4.64-16.395 11.832-16.395c7.192 0 11.832 5.955 11.832 16.395zm-23.664 14.77c2.939 5.027 7.347 7.424 13.533 7.424 10.053 0 17.323-8.661 17.323-22.195S106.31 28.12 96.257 28.12c-6.187 0-10.595 2.397-13.533 7.424v-6.419h-6.96v58.387h6.96zm-44.389-51.04c-15.699 0-26.139 11.291-26.139 29.232 0 18.02 10.44 29.232 26.139 29.232s26.061-11.29 26.061-29.232-10.363-29.232-26.061-29.232zm0 6.187c10.827 0 18.019 8.506 18.019 23.045s-7.192 23.045-18.019 23.045-18.096-8.507-18.096-23.045 7.27-23.045 18.096-23.045z" fill="#ccc"/><path d="m480.99 42.658 18.792-27.608h-15.235l-12.296 18.173-12.373-18.173h-15.157l18.792 27.608-19.565 28.768h15.235l13.069-19.256 13.147 19.256h15.157zm-76.405 7.424h4.099c5.026 0 7.269 2.01 9.898 7.347l6.883 13.997h15.467l-7.888-16.008c-2.939-5.955-5.8-9.358-10.13-10.363 7.268-2.243 10.826-7.038 10.826-14.461 0-10.904-6.883-15.544-21.499-15.544h-22.195v56.376h14.54zm6.11-10.053h-6.11V25.568h6.11c5.954 0 8.506 1.933 8.506 7.192s-2.552 7.27-8.507 7.27zm-62.873-4.485V12.653h-6.96v58.773h6.96v-6.342c2.939 5.027 7.347 7.424 13.533 7.424 10.053 0 17.323-8.66 17.323-22.195s-7.269-22.195-17.323-22.195c-6.186 0-10.595 2.398-13.533 7.424zm23.664 14.77c0 10.44-4.64 16.396-11.832 16.396s-11.832-5.955-11.832-16.395 4.64-16.395 11.832-16.395 11.832 5.954 11.832 16.395zm-48.72-3.789H298.02c.696-7.965 5.646-12.528 13.301-12.528 6.806 0 11.368 4.795 11.445 12.528zm6.96 2.011c0-12.683-7.192-20.416-18.483-20.416-12.605 0-20.725 8.661-20.725 22.581 0 13.301 8.352 21.808 21.963 21.808 5.336 0 10.595-1.083 15.621-3.248v-6.573c-4.872 2.63-9.821 3.944-15.235 3.944-9.202 0-14.46-5.104-15.08-14.693h31.94zm-112.75-33.485 14.152 56.376h9.59l11.987-48.952 11.909 48.952h9.589l14.152-56.376h-7.733L268.79 62.688l-11.832-47.637h-8.584l-11.832 47.637-11.832-47.637z" fill="#fff"/></g></g>
<g id="panel-log" stroke="#fff"><g fill="none" stroke-linecap="round" stroke-width="3.5"><path d="M21 57h39M21 48.5h39M21 40h34M21 31.5h39M21 23h39"/></g><g fill="#fff" stroke-linejoin="round" stroke-width="2.1"><path d="M13.5 63.5c0 2.5 2.5 5 5 5h45c2.5 0 5-2.5 5-5v-46c0-2.5-2.5-5-5-5H25l-11.5 16z" fill-opacity=".35" style="mix-blend-mode:normal"/><path d="M25 12.5s-1.323 7.847 4 15c-9.294-1.268-15.5 1-15.5 1"/></g></g>
<g id="panel-status" fill="#fff" stroke="#fff" stroke-linejoin="round" stroke-width="2.1"><g fill-opacity=".35"><path d="M9.683 26.483c-3.13 0-5.666-2.455-5.666-5.483 0-3.029 2.536-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484 0 3.028-2.537 5.483-5.666 5.483zM19.113 44.913c-3.13 0-5.666-2.455-5.666-5.483s2.537-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484s-2.537 5.483-5.666 5.483zM9.683 63.483c-3.13 0-5.666-2.455-5.666-5.483s2.536-5.484 5.666-5.484h48.634c3.13 0 5.666 2.455 5.666 5.484s-2.537 5.483-5.666 5.483z"/></g><path d="M10.06 26.456c-3.322 0-6.016-2.443-6.016-5.456 0-3.013 2.694-5.456 6.016-5.456h5.932c3.322 0 6.016 2.443 6.016 5.456 0 3.013-2.694 5.456-6.016 5.456zM19.48 44.886c-3.317 0-6.007-2.443-6.007-5.456 0-3.013 2.69-5.456 6.007-5.456h32.516c3.317 0 6.007 2.443 6.007 5.456 0 3.014-2.69 5.456-6.007 5.456zM10.052 63.456c-3.318 0-6.008-2.443-6.008-5.456 0-3.013 2.69-5.456 6.008-5.456h24.944c3.318 0 6.008 2.443 6.008 5.456 0 3.013-2.69 5.456-6.008 5.456z"/></g>
<g id="panel-receiver" stroke="#fff" stroke-width="2.1"><path d="M66 29.05 12 10" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="6.8" y="29.05" width="67.5" height="38.5" rx="5" ry="5" fill="#fff" fill-opacity=".35"/><g fill="#fff" stroke-linecap="round" stroke-linejoin="round" fill-opacity=".35"><rect x="12.3" y="34.05" width="28.5" height="9.5" rx="2" ry="2"/><circle cx="56.55" cy="48.55" r="13.5"/><g transform="translate(0 -.5)"><circle cx="17.05" cy="50.55" r="3"/><circle cx="26.3" cy="50.55" r="3"/><circle cx="35.55" cy="50.55" r="3"/><circle cx="17.05" cy="57.05" r="3"/><circle cx="26.3" cy="57.05" r="3"/><circle cx="35.55" cy="57.05" r="3"/></g></g></g>
<g id="panel-map" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.1"><g stroke-opacity=".989"><path d="m30 11-19 8v50l19-5 19 5 20-5V11l-10.844 4.879a11.93 11.93 0 0 1 2.394 7.172c0 2.111-.55 4.09-1.507 5.814h.004L49 47 38.279 29.256h.013a11.922 11.922 0 0 1-1.742-6.205c0-2.905 1.034-5.568 2.752-7.644z" fill="#fff" fill-opacity=".35"/><g fill="none"><path d="M30 11v53M49 47v22M11 59l19-25 19 18 20-6"/></g></g><circle cx="48.55" cy="23.05" r="6.5" fill="none" stroke-opacity=".989"/><path d="M48.551 11.051c-6.627 0-12 5.373-12 12 0 2.274.643 4.393 1.742 6.205h-.013L49 47l10.048-18.135h-.004a11.935 11.935 0 0 0 1.507-5.814c0-6.628-5.372-12-12-12z" fill="#fff" fill-opacity=".2"/></g>
<g id="panel-settings" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.6"><path d="m39.855 10-1 .021c-1.041.04-2.078.134-3.11.282-.546.078-.853.587-.78 1.135a7.876 7.876 0 0 1-.022 1.841 6.504 6.504 0 0 1-5.428 5.428 7.889 7.889 0 0 1-1.992 0 6.501 6.501 0 0 1-4.156-2.463c-.336-.437-.916-.581-1.358-.25a29.998 29.998 0 0 0-6.017 6.016c-.331.442-.188 1.02.25 1.355a6.505 6.505 0 0 1 2.465 4.158 7.885 7.885 0 0 1 0 1.993 6.504 6.504 0 0 1-5.428 5.427 7.862 7.862 0 0 1-1.992-.002c-.461-.072-.904.256-.983.803a30.012 30.012 0 0 0-.002 8.51c.079.547.588.853 1.135.781a7.876 7.876 0 0 1 1.842.022 6.504 6.504 0 0 1 5.428 5.427 7.889 7.889 0 0 1 0 1.992 6.501 6.501 0 0 1-2.463 4.157c-.438.336-.581.915-.25 1.357a29.998 29.998 0 0 0 6.015 6.018c.442.33 1.02.187 1.356-.25a6.505 6.505 0 0 1 4.158-2.465 7.885 7.885 0 0 1 1.992 0 6.504 6.504 0 0 1 5.428 5.428 7.862 7.862 0 0 1-.002 1.992c-.072.46.256.904.803.982a30.012 30.012 0 0 0 8.51.002c.546-.078.853-.59.78-1.136a7.859 7.859 0 0 1 .022-1.84 6.504 6.504 0 0 1 5.428-5.428 7.889 7.889 0 0 1 1.992 0 6.501 6.501 0 0 1 4.156 2.463c.336.437.916.581 1.358.25a29.998 29.998 0 0 0 6.017-6.016c.331-.442.188-1.02-.25-1.355a6.505 6.505 0 0 1-2.465-4.158 7.885 7.885 0 0 1 0-1.993 6.504 6.504 0 0 1 5.428-5.427 7.862 7.862 0 0 1 1.992.002c.461.072.904-.258.983-.805a29.977 29.977 0 0 0 .002-8.508c-.079-.546-.59-.853-1.137-.781a7.859 7.859 0 0 1-1.84-.022 6.504 6.504 0 0 1-5.427-5.427 7.889 7.889 0 0 1 0-1.992 6.501 6.501 0 0 1 2.462-4.157c.438-.336.582-.915.25-1.357a29.998 29.998 0 0 0-6.015-6.018c-.442-.33-1.02-.187-1.356.25a6.505 6.505 0 0 1-4.158 2.465 7.885 7.885 0 0 1-1.992 0 6.504 6.504 0 0 1-5.428-5.428 7.862 7.862 0 0 1 .002-1.992c.072-.46-.256-.904-.803-.982a30.002 30.002 0 0 0-4.4-.305zM40 26.5A13.5 13.5 0 0 1 53.5 40 13.5 13.5 0 0 1 40 53.5 13.5 13.5 0 0 1 26.5 40 13.5 13.5 0 0 1 40 26.5z" fill="#fff" fill-opacity=".35"/><circle cx="40" cy="40" r="13.5" fill="#fff" fill-opacity=".2"/><circle cx="40" cy="40" r="8" fill="none"/></g>
<g id="zoom-in" stroke="#fff"><circle cx="31.75" cy="32" r="27.5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/><path d="m57.147 47.242 20.644 20.645a4 4 90 0 1 0 5.657l-3.748 3.748a4 4 180 0 1-5.656 0L47.743 56.647" fill="#fff" stroke-width="2.1"/><path d="M31.75 18.5v27M18.25 32h27" fill="none" stroke-linecap="round" stroke-width="10"/></g>
<g id="zoom-out" stroke="#fff"><circle cx="31.75" cy="32" r="27.5" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="8"/><path d="m57.147 47.242 20.644 20.645a4 4 90 0 1 0 5.657l-3.748 3.748a4 4 180 0 1-5.656 0L47.743 56.647" fill="#fff" stroke-width="2.1"/><path d="M18.25 32h27" fill="none" stroke-linecap="round" stroke-width="10"/></g>
<g id="zoom-out-total" stroke="#fff"><circle cx="44.001" cy="51.307" r="16.326" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.75"/><path d="M59.079 60.356 71.6 72.877a2 2 90 0 1 0 2.829l-2.755 2.754a2 2 180 0 1-2.828 0L53.496 65.94" fill="#fff" stroke-width="1.247"/><path d="m20.893 1.244-16.199 16.9 16.199 16.9V21.698H59.5v13.346l16.199-16.9L59.5 1.244V14.7H20.893z" fill="#fff"/></g>
<g id="zoom-in-total" stroke="#fff"><circle cx="44.701" cy="51.307" r="16.326" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.75"/><path d="M59.779 60.356 72.3 72.877a2 2 90 0 1 0 2.829l-2.755 2.754a2 2-180 0 1-2.828 0L54.196 65.94" fill="#fff" stroke-width="1.247"/><path d="M4.8 1.244v33.801L17.595 21.7H62.4l12.793 13.346v-33.8L62.296 14.698H17.698z" fill="#fff"/></g>
<g id="speaker" stroke="#fff"><path d="M33.5 7.15 16.74 25.201H2.65v31.7h16.041L33.5 72.848z" fill="#fff" stroke-width="2.1"/><g fill="none"><path d="M46.336 16.041A20 30 0 0 1 54.3 40a20 30 0 0 1-7.964 23.959" stroke-width="3"/><path d="M52.716 9.652A30.6 38 0 0 1 64.9 40a30.6 38 0 0 1-12.184 30.348" stroke-width="4.2"/><path d="M59.035 2.065A41.1 47.5 0 0 1 75.4 40a41.1 47.5 0 0 1-16.365 37.935" stroke-width="5.4"/></g></g>
<g id="speaker-muted"><path d="M33.5 7.15 16.74 25.201H2.65v31.7h16.041L33.5 72.848z" fill="#fff" stroke="#fff" stroke-width="2.1"/></g>
<g id="waterfall-auto"><path d="M33.512 72.05s3.393-64.129 8.24-64.093c4.48.034 4.392 28.286 9.98 28.198 4.775-.074 5.279-13.721 6.33-17.88 1.013-4.004 2.915-5.172 5.321-4.894 2.993.346 8.205 5.925 10.588 13.761 4.16 13.677 5.53 44.907 5.53 44.907z" fill="#ccc" stroke="#ccc"/><path d="M29.3 68.8 18.4 54 7.5 68.8zM7.5 11.2 18.4 26l10.9-14.8zM18.4 26v28" fill="#fff" stroke="#fff" stroke-width="7.5"/></g>
<g id="waterfall-default"><path d="M36.356 75.295s3.187-34.502 7.74-34.482c4.207.018 4.125 15.218 9.374 15.171 4.485-.04 4.958-7.382 5.945-9.62.951-2.154 2.738-2.782 4.998-2.632 2.812.186 7.707 3.187 9.945 7.403 3.906 7.358 5.193 24.16 5.193 24.16z" fill="#ccc" stroke="#ccc" stroke-width=".711"/><path d="M18.4 25.424v29.075m-10.9 0L18.4 69.3l10.9-14.801zm10.9-43.875L7.5 25.425h21.801z" fill="#fff" stroke="#fff" stroke-width="7.5"/></g>
<g id="squelch" fill="#fff" stroke-width=".767"><path d="M33.333 24.7c-.058-14.132-5.913-21.9-16.58-21.9-10.145 0-16 7.673-16 20.963 0 10.575 3.305 15.722 11.884 18.343l5.913 1.872c5.797 1.778 7.942 4.399 7.942 9.92 0 5.71-3.246 9.172-8.521 9.172-5.913 0-9.218-4.024-9.508-11.324H0C.521 66.345 6.724 74.3 17.506 74.3c10.899 0 17.333-8.142 17.333-22.087 0-10.762-3.362-16.378-11.188-18.81l-6.609-2.06c-6.203-1.965-8-3.93-8-8.89 0-5.148 2.782-8.423 7.304-8.423 5.507 0 8.58 3.743 8.87 10.669zM75.362 62.508C78.202 56.893 80 47.534 80 38.363c0-10.014-2.203-19.653-5.913-26.11C70.435 5.887 65.681 2.8 59.653 2.8S48.87 5.888 45.218 12.252C41.45 18.71 39.305 28.35 39.305 38.55s2.203 19.84 5.913 26.298c3.652 6.364 8.406 9.452 14.435 9.452 4.405 0 7.594-1.404 10.956-4.68l4.986 7.581L80 69.621zM64.638 46.13l-4.406 7.58 4.464 6.833c-1.391 1.123-3.246 1.778-5.101 1.778C52.638 62.321 48 52.869 48 38.549c0-14.412 4.58-23.771 11.652-23.771s11.652 9.358 11.652 23.864c0 5.615-.638 10.67-1.913 14.787z"/></g>
<g id="trashcan"><path d="M56.667 13.333V6.666A6.67 6.67 0 0 0 50 0H30a6.67 6.67 0 0 0-6.666 6.666v6.667H6.667V20h6.666v53.333A6.67 6.67 0 0 0 20 79.999h40a6.67 6.67 0 0 0 6.667-6.666V20h6.666v-6.667zM30 6.666h20v6.667H30zm30 66.667H20V20h40zM36.667 26.666H30v40h6.667zm13.333 0h-6.667v40H50z" fill="#fff"/></g>
<g id="edit"><path d="m52.5 7.5-45 45v20h20l45-45zm12.93 20-4.697 4.697-12.93-12.93L52.5 14.57zM15.302 51.768l4.06-4.06c1.533 1.125 3.575 2.002 5.81 2.002 2.148 0 4.453-.8 6.595-2.942l10-10c2.52-2.52 4.622-7.728.968-12.43l1.535-1.535 12.93 12.93L28.23 64.698zm22.93-18.535-10 10c-2.062 2.058-4.007 1.595-5.27.875L39.14 27.93c1.465 2.485-.27 4.643-.907 5.303zM12.5 56.035 23.965 67.5H12.5z" fill="#fff"/></g>
<g id="bookmark"><path d="M62.5 0h-45A7.5 7.5 0 0 0 10 7.5V80l30-17.5L70 80V7.5A7.5 7.5 0 0 0 62.5 0zm0 66.942L40 53.817 17.5 66.942V8.437a.938.938 0 0 1 .938-.938h43.124c.518 0 .938.42.938.937z" fill="#fff"/></g>
<g id="rx-details-arrow-down"><path d="M5 0C2.5 0 0 2.5 0 5v7h43V5c0-2.5-2.5-5-5-5zm8 4h17l-8.5 6.5z" fill="#999" fill-opacity=".196"/><path d="M13 4h17l-8.5 6.5z" fill="#848484" fill-opacity=".592"/></g>
<g id="rx-details-arrow-up"><path d="M5 0C2.5 0 0 2.5 0 5v7h43V5c0-2.5-2.5-5-5-5zm16.5 3L30 9.5H13z" fill="#999" fill-opacity=".196"/><path d="M30 9.5H13L21.5 3z" fill="#848484" fill-opacity=".592"/></g>
<g id="maps-pin" transform="translate(-965.78 -331.788) scale(1.1856)"><path d="M817.11 282.97c-1.258 1.343-2.046 3.299-2.016 5.139.064 3.845 1.797 5.3 4.569 10.592.998 2.328 2.04 4.792 3.031 8.873.138.602.272 1.16.334 1.21.062.048.197-.513.334-1.115.991-4.081 2.033-6.543 3.032-8.872 2.77-5.291 4.504-6.747 4.568-10.592.03-1.84-.76-3.797-2.018-5.14-1.437-1.534-3.605-2.67-5.916-2.717-2.311-.047-4.48 1.088-5.918 2.622z" fill="#ff4646" stroke="#d73534"/><circle cx="823.03" cy="288.25" r="3.035" fill="#590000"/></g>
<g id="play-button"><circle cx="350" cy="350" r="330" fill="none" stroke="#fff" stroke-width="36"/><path d="M195 211v278l366-139z" fill="#fff"/></g>
<g id="meta-mute" stroke="#fff" stroke-width="5"><path stroke-linejoin="round" style="paint-order:fill" fill="none" d="m21.989 47.699 17.4 15.051V13.769L22.235 28.606H6v19.093z" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/><path d="m48.652 50.27 20.743-24.299M69.395 50.27 48.652 25.971" stroke-linecap="round" transform="matrix(5.3513 0 0 5.3723 -.73 -1.542)"/></g>
<g id="waterfall-continuous"><g stroke="#fff" stroke-width="8"><path d="M5 40A35 35 0 0 1 26.606 7.664a35 35 0 0 1 38.143 7.587" fill="none"/><path d="m68.284 11.716 2.828 9.9-9.899-2.829z" fill="#fff"/></g><path d="m48.008 48.144 2.816 8.624h9.035L45.075 14h-9.739L20.141 56.768h8.976l2.875-8.624zm-2.405-7.333H34.456l5.573-16.72z" fill="#fff" aria-label="A"/><g stroke="#fff" stroke-width="8"><path d="M75 40a35 35 0 0 1-21.606 32.336 35 35 0 0 1-38.143-7.587" fill="none"/><path d="m11.716 68.284-2.828-9.9 9.899 2.829z" fill="#fff"/></g></g>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,22 +1,25 @@
<div class="webrx-top-container">
<div class="webrx-top-bar">
<a href="https://www.openwebrx.de/" target="_blank"><img src="${document_root}static/gfx/openwebrx-top-logo.png" class="webrx-top-logo" alt="OpenWebRX Logo"/></a>
<a href="https://www.openwebrx.de/" target="_blank"><svg viewBox="0 0 591 100" class="webrx-top-logo"><title>Visit the OpenWebRX homepage</title><use xlink:href="${document_root}static/gfx/svg-defs.svg#top-logo"></use></svg></a>
<img class="webrx-rx-avatar openwebrx-photo-trigger" src="${document_root}static/gfx/openwebrx-avatar.png" alt="Receiver avatar"/>
<div class="webrx-rx-texts openwebrx-photo-trigger">
<h1 class="webrx-rx-title">${receiver_name}</h1>
<div class="webrx-rx-desc">${receiver_location} | Loc: ${locator}, ASL: ${receiver_asl} m</div>
</div>
<section class="openwebrx-main-buttons">
<div class="button" data-toggle-panel="openwebrx-panel-status"><span class="sprite sprite-panel-status"></span><br/>Status</div>
<div class="button" data-toggle-panel="openwebrx-panel-log"><span class="sprite sprite-panel-log"></span><br/>Log</div>
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><span class="sprite sprite-panel-receiver"></span><br/>Receiver</div>
<a class="button" href="${document_root}map" target="openwebrx-map"><span class="sprite sprite-panel-map"></span><br/>Map</a>
<a class="button" href="${document_root}settings" target="openwebrx-settings"><span class="sprite sprite-panel-settings"></span><br/>Settings</a>
<div class="button" data-toggle-panel="openwebrx-panel-status"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-status"></use></svg><br/>Status</div>
<div class="button" data-toggle-panel="openwebrx-panel-log"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-log"></use></svg><br/>Log</div>
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-receiver"></use></svg><br/>Receiver</div>
<a class="button" href="${document_root}map" target="openwebrx-map"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-map"></use></svg><br/>Map</a>
<a class="button" href="${document_root}settings" target="openwebrx-settings"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-settings"></use></svg><br/>Settings</a>
</section>
</div>
<div class="openwebrx-description-container">
<div class="webrx-rx-photo-title">${photo_title}</div>
<div class="webrx-rx-photo-desc">${photo_desc}</div>
</div>
<a class="openwebrx-rx-details-arrow openwebrx-rx-details-arrow--down openwebrx-photo-trigger"><span class="sprite sprite-rx-details-arrow"></span></a>
<a class="openwebrx-rx-details-arrow openwebrx-rx-details-arrow--down openwebrx-photo-trigger">
<svg class="down" viewBox="0 0 43 12"><use xlink:href="static/gfx/svg-defs.svg#rx-details-arrow-down"></use></svg>
<svg class="up" viewBox="0 0 43 12"><use xlink:href="static/gfx/svg-defs.svg#rx-details-arrow-up"></use></svg>
</a>
</div>

View File

@ -23,7 +23,14 @@
<html>
<head>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" sizes="16x16 32x32" />
<link rel="icon" type="image/png" sizes="32x32" href="static/gfx/favicon32.png" />
<link rel="icon" type="image/png" sizes="44x44" href="static/gfx/favicon44.png" />
<link rel="icon" type="image/png" sizes="64x64" href="static/gfx/favicon64.png" />
<link rel="icon" type="image/png" sizes="96x96" href="static/gfx/favicon96.png" />
<link rel="icon" type="image/png" sizes="128x128" href="static/gfx/favicon128.png" />
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta name="msapplication-TileImage" content="mstile-144x144.png">
<script src="compiled/receiver.js"></script>
<link rel="stylesheet" type="text/css" href="static/lib/nanoscroller.css" />
<link rel="stylesheet" type="text/css" href="static/css/openwebrx.css" />
@ -34,6 +41,7 @@
<body onload="openwebrx_init();">
<div id="webrx-page-container">
${header}
<div class="openwebrx-waterfall-container">
<div id="openwebrx-frequency-container">
<div id="openwebrx-bookmarks-container"></div>
<div id="openwebrx-scale-container">
@ -47,6 +55,10 @@
</div>
<div id="openwebrx-panels-container">
<div id="openwebrx-panels-container-left">
<div class="openwebrx-panel" data-panel-name="client-under-devel" style="width: 245px; background-color: Red;">
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
<br />We're working on the code right now, so the application might fail.
</div>
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" style="display: none; width: 619px;" data-panel-name="digimodes">
<div id="openwebrx-digimode-canvas-container">
<div id="openwebrx-digimode-select-channel"></div>
@ -65,26 +77,63 @@
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-ysf" style="display: none;" data-panel-name="metadata-ysf">
<div class="openwebrx-meta-slot">
<div class="openwebrx-ysf-mode"></div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
</div>
<div class="openwebrx-ysf-source"><span class="location"></span><span class="callsign"></span></div>
<div class="openwebrx-ysf-up"></div>
<div class="openwebrx-ysf-down"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dstar" style="display: none;" data-panel-name="metadata-dstar">
<div class="openwebrx-meta-slot">
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
</div>
<div class="openwebrx-dstar-ourcall"><span class="location"></span><span class="callsign"></span></div>
<div class="openwebrx-dstar-message"></div>
<div class="openwebrx-dstar-yourcall"></div>
<div class="openwebrx-dstar-departure"></div>
<div class="openwebrx-dstar-destination"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-nxdn" style="display: none;" data-panel-name="metadata-nxdn">
<div class="openwebrx-meta-slot">
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-nxdn-source"></div>
<div class="openwebrx-nxdn-name"></div>
<div class="openwebrx-nxdn-destination"></div>
</div>
</div>
<div class="openwebrx-panel openwebrx-meta-panel" id="openwebrx-panel-metadata-dmr" style="display: none;" data-panel-name="metadata-dmr">
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 1</div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-dmr-id"></div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
<div class="mute">
<svg viewBox="0 0 400 400"><use xlink:href="static/gfx/svg-defs.svg#meta-mute"></use></svg>
</div>
</div>
<div class="openwebrx-meta-slot openwebrx-dmr-timeslot-panel">
<div class="openwebrx-dmr-slot">Timeslot 2</div>
<div class="openwebrx-meta-user-image"></div>
<div class="openwebrx-dmr-id"></div>
<div class="openwebrx-meta-user-image">
<img class="directcall" src="static/gfx/openwebrx-directcall.svg">
<img class="groupcall" src="static/gfx/openwebrx-groupcall.svg">
</div>
<div class="openwebrx-dmr-id"><span class="location"></span><span class="dmr-id"></span></div>
<div class="openwebrx-dmr-name"></div>
<div class="openwebrx-dmr-target"></div>
<div class="mute">
<svg viewBox="0 0 400 400"><use xlink:href="static/gfx/svg-defs.svg#meta-mute"></use></svg>
</div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" style="width: 619px;">
@ -117,7 +166,7 @@
<div class="webrx-mouse-freq"></div>
</div>
<div class="openwebrx-button openwebrx-square-button openwebrx-bookmark-button" style="display:none;" title="Add bookmark...">
<span class="sprite sprite-bookmark"></span>
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#bookmark"></use></svg>
</div>
</div>
<div class="openwebrx-panel-line">
@ -126,22 +175,32 @@
</div>
<div class="openwebrx-modes openwebrx-panel-line"></div>
<div class="openwebrx-panel-line">
<div title="Mute on/off" class="openwebrx-button openwebrx-mute-button" onclick="toggleMute();"><span class="sprite sprite-speaker openwebrx-sliderbtn-img"></span></div>
<div title="Mute on/off" class="openwebrx-button openwebrx-slider-button openwebrx-mute-button" onclick="toggleMute();">
<svg class="unmuted" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#speaker"></use></svg>
<svg class="muted" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#speaker-muted"></use></svg>
</div>
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button"><span class="sprite sprite-waterfall-auto openwebrx-sliderbtn-img"></span></div>
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button openwebrx-slider-button">
<svg class="auto" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-auto"></use></svg>
<svg class="continuous" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-continuous"></use></svg>
</div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div title="Auto-set squelch level" class="openwebrx-squelch-auto openwebrx-button"><span class="sprite sprite-squelch openwebrx-sliderbtn-img"></span></div>
<div title="Auto-set squelch level" class="openwebrx-squelch-auto openwebrx-button openwebrx-slider-button">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#squelch"></use></svg>
</div>
<input title="Squelch" class="openwebrx-squelch-slider openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><span class="sprite sprite-waterfall-default openwebrx-sliderbtn-img"></span></div>
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button openwebrx-slider-button" onclick="waterfallColorsDefault()">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-default"></use></svg>
</div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
</div>
<div class="openwebrx-panel-line">
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"><span class="sprite sprite-zoom-in"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"><span class="sprite sprite-zoom-out"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><span class="sprite sprite-zoom-in-total"></span></div>
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><span class="sprite sprite-zoom-out-total"></span></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomInOneStep();" title="Zoom in one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-in"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomOutOneStep();" title="Zoom out one step"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-out"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomInTotal();" title="Zoom in totally"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-in-total"></use></svg></div>
<div class="openwebrx-button openwebrx-square-button openwebrx-zoom-button" onclick="zoomOutTotal();" title="Zoom out totally"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#zoom-out-total"></use></svg></div>
<div id="openwebrx-smeter-db">0 dB</div>
</div>
<div class="openwebrx-panel-line">
@ -152,7 +211,6 @@
</div>
</div>
</div>
</div>
<div id="openwebrx-error-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<div>This receiver is currently unavailable due to technical issues.</div>
@ -160,6 +218,14 @@
<div class="errormessage"></div>
</div>
</div>
</div>
</div>
<div id="openwebrx-autoplay-overlay" class="openwebrx-overlay" style="display:none;">
<div class="overlay-content">
<svg viewBox="0 0 700 700"><use xlink:href="static/gfx/svg-defs.svg#play-button"></use></svg>
<div>Start OpenWebRX</div>
</div>
</div>
<div id="openwebrx-dialog-bookmark" class="openwebrx-dialog" style="display:none;">
<form>
<div class="form-field">

View File

@ -282,7 +282,7 @@ AudioEngine.prototype.processAudio = function(data, resampler) {
var buffer;
if (this.compression === "adpcm") {
//resampling & ADPCM
buffer = this.audioCodec.decode(new Uint8Array(data));
buffer = this.audioCodec.decodeWithSync(new Uint8Array(data));
} else {
buffer = new Int16Array(data);
}
@ -328,6 +328,10 @@ ImaAdpcmCodec.prototype.reset = function() {
this.stepIndex = 0;
this.predictor = 0;
this.step = 0;
this.synchronized = 0;
this.syncWord = "SYNC";
this.syncCounter = 0;
this.skip = 0;
};
ImaAdpcmCodec.imaIndexTable = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 ];
@ -353,6 +357,43 @@ ImaAdpcmCodec.prototype.decode = function(data) {
return output;
};
ImaAdpcmCodec.prototype.decodeWithSync = function(data) {
var output = new Int16Array(data.length * 2);
var index = this.skip;
var oi = 0;
while (index < data.length) {
while (this.synchronized < 4 && index < data.length) {
if (data[index] === this.syncWord.charCodeAt(this.synchronized)) {
this.synchronized++;
} else {
this.synchronized = 0;
}
index++;
if (this.synchronized === 4) {
if (index + 4 < data.length) {
var syncData = new Int16Array(data.buffer.slice(index, index + 4));
this.stepIndex = syncData[0];
this.predictor = syncData[1];
}
this.syncCounter = 1000;
index += 4;
break;
}
}
while (index < data.length) {
if (this.syncCounter-- < 0) {
this.synchronized = 0;
break;
}
output[oi++] = this.decodeNibble(data[index] & 0x0F);
output[oi++] = this.decodeNibble(data[index] >> 4);
index++;
}
}
this.skip = index - data.length;
return output.slice(0, oi);
};
ImaAdpcmCodec.prototype.decodeNibble = function(nibble) {
this.stepIndex += ImaAdpcmCodec.imaIndexTable[nibble];
this.stepIndex = Math.min(Math.max(this.stepIndex, 0), 88);

View File

@ -87,8 +87,8 @@ BookmarkBar.prototype.render = function(){
var $bookmark = $(
'<div class="bookmark" data-source="' + b.source + '"' + (b.editable?' editable="editable"':'') + '>' +
'<div class="bookmark-actions">' +
'<div class="openwebrx-button action" data-action="edit"><span class="sprite sprite-edit"></span></div>' +
'<div class="openwebrx-button action" data-action="delete"><span class="sprite sprite-trashcan"><span></div>' +
'<div class="openwebrx-button action" data-action="edit"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#edit"></use></svg></div>' +
'<div class="openwebrx-button action" data-action="delete"><svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#trashcan"></use></svg></div>' +
'</div>' +
'<div class="bookmark-content">' + b.name + '</div>' +
'</div>'

View File

@ -13,6 +13,8 @@ Filter.prototype.getLimits = function() {
max_bw = 100000;
} else if (this.demodulator.get_modulation() === 'drm') {
max_bw = 50000;
} else if (this.demodulator.get_modulation() === "freedv") {
max_bw = 4000;
} else {
max_bw = (audioEngine.getOutputRate() / 2) - 1;
}

View File

@ -89,7 +89,7 @@ DemodulatorPanel.prototype.setMode = function(requestedModulation) {
return;
}
if (!mode.isAvailable()) {
divlog('Modulation "' + mode.name + '" not supported. Please check requirements', true);
divlog('Modulation "' + mode.name + '" not supported. Please check the feature report', true);
return;
}

View File

@ -70,7 +70,7 @@ Js8Thread.prototype.getMessageDuration = function() {
Js8Thread.prototype.getMode = function() {
// we filter messages by mode, so the first one is as good as any
if (!this.messages.length) return;
return this.messages[0].mode;
return this.messages[0].js8mode;
};
Js8Thread.prototype.acceptsMode = function(mode) {
@ -117,6 +117,10 @@ Js8Threader = function(el){
Js8Threader.prototype = new MessagePanel();
Js8Threader.prototype.supportsMessage = function(message) {
return message['mode'] === 'JS8';
};
Js8Threader.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -158,7 +162,7 @@ Js8Threader.prototype.pushMessage = function(message) {
var thread;
// only look for exising threads if the message is not a starting message
if ((message.thread_type & 1) === 0) {
thread = this.findThread(message.freq, message.mode);
thread = this.findThread(message.freq, message.js8mode);
}
if (!thread) {
var line = $("<tr></tr>");

View File

@ -4,6 +4,10 @@ function MessagePanel(el) {
this.initClearButton();
}
MessagePanel.prototype.supportsMessage = function(message) {
return false;
};
MessagePanel.prototype.render = function() {
};
@ -46,10 +50,17 @@ MessagePanel.prototype.initClearButton = function() {
function WsjtMessagePanel(el) {
MessagePanel.call(this, el);
this.initClearTimer();
this.qsoModes = ['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65'];
this.beaconModes = ['WSPR', 'FST4W'];
this.modes = [].concat(this.qsoModes, this.beaconModes);
}
WsjtMessagePanel.prototype = new MessagePanel();
WsjtMessagePanel.prototype.supportsMessage = function(message) {
return this.modes.indexOf(message['mode']) >= 0;
};
WsjtMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -78,14 +89,14 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
return $('<div/>').text(input).html()
};
if (['FT8', 'JT65', 'JT9', 'FT4', 'FST4', 'Q65'].indexOf(msg['mode']) >= 0) {
if (this.qsoModes.indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
if (matches && matches[2] !== 'RR73') {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>';
} else {
linkedmsg = html_escape(linkedmsg);
}
} else if (['WSPR', 'FST4W'].indexOf(msg['mode']) >= 0) {
} else if (this.beaconModes.indexOf(msg['mode']) >= 0) {
matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
if (matches) {
linkedmsg = html_escape(matches[1]) + '<a href="map?locator=' + matches[2] + '" target="openwebrx-map">' + matches[2] + '</a>' + html_escape(matches[3]);
@ -108,7 +119,7 @@ WsjtMessagePanel.prototype.pushMessage = function(msg) {
$.fn.wsjtMessagePanel = function(){
if (!this.data('panel')) {
this.data('panel', new WsjtMessagePanel(this));
};
}
return this.data('panel');
};
@ -119,6 +130,10 @@ function PacketMessagePanel(el) {
PacketMessagePanel.prototype = new MessagePanel();
PacketMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'APRS';
};
PacketMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -180,6 +195,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
}
} else if (msg.lat && msg.lon) {
classes.push('openwebrx-maps-pin');
overlay = '<svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg>';
}
var attrs = [
'class="' + classes.join(' ') + '"',
@ -205,7 +221,7 @@ PacketMessagePanel.prototype.pushMessage = function(msg) {
$.fn.packetMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new PacketMessagePanel(this));
};
}
return this.data('panel');
};
@ -216,6 +232,10 @@ PocsagMessagePanel = function(el) {
PocsagMessagePanel.prototype = new MessagePanel();
PocsagMessagePanel.prototype.supportsMessage = function(message) {
return message['mode'] === 'Pocsag';
};
PocsagMessagePanel.prototype.render = function() {
$(this.el).append($(
'<table>' +
@ -242,6 +262,6 @@ PocsagMessagePanel.prototype.pushMessage = function(msg) {
$.fn.pocsagMessagePanel = function() {
if (!this.data('panel')) {
this.data('panel', new PocsagMessagePanel(this));
};
}
return this.data('panel');
};

View File

@ -22,20 +22,31 @@ function DmrMetaSlot(el) {
DmrMetaSlot.prototype.update = function(data) {
this.el[data['sync'] ? "addClass" : "removeClass"]("sync");
if (data['sync'] && data['sync'] === "voice") {
this.setId(data['additional'] && data['additional']['callsign'] || data['source']);
this.setId(data['additional'] && data['additional']['callsign'] || data['talkeralias'] || data['source']);
this.setName(data['additional'] && data['additional']['fname']);
this.setMode(['group', 'direct'].includes(data['type']) ? data['type'] : undefined);
this.setTarget(data['target']);
this.setLocation(data['lat'], data['lon'], this.getCallsign(data));
this.el.addClass("active");
} else {
this.clear();
}
};
DmrMetaSlot.prototype.getCallsign = function(data) {
if ('additional' in data) {
return data['additional']['callsign'];
}
if ('talkeralias' in data) {
var matches = /^([A-Z0-9]+)(\s.*)?$/.exec(data['talkeralias']);
if (matches) return matches[1];
}
};
DmrMetaSlot.prototype.setId = function(id) {
if (this.id === id) return;
this.id = id;
this.el.find('.openwebrx-dmr-id').text(id || '');
this.el.find('.openwebrx-dmr-id .dmr-id').text(id || '');
}
DmrMetaSlot.prototype.setName = function(name) {
@ -59,11 +70,23 @@ DmrMetaSlot.prototype.setTarget = function(target) {
this.el.find('.openwebrx-dmr-target').text(target || '');
}
DmrMetaSlot.prototype.setLocation = function(lat, lon, callsign) {
var hasLocation = lat && lon && callsign && callsign != '';
if (hasLocation === this.hasLocation && this.callsign === callsign) return;
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-dmr-id .location').html(html);
}
DmrMetaSlot.prototype.clear = function() {
this.setId();
this.setName();
this.setMode();
this.setTarget();
this.setLocation();
this.el.removeClass("active");
};
@ -112,7 +135,9 @@ YsfMetaPanel.prototype.update = function(data) {
this.setLocation(data['lat'], data['lon'], data['source']);
this.setUp(data['up']);
this.setDown(data['down']);
if (data['mode'].indexOf('data') < 0) {
this.el.find(".openwebrx-meta-slot").addClass("active");
}
} else {
this.clear();
}
@ -145,7 +170,7 @@ YsfMetaPanel.prototype.setLocation = function(lat, lon, callsign) {
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"></a>';
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-ysf-source .location').html(html);
};
@ -162,9 +187,145 @@ YsfMetaPanel.prototype.setDown = function(down) {
this.el.find('.openwebrx-ysf-down').text(down || '');
}
function DStarMetaPanel(el) {
MetaPanel.call(this, el);
this.modes = ['DSTAR'];
this.clear();
}
DStarMetaPanel.prototype = new MetaPanel();
DStarMetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return;
if (data['sync'] && data['sync'] == 'voice') {
this.el.find(".openwebrx-meta-slot").addClass("active");
this.setOurCall(data['ourcall']);
this.setYourCall(data['yourcall']);
this.setDeparture(data['departure']);
this.setDestination(data['destination']);
this.setMessage(data['message']);
this.setLocation(data['lat'], data['lon'], data['ourcall']);
} else {
this.clear();
}
};
DStarMetaPanel.prototype.setOurCall = function(ourcall) {
if (this.ourcall === ourcall) return;
this.ourcall = ourcall;
this.el.find('.openwebrx-dstar-ourcall .callsign').text(ourcall || '');
};
DStarMetaPanel.prototype.setYourCall = function(yourcall) {
if (this.yourcall === yourcall) return;
this.yourcall = yourcall;
this.el.find('.openwebrx-dstar-yourcall').text(yourcall || '');
};
DStarMetaPanel.prototype.setDeparture = function(departure) {
if (this.departure === departure) return;
this.departure = departure;
this.el.find('.openwebrx-dstar-departure').text(departure || '');
};
DStarMetaPanel.prototype.setDestination = function(destination) {
if (this.destination === destination) return;
this.destination = destination;
this.el.find('.openwebrx-dstar-destination').text(destination || '');
};
DStarMetaPanel.prototype.setMessage = function(message) {
if (this.message === message) return;
this.message = message;
this.el.find('.openwebrx-dstar-message').text(message || '');
}
DStarMetaPanel.prototype.clear = function() {
MetaPanel.prototype.clear.call(this);
this.setOurCall();
this.setYourCall();
this.setDeparture();
this.setDestination();
this.setMessage();
this.setLocation();
};
DStarMetaPanel.prototype.setLocation = function(lat, lon, callsign) {
var hasLocation = lat && lon && callsign && callsign != '';
if (hasLocation === this.hasLocation && this.callsign === callsign) return;
this.hasLocation = hasLocation; this.callsign = callsign;
var html = '';
if (hasLocation) {
html = '<a class="openwebrx-maps-pin" href="map?callsign=' + encodeURIComponent(callsign) + '" target="_blank"><svg viewBox="0 0 20 35"><use xlink:href="static/gfx/svg-defs.svg#maps-pin"></use></svg></a>';
}
this.el.find('.openwebrx-dstar-ourcall .location').html(html);
};
function NxdnMetaPanel(el) {
MetaPanel.call(this, el);
this.modes = ['NXDN'];
this.clear();
}
NxdnMetaPanel.prototype = new MetaPanel();
NxdnMetaPanel.prototype.update = function(data) {
if (!this.isSupported(data)) return;
if (data['sync'] && data['sync'] === 'voice') {
this.el.find(".openwebrx-meta-slot").addClass("active");
this.setSource(data['additional'] && data['additional']['callsign'] || data['source']);
this.setName(data['additional'] && data['additional']['fname']);
this.setDestination(data['destination']);
this.setMode(['conference', 'individual'].includes(data['type']) ? data['type'] : undefined);
} else {
this.clear();
}
};
NxdnMetaPanel.prototype.setSource = function(source) {
if (this.source === source) return;
this.source = source;
this.el.find('.openwebrx-nxdn-source').text(source || '');
};
NxdnMetaPanel.prototype.setName = function(name) {
if (this.name === name) return;
this.name = name;
this.el.find('.openwebrx-nxdn-name').text(name || '');
};
NxdnMetaPanel.prototype.setDestination = function(destination) {
if (this.destination === destination) return;
this.destination = destination;
this.el.find('.openwebrx-nxdn-destination').text(destination || '');
};
NxdnMetaPanel.prototype.setMode = function(mode) {
if (this.mode === mode) return;
this.mode = mode;
var modes = ['individual', 'conference'];
var classes = modes.filter(function(c){
return c !== mode;
});
this.el.find('.openwebrx-meta-slot').removeClass(classes.join(' ')).addClass(mode);
};
NxdnMetaPanel.prototype.clear = function() {
MetaPanel.prototype.clear.call(this);
this.setMode();
this.setSource();
this.setName();
this.setDestination();
};
MetaPanel.types = {
dmr: DmrMetaPanel,
ysf: YsfMetaPanel
ysf: YsfMetaPanel,
dstar: DStarMetaPanel,
nxdn: NxdnMetaPanel,
};
$.fn.metaPanel = function() {

View File

@ -38,7 +38,7 @@ $.fn.exponentialInput = function() {
// calculate initial exponent
var value = parseFloat($input.val());
if (!Number.isNaN(value)) {
$exponent.val(Math.floor(Math.log10(value) / 3) * 3);
$exponent.val(Math.floor(Math.log10(Math.abs(value)) / 3) * 3);
setExponent();
}
})

BIN
htdocs/mstile-144x144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -779,13 +779,10 @@ function on_ws_recv(evt) {
break;
case "secondary_config":
var s = json['value'];
if ('secondary_fft_size' in s)
window.secondary_fft_size = s['secondary_fft_size'];
if ('secondary_bw' in s)
window.secondary_bw = s['secondary_bw'];
if ('if_samp_rate' in s)
window.if_samp_rate = s['if_samp_rate'];
secondary_demod_init_canvases();
secondary_fft_size = s['secondary_fft_size'] || secondary_fft_size;
secondary_bw = s['secondary_bw'] || secondary_bw;
if_samp_rate = s['if_samp_rate'] || if_samp_rate;
if (if_samp_rate) secondary_demod_init_canvases();
break;
case "receiver_details":
$('.webrx-top-container').header().setDetails(json['value']);
@ -821,12 +818,6 @@ function on_ws_recv(evt) {
this.update(json['value']);
});
break;
case "js8_message":
$("#openwebrx-panel-js8-message").js8().pushMessage(json['value']);
break;
case "wsjt_message":
$("#openwebrx-panel-wsjt-message").wsjtMessagePanel().pushMessage(json['value']);
break;
case "dial_frequencies":
var as_bookmarks = json['value'].map(function (d) {
return {
@ -837,9 +828,6 @@ function on_ws_recv(evt) {
});
bookmarks.replace_bookmarks(as_bookmarks, 'dial_frequencies');
break;
case "aprs_data":
$('#openwebrx-panel-packet-message').packetMessagePanel().pushMessage(json['value']);
break;
case "bookmarks":
bookmarks.replace_bookmarks(json['value'], "server");
break;
@ -851,14 +839,24 @@ function on_ws_recv(evt) {
$("#openwebrx-panel-receiver").demodulatorPanel().stopDemodulator();
break;
case 'secondary_demod':
secondary_demod_push_data(json['value']);
var value = json['value'];
var panels = [
$("#openwebrx-panel-wsjt-message").wsjtMessagePanel(),
$('#openwebrx-panel-packet-message').packetMessagePanel(),
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel(),
$("#openwebrx-panel-js8-message").js8()
];
if (!panels.some(function(panel) {
if (!panel.supportsMessage(value)) return false;
panel.pushMessage(value);
return true;
})) {
secondary_demod_push_data(value);
}
break;
case 'log_message':
divlog(json['value'], true);
break;
case 'pocsag_data':
$('#openwebrx-panel-pocsag-message').pocsagMessagePanel().pushMessage(json['value']);
break;
case 'backoff':
divlog("Server is currently busy: " + json['reason'], true);
var $overlay = $('#openwebrx-error-overlay');
@ -1213,19 +1211,12 @@ var audioEngine;
function openwebrx_init() {
audioEngine = new AudioEngine(audio_buffer_maximal_length_sec, audioReporter);
$('body').on('click', '#openwebrx-autoplay-overlay', function(){
var $overlay = $('#openwebrx-autoplay-overlay');
$overlay.on('click', function(){
audioEngine.resume();
});
audioEngine.onStart(onAudioStart);
if (!audioEngine.isAllowed()) {
var $overlay = $(
'<div id="openwebrx-autoplay-overlay" class="openwebrx-overlay" style="display:none;">' +
'<div class="overlay-content">' +
'<img id="openwebrx-play-button" src="static/gfx/openwebrx-play-button.svg" />' +
'<div>Start OpenWebRX</div>' +
'</div>' +
'</div>'
);
$('body').append($overlay);
$overlay.show();
}
@ -1272,6 +1263,9 @@ function digimodes_init() {
$('.openwebrx-dmr-timeslot-panel').click(function (e) {
$(e.currentTarget).toggleClass("muted");
update_dmr_timeslot_filtering();
// don't mute when the location icon is clicked
}).find('.location').click(function(e) {
e.stopPropagation();
});
$('.openwebrx-meta-panel').metaPanel();
@ -1394,6 +1388,8 @@ var secondary_demod_current_canvas_actual_line;
var secondary_demod_current_canvas_context;
var secondary_demod_current_canvas_index;
var secondary_demod_canvases;
var secondary_bw = 31.25;
var if_samp_rate;
function secondary_demod_create_canvas() {
var new_canvas = document.createElement("canvas");

2388
inkscape files/favicon.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 173 KiB

View File

@ -1,6 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
@ -14,8 +12,8 @@
viewBox="0 0 20 34.892337"
id="svg3455"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="Map Pin.svg">
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="google_maps_pin.svg">
<defs
id="defs3457" />
<sodipodi:namedview
@ -31,15 +29,16 @@
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1024"
inkscape:window-height="705"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
fit-margin-bottom="0"
inkscape:document-rotation="0" />
<metadata
id="metadata3460">
<rdf:RDF>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 80 80"
version="1.1"
id="svg4"
sodipodi:docname="openwebrx-bookmark.svg"
width="80"
height="80"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1381"
id="namedview6"
showgrid="false"
inkscape:pagecheckerboard="true"
inkscape:zoom="11.335806"
inkscape:cx="36.739751"
inkscape:cy="44.281478"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="m 62.5,0 h -45 C 13.357813,0 10,3.3578125 10,7.5 V 80 L 40,62.5 70,80 V 7.5 C 70,3.3578125 66.642187,0 62.5,0 Z m 0,66.942188 -22.5,-13.125 -22.5,13.125 V 8.4375 A 0.9375,0.9375 0 0 1 18.4375,7.5 h 43.125 C 62.080313,7.5 62.5,7.9192187 62.5,8.436875 Z"
id="path2"
style="stroke-width:0.156;stroke-miterlimit:4;stroke-dasharray:none;fill:#ffffff;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="DESIGNS"
x="0px"
y="0px"
width="80"
height="80"
viewBox="0 0 80 80"
xml:space="preserve"
sodipodi:docname="openwebrx-edit.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"><defs
id="defs13" /><sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1381"
id="namedview11"
showgrid="false"
inkscape:pagecheckerboard="true"
inkscape:zoom="8.015625"
inkscape:cx="50.931774"
inkscape:cy="30.97076"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1"
inkscape:current-layer="DESIGNS" /><title
property="dc:title"
id="title2">to edit</title><path
class="linesandangles_een"
d="m 52.5,7.5 -45,45 v 20 h 20 l 45,-45 z m 12.93,20 -4.6975,4.6975 -12.93,-12.93 L 52.5,14.57 Z m -50.1275,24.2675 4.06,-4.06 c 1.5325,1.125 3.575,2.0025 5.81,2.0025 2.1475,0 4.4525,-0.8 6.595,-2.9425 l 10,-10 c 2.52,-2.52 4.6225,-7.7275 0.9675,-12.43 l 1.535,-1.535 12.93,12.93 -28.9675,28.965 z m 22.93,-18.535 -10,10 c -2.0625,2.0575 -4.0075,1.595 -5.27,0.875 L 39.14,27.93 c 1.465,2.485 -0.27,4.6425 -0.9075,5.3025 z M 12.5,56.035 23.965,67.5 H 12.5 Z"
style="fill:#ffffff;fill-opacity:1;stroke-width:2.5"
id="path6" /><metadata
id="metadata8"><work
rdf:about=""><format>image/svg+xml</format><type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><license
rdf:resource="http://creativecommons.org/licenses/by/4.0/" /><attributionname>Shannon E Thomas</attributionname><attributionurl>http://www.toicon.com/icons/lines-and-angles_edit</attributionurl></work><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>to edit</dc:title></cc:Work></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="400"
height="400"
viewBox="0 0 400 400"
version="1.1"
id="svg6073"
sodipodi:docname="openwebrx-groupcall.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)">
<defs
id="defs6067">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter8693"
x="-0.25"
y="-0.25"
width="1.5"
height="1.5">
<feFlood
flood-opacity="0.4"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood8683" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite8685" />
<feGaussianBlur
in="composite1"
stdDeviation="66.6"
result="blur"
id="feGaussianBlur8687" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset8689" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite8691" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="124.48089"
inkscape:cy="196.34508"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1" />
<metadata
id="metadata6070">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="original image"
style="display:none">
<image
width="400"
height="400"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
fzmUE8s5xKfbAAAAAElFTkSuQmCC
"
id="image8601"
x="0"
y="0" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<g
fill="#ffffff"
id="g6985-0"
transform="matrix(0.42445796,0,0,0.42483521,129.12011,42)"
style="display:inline;filter:url(#filter8693)">
<path
id="path6983-9"
style="display:inline;stroke-width:0.424647"
d="m 141.00391,42 c -41.927961,0 -60.353635,29.337263 -55.892582,68.32227 3.624871,31.64342 17.451122,70.17417 55.892582,70.16992 38.43891,-0.004 52.27195,-38.52395 55.89257,-70.16992 C 201.35754,71.339387 182.94035,42 141.00391,42 Z m 118,0 c -41.92796,0 -60.35364,29.337263 -55.89258,68.32227 3.62487,31.64342 17.45112,70.17417 55.89258,70.16992 38.43891,-0.004 52.27195,-38.52395 55.89257,-70.16992 C 319.35754,71.339387 300.94035,42 259.00391,42 Z M 96.357422,174.15625 c -3.460606,1.83019 -7.147703,3.43692 -11.263672,5.05469 -8.4569,3.32731 -18.023739,5.13806 -25.410156,10.65625 -12.774487,9.54307 -16.074531,31.01082 -18.667969,45.65234 -2.14139,12.08826 -3.580211,24.42369 -4,36.70313 -0.325135,9.51078 4.366733,10.84338 12.316406,13.71484 9.954813,3.59326 20.231962,6.26064 30.578125,8.44727 19.982208,4.22328 40.579354,7.46994 61.087894,7.61523 19.79717,-0.14066 39.67129,-3.18071 59,-7.18945 19.32925,4.00882 39.20306,7.0492 59,7.18945 20.50896,-0.14572 41.1061,-3.39195 61.08789,-7.61523 10.34616,-2.18578 20.62611,-4.85316 30.58008,-8.44727 7.95264,-2.87061 12.64349,-4.20321 12.31836,-13.71484 h 0.004 c -0.42021,-12.27944 -1.85903,-24.61487 -4,-36.70313 -2.59386,-14.64364 -5.89433,-36.11139 -18.66797,-45.65234 -7.38726,-5.51776 -16.95325,-7.32894 -25.41015,-10.65625 -4.11597,-1.61862 -7.80307,-3.22492 -11.26368,-5.05469 -11.68023,12.81983 -26.91033,19.52649 -44.64843,19.52734 -17.73131,0 -32.96039,-6.70666 -44.64063,-19.52734 -3.4606,1.83019 -7.1477,3.43692 -11.26367,5.05469 -1.01596,0.39972 -2.04953,0.77564 -3.0918,1.14062 -1.04227,-0.36499 -2.07584,-0.7409 -3.09179,-1.14062 -4.11597,-1.61862 -7.80307,-3.22492 -11.26368,-5.05469 -11.68023,12.81983 -26.91033,19.52649 -44.64843,19.52734 -17.73131,0 -32.96039,-6.70666 -44.640628,-19.52734 z"
transform="matrix(2.3559459,0,0,2.3538539,-304.2,-98.861862)" />
</g>
<g
fill="#ffffff"
id="g6985"
transform="matrix(0.42445796,0,0,0.42483521,70.120108,102)"
style="display:inline;filter:url(#filter8693)">
<path
id="path6983"
d="m 550.98,541.91 c -0.99,-28.904 -4.377,-57.939 -9.421,-86.393 -6.111,-34.469 -13.889,-85.002 -43.983,-107.46 -17.404,-12.988 -39.941,-17.249 -59.865,-25.081 -9.697,-3.81 -18.384,-7.594 -26.537,-11.901 -27.518,30.176 -63.4,45.962 -105.19,45.964 -41.774,0 -77.652,-15.786 -105.17,-45.964 -8.153,4.308 -16.84,8.093 -26.537,11.901 -19.924,7.832 -42.461,12.092 -59.863,25.081 -30.096,22.463 -37.873,72.996 -43.983,107.46 -5.045,28.454 -8.433,57.489 -9.422,86.393 -0.766,22.387 10.288,25.525 29.017,32.284 23.453,8.458 47.666,14.737 72.041,19.884 47.077,9.941 95.603,17.582 143.92,17.924 48.318,-0.343 96.844,-7.983 143.92,-17.924 24.375,-5.145 48.59,-11.424 72.041,-19.884 18.736,-6.757 29.789,-9.895 29.023,-32.284 z M 306,325.99 C 396.56,325.98 429.15,235.31 437.68,160.82 448.19,69.06 404.8,0 306,0 207.22,0 163.81,69.055 174.32,160.82 182.86,235.304 215.434,326 306,325.99 Z"
style="display:inline" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="400"
height="400"
viewBox="0 0 400 400"
version="1.1"
id="svg8719"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="openwebrx-mute.svg">
<defs
id="defs8713" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="328.57143"
inkscape:cy="213.08184"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1" />
<metadata
id="metadata8716">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="original image"
style="display:none">
<image
width="400"
height="400"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
"
id="image9471"
x="0"
y="0" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<g
id="g1"
transform="matrix(5.3512516,0,0,5.3723191,-0.7293806,-1.5422576)">
<polygon
id="polygon1"
points="21.989,47.699 39.389,62.75 39.389,13.769 22.235,28.606 6,28.606 6,47.699 "
style="fill-opacity:0;stroke:#ffffff;stroke-width:5;stroke-linejoin:round;paint-order:fill" />
<path
id="path3003"
d="M 48.651772,50.269646 69.395223,25.971024"
style="stroke:#ffffff;stroke-width:5;stroke-linecap:round" />
<path
id="path3003-1"
d="M 69.395223,50.269646 48.651772,25.971024"
style="stroke:#ffffff;stroke-width:5;stroke-linecap:round" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
sodipodi:docname="openwebrx-panel-log.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
inkscape:export-filename="/home/jakob/workspace/openwebrx/htdocs/gfx/inkscape files/openwebrx-panel-log.png"
inkscape:export-xdpi="384"
inkscape:export-ydpi="384">
<sodipodi:namedview
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1381"
id="namedview15"
showgrid="true"
showguides="false"
inkscape:lockguides="false"
inkscape:pagecheckerboard="true"
inkscape:zoom="15.572868"
inkscape:cx="22.674387"
inkscape:cy="4.2078533"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1"
inkscape:current-layer="g2153"
viewbox-width="80"
scale-x="1"
inkscape:document-rotation="0" />
<defs
id="defs2">
<marker
style="overflow:visible"
id="Arrow2Lstart"
refX="0"
refY="0"
orient="auto"
inkscape:stockid="Arrow2Lstart"
inkscape:isstock="true">
<path
transform="matrix(1.1,0,0,1.1,1.1,0)"
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
id="path871" />
</marker>
</defs>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="original image"
style="display:none">
<image
width="80"
height="80"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz AAAemwAAHpsBLijKHAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAARGSURB VFiF7ZjPayRFFMe/r6q6Z5KeMAmbyy7mIMQokbCCe0j24HoSIfEggSX/huAxh5yC4smbkEMCOSSX RY2YVcQgeFAxgihETTCXCMEow04ck+nu+uFhuiY1nXTSPZOAgl8ouofp6vrMq1dv3ntkjEEeERHl erCgTAaAuGqiA5S+9syUvL/1IQV4KVgCZQdz7tuPdAPj3BsAGoAhog64TDAHigHgybgIrhu5UMpe XbgLwVJQwhn8msDgQCkAsQW1cFkWS0P5AMTOzs7rIyMjryXP5Ds159/bBjs+Pv5uaWlpdWFh4Qhn Vmz5XvpQJNay2+cD8AD4Gxsbd2dmZt4/PT39VilVI6LCYMYYcq/lcnmMcz44OTl5d3t7+wRABOtz LpizhdwCAfBnZ2er6+vrnzPG+uv1+qNarfYNYwxF4YwxpJQiKSWL45gBYOPj4wv7+/tvjI2NbQAI 0dras628wNlFAseXl5ffZoz1M8ZulUql/iAIpBBCJ/PyQkFrTUopajabAgDiOIaU8rcgCO7gzHep AyzROajd3d2HlUrlFSIKAMDzvGoQBJIxZopaTCnFpJRkAZVSdmuB1KESGdbyAIjV1dVnRkdH3yKi kp3ged6053n30fKFoiJjDPr6+j44PDz8KrH2lZGfwTmJU1NT5bm5uWWcxS73uScAfi9KZYyBMYaM MU+IyDDGMi0uUtHdBlK+ubm5yDl/ioj8C+b1A3gXBUOGUoqiKGInJyfWVTLlWqy9lSsrK09Xq9WH URR9L4QIGGO3AIRE9CdaltpN5sZFwIrIgrlWo6GhoQqA8ODg4L1KpRIPDw8/4Jy/mIDfTsaDwosJ Ac45EdFWo9H4IQ9YGg4AwDk3QghNRGUiGiwKkiX3MOUBu+gFhogQhuFHvu9/KITo5m+oLSllbh9j vSx0k7oyUQQA3/fvc86n0N0fd1uMsc8A/HhtYEQ0QER3eoFKFOR9MBdYGIaf+r7/uFcf01rbkHSl /vM+NimEeLnnxbqMY5epDODfE8esoij6AsDW/3EM+X3sHuf8Xq+Lcc5RLpcJwNeNRuPnrsFsjg7g NhG90CsYEdnxqzHmlyR85ALr8CGb/tbr9ce+73/MOe8ma3Xfx6IoYs1mkyulhJNSXwpmoYz9JVJK FoYhtzk6Y6wnn9RaUxzHLIoiLqVkdh0nxT7XInBLdn10dPQXWqVbOQzDUykli6KocPGRVrp8M8YQ 53xQKXWCdMGLzrTaA1AC4DebzU+01n/XarUvAcjL8vMi0lpTkvejWq0+PzAw8Ori4uJz8/PzfyBd 8DrVt20HeGtra89OT0+/GQTBS7iZsGLiOP5pb2/vnYmJiS200nQJQBtLnqrAbV153U2UDqhkqATG Do1Wq6xV8BpjTOKACp3+dl1tp8vA3FaUsW2orN5FVqPuJuC0vXcbd1ndHjhANwnWvqZbnefA2l/c UDM4razm8D+PkQnkpFfGiQAAAABJRU5ErkJggg== "
id="image86"
x="0"
y="0"
style="display:inline;stroke:#ffffff;stroke-width:0.529167;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="g2149">
<g
id="g2160"
style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none">
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,57 H 60"
id="path1136"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,48.5 H 60"
id="path1136-3"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,40 H 55"
id="path1136-6"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,31.5 H 60"
id="path1136-7"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 21,23 H 60"
id="path1136-5"
sodipodi:nodetypes="cc" />
</g>
<g
id="g2153"
style="stroke-width:2.1;stroke-miterlimit:4;stroke-dasharray:none">
<path
style="display:inline;mix-blend-mode:normal;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.5,63.5 c 0,2.5 2.5,5 5,5 h 45 c 2.5,0 5,-2.5 5,-5 v -46 c 0,-2.5 -2.5,-5 -5,-5 H 25 l -11.5,16 z"
id="path1123"
sodipodi:nodetypes="ssssssccs"
inkscape:label="path1123" />
<path
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 25,12.5 c 0,0 -1.323049,7.84716 4,15 -9.294147,-1.268245 -15.5,1 -15.5,1"
id="path1188"
sodipodi:nodetypes="ccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg3105"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="openwebrx-panel-map.svg">
<defs
id="defs3099" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="40"
inkscape:cy="39.911612"
inkscape:document-units="px"
inkscape:current-layer="g3837"
inkscape:document-rotation="0"
showgrid="false"
units="px"
scale-x="1"
inkscape:pagecheckerboard="true"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:snap-smooth-nodes="false"
inkscape:snap-midpoints="false"
inkscape:snap-object-midpoints="true"
inkscape:snap-tangential="true"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1" />
<metadata
id="metadata3102">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="original image"
style="display:none">
<image
width="80"
height="80"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
TkSuQmCC
"
id="image3825"
x="0"
y="0"
style="display:inline" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<g
id="g3837"
style="display:inline;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286">
<path
id="path3829"
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286"
d="m 30,11 -19,8 v 50 l 19,-5 19,5 20,-5 V 11 l -10.84375,4.878906 c 1.497572,2.00222 2.394531,4.479206 2.394531,7.171875 0,2.111185 -0.550543,4.091049 -1.507812,5.814453 h 0.0039 L 49,47 38.279297,29.255859 h 0.01367 c -1.099062,-1.812401 -1.742188,-3.930676 -1.742188,-6.205078 0,-2.905449 1.033756,-5.568373 2.751953,-7.644531 z"
sodipodi:nodetypes="ccccccccscccccscc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286"
d="M 30,11 V 64"
id="path3833" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286"
d="M 49,47 V 69"
id="path3839"
sodipodi:nodetypes="cc" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286"
d="M 11,59 30,34 49,52 69,46"
id="path3842"
sodipodi:nodetypes="cccc" />
</g>
<g
id="g3870"
style="display:inline">
<circle
style="display:inline;fill:none;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.989286"
id="path3845"
cx="48.549999"
cy="23.049999"
inkscape:label="path3845"
r="6.5" />
<path
id="path3851"
style="display:inline;fill:#ffffff;fill-opacity:0.2;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 48.550781,11.050781 c -6.627417,0 -12,5.372583 -12,12 0,2.274402 0.643126,4.392677 1.742188,6.205078 h -0.01367 L 49,47 59.046875,28.865234 h -0.0039 c 0.957269,-1.723404 1.507812,-3.703268 1.507812,-5.814453 0,-6.627417 -5.372583,-12 -12,-12 z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="openwebrx-panel-receiver.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="11.313708"
inkscape:cx="40.751301"
inkscape:cy="43.013749"
inkscape:document-units="px"
inkscape:current-layer="g987"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="original image"
style="display:none">
<image
width="84.210526"
height="77.894737"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
ova9yyn/BRGr/5TyNQXzAAAAAElFTkSuQmCC
"
id="image903"
x="-2.105"
y="1.0526316"
style="fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 66,29.05 12,10"
id="path917"
sodipodi:nodetypes="cc" />
<rect
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
id="rect907"
width="67.5"
height="38.5"
x="6.8000002"
y="29.049999"
ry="5"
rx="5" />
<g
id="g987"
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<rect
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect979"
width="28.5"
height="9.5"
x="12.3"
y="34.049999"
ry="2"
rx="2" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path973"
cx="56.549999"
cy="48.549999"
r="13.5" />
<g
id="g1021"
style="display:inline;fill:#ffffff;fill-opacity:0.34999999"
transform="translate(0,-0.49999928)">
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982"
cx="17.049999"
cy="50.549999"
r="3" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982-3"
cx="26.299999"
cy="50.549999"
r="3" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982-6"
cx="35.549999"
cy="50.549999"
r="3" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982-7"
cx="17.049999"
cy="57.049999"
r="3" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982-3-5"
cx="26.299999"
cy="57.049999"
r="3" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.34999999;stroke:#ffffff;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path982-6-3"
cx="35.549999"
cy="57.049999"
r="3" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg8"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="openwebrx-panel-settings.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect888"
is_visible="true"
lpeversion="1"
satellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,1,0,1"
unit="px"
method="auto"
mode="F"
radius="1"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="13.280349"
inkscape:cx="40"
inkscape:cy="40"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
inkscape:object-paths="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-object-midpoints="true"
inkscape:window-width="2560"
inkscape:window-height="1381"
inkscape:window-x="0"
inkscape:window-y="348"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="original image"
style="display:none">
<image
width="80"
height="80"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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 EAAAAABJRU5ErkJggg== "
id="image941"
x="0"
y="0" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<path
id="path945-7"
style="display:inline;fill:#ffffff;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
d="m 39.855469,10 -1,0.02148 c -1.041041,0.03967 -2.078102,0.133542 -3.109375,0.28125 -0.546679,0.0783 -0.853262,0.587746 -0.78125,1.134766 0.06102,0.463559 0.06238,1.29646 -0.02148,1.841797 -0.429434,2.792231 -2.635502,4.998303 -5.427734,5.427734 -0.545334,0.08387 -1.446955,0.0845 -1.992187,0 -1.649502,-0.255648 -3.139113,-1.139394 -4.15625,-2.46289 -0.336212,-0.437478 -0.915438,-0.58112 -1.357422,-0.25 -2.281483,1.709219 -4.307758,3.734592 -6.017578,6.015625 -0.331237,0.441896 -0.187588,1.019411 0.25,1.355468 1.324163,1.016924 2.20886,2.50835 2.464843,4.158204 0.08459,0.545224 0.08387,1.44685 0,1.992187 -0.429434,2.792231 -2.635502,4.998303 -5.427734,5.427734 -0.545334,0.08387 -1.447037,0.08309 -1.992188,-0.002 -0.46091,-0.07188 -0.903957,0.256079 -0.982421,0.802735 -0.154876,1.078998 -0.250509,2.166418 -0.28711,3.255859 -0.018543,0.551946 -0.020293,1.448045 -0.002,2 0.0362,1.089396 0.132644,2.174907 0.287109,3.253906 0.07826,0.546684 0.587746,0.853262 1.134766,0.78125 0.463559,-0.06102 1.29646,-0.06238 1.841797,0.02148 2.792231,0.429434 4.998303,2.635502 5.427734,5.427734 0.08387,0.545334 0.0845,1.446955 0,1.992187 -0.255648,1.649502 -1.139394,3.139114 -2.46289,4.15625 -0.437478,0.336213 -0.58112,0.915438 -0.25,1.357422 1.709219,2.281483 3.734592,4.307759 6.015625,6.017578 0.441896,0.331237 1.019411,0.187588 1.355468,-0.25 1.016924,-1.324164 2.50835,-2.20886 4.158204,-2.464843 0.545224,-0.08459 1.44685,-0.08387 1.992187,0 2.792231,0.429434 4.998303,2.635502 5.427734,5.427734 0.08387,0.545334 0.08309,1.447037 -0.002,1.992188 -0.07188,0.46091 0.256079,0.903958 0.802735,0.982421 1.078998,0.154876 2.166418,0.250509 3.255859,0.28711 0.551946,0.01854 1.448045,0.0203 2,0.002 1.089396,-0.0362 2.174907,-0.132644 3.253906,-0.287109 0.546684,-0.07826 0.853261,-0.589699 0.78125,-1.136719 -0.06102,-0.463559 -0.06238,-1.294507 0.02148,-1.839844 0.429434,-2.792231 2.635502,-4.998303 5.427734,-5.427734 0.545334,-0.08387 1.446955,-0.0845 1.992187,0 1.649502,0.255648 3.139113,1.139394 4.15625,2.46289 0.336213,0.437478 0.915438,0.58112 1.357422,0.25 2.281483,-1.709219 4.30776,-3.734592 6.017578,-6.015625 0.331238,-0.441896 0.18759,-1.019411 -0.25,-1.355468 -1.324164,-1.016924 -2.20886,-2.50835 -2.464843,-4.158204 -0.08459,-0.545223 -0.08387,-1.44685 0,-1.992187 0.429434,-2.792231 2.635502,-4.998303 5.427734,-5.427734 0.545334,-0.08387 1.447037,-0.08309 1.992188,0.002 0.46091,0.07188 0.903958,-0.258032 0.982421,-0.804688 0.154876,-1.078998 0.250509,-2.164465 0.28711,-3.253906 0.01854,-0.551946 0.0203,-1.448045 0.002,-2 -0.0362,-1.089397 -0.132644,-2.174907 -0.287109,-3.253906 -0.07826,-0.546684 -0.589699,-0.853261 -1.136719,-0.78125 -0.463559,0.06102 -1.294507,0.06238 -1.839844,-0.02148 -2.792231,-0.429434 -4.998303,-2.635502 -5.427734,-5.427734 -0.08387,-0.545334 -0.0845,-1.446956 0,-1.992187 0.255648,-1.649502 1.139394,-3.139113 2.46289,-4.15625 0.437478,-0.336212 0.58112,-0.915438 0.25,-1.357422 -1.709219,-2.281483 -3.734592,-4.307759 -6.015625,-6.017578 -0.441896,-0.331237 -1.019411,-0.18759 -1.355468,0.25 -1.016924,1.324163 -2.50835,2.20886 -4.158204,2.464843 -0.545223,0.08459 -1.44685,0.08387 -1.992187,0 -2.792231,-0.429434 -4.998303,-2.635502 -5.427734,-5.427734 -0.08387,-0.545334 -0.08309,-1.447037 0.002,-1.992188 0.07188,-0.46091 -0.256079,-0.903958 -0.802735,-0.982421 C 43.176861,10.149812 42.089441,10.054179 41,10.017578 40.448054,9.9990351 39.935292,9.9998077 39.855469,10 Z M 40,26.5 A 13.5,13.5 0 0 1 53.5,40 13.5,13.5 0 0 1 40,53.5 13.5,13.5 0 0 1 26.5,40 13.5,13.5 0 0 1 40,26.5 Z" />
<circle
style="display:inline;fill:#ffffff;fill-opacity:0.2;stroke:#ffffff;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1063"
cy="39.999958"
cx="40"
r="13.5" />
<circle
style="display:inline;fill:none;fill-opacity:0.35;stroke:#ffffff;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1724"
cx="40"
cy="39.999958"
r="8" />
<path
style="display:none;fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 52.24587,69.564145 27.75413,10.435855"
id="path975"
sodipodi:nodetypes="cc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

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