merge recent openwebrx changes into our work
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1 +1,3 @@
 | 
			
		||||
*.pyc
 | 
			
		||||
*.swp
 | 
			
		||||
tags
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								README.md
									
									
									
									
									
								
							@@ -1,21 +1,26 @@
 | 
			
		||||
OpenWebRX
 | 
			
		||||
=========
 | 
			
		||||
 | 
			
		||||
[:floppy_disk: Setup guide for Ubuntu](http://blog.sdr.hu/2015/06/30/quick-setup-openwebrx.html)  |  [:blue_book: Knowledge base on the Wiki](https://github.com/simonyiszk/openwebrx/wiki/)  |  [:earth_americas: Receivers on SDR.hu](http://sdr.hu/) 
 | 
			
		||||
 | 
			
		||||
OpenWebRX is a multi-user SDR receiver software with a web interface.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
It has the following features:
 | 
			
		||||
 | 
			
		||||
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a> based demodulators (AM/FM/SSB),
 | 
			
		||||
- <a href="https://github.com/simonyiszk/csdr">csdr</a> based demodulators (AM/FM/SSB/CW/BPSK31),
 | 
			
		||||
- filter passband can be set from GUI,
 | 
			
		||||
- waterfall display can be shifted back in time,
 | 
			
		||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>.
 | 
			
		||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>,
 | 
			
		||||
- it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28),
 | 
			
		||||
- currently supports RTL-SDR and HackRF; other SDR hardware may be easily added.
 | 
			
		||||
- currently supports RTL-SDR, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>,
 | 
			
		||||
- it has a 3D waterfall display:
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
**News (2015-08-18)**
 | 
			
		||||
- My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
 | 
			
		||||
- My BSc. thesis written on OpenWebRX is <a href="https://sdr.hu/static/bsc-thesis.pdf">available here.</a>
 | 
			
		||||
- Several bugs were fixed to improve reliability and stability.
 | 
			
		||||
- OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.)
 | 
			
		||||
- OpenWebRX now uses <a href="https://github.com/simonyiszk/csdr#sdrjs">sdr.js</a> (*libcsdr* compiled to JavaScript) for some client-side DSP tasks. 
 | 
			
		||||
@@ -28,13 +33,20 @@ It has the following features:
 | 
			
		||||
- OpenWebRX now supports URLs like: `http://localhost:8073/#freq=145555000,mod=usb`
 | 
			
		||||
- UI improvements were made, thanks to John Seamons and Gnoxter.
 | 
			
		||||
 | 
			
		||||
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*, and install the new dependency, *ncat*!
 | 
			
		||||
**News (2017-04-04)**
 | 
			
		||||
- *ncat* has been replaced with a custom implementation called *nmux* due to a bug that caused regular crashes on some machines. The *nmux* tool is part of the *csdr* package.
 | 
			
		||||
- Most consumer SDR devices are supported via <a href="https://github.com/rxseger/rx_tools">rx_tools</a>, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX">OpenWebRX Wiki</a> on that.
 | 
			
		||||
 | 
			
		||||
**News (2017-07-12)**
 | 
			
		||||
- OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display.
 | 
			
		||||
 | 
			
		||||
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*!
 | 
			
		||||
 | 
			
		||||
## OpenWebRX servers on SDR.hu
 | 
			
		||||
 | 
			
		||||
[SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Setup
 | 
			
		||||
 | 
			
		||||
@@ -44,11 +56,6 @@ First you will need to install the dependencies:
 | 
			
		||||
 | 
			
		||||
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a>
 | 
			
		||||
- <a href="http://sdr.osmocom.org/trac/wiki/rtl-sdr">rtl-sdr</a>
 | 
			
		||||
- ncat (On Debian/Ubuntu, it is in the *nmap* package). 
 | 
			
		||||
 | 
			
		||||
> By the way, *nmap* is a tool commonly used for auditing network security, and it is not used by OpenWebRX in any way. We need to install it, because the *ncat* command is packaged with it.
 | 
			
		||||
>
 | 
			
		||||
> *ncat* is a better *netcat* alternative, which is used by OpenWebRX for internally distributing the I/Q data stream. It also solves the problem of having different versions of *netcat* on different Linux distributions, which are not compatible by their command-line arguments.
 | 
			
		||||
 | 
			
		||||
After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server:
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +69,7 @@ Please note that the server is also listening on the following ports (on localho
 | 
			
		||||
 | 
			
		||||
Now the next step is to customize the parameters of your server in `config_webrx.py`.
 | 
			
		||||
 | 
			
		||||
Actually, if you do something cool with OpenWebRX (or just have a problem), please drop me a mail:  
 | 
			
		||||
Actually, if you do something cool with OpenWebRX, please drop me a mail:  
 | 
			
		||||
*Andras Retzler, HA7ILM <randras@sdr.hu>*
 | 
			
		||||
 | 
			
		||||
## Usage tips
 | 
			
		||||
@@ -85,4 +92,4 @@ If you want to run OpenWebRX on a remote server instead of *localhost*, do not f
 | 
			
		||||
 | 
			
		||||
OpenWebRX is available under Affero GPL v3 license (<a href="https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)">summary</a>).
 | 
			
		||||
 | 
			
		||||
OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for other licensing options. 
 | 
			
		||||
OpenWebRX is also available under a commercial license on request. Please contact me at the address *<randras@sdr.hu>* for licensing options. 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										101
									
								
								config_webrx.py
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								config_webrx.py
									
									
									
									
									
								
							@@ -3,9 +3,9 @@
 | 
			
		||||
"""
 | 
			
		||||
config_webrx: configuration options for OpenWebRX
 | 
			
		||||
 | 
			
		||||
	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>
 | 
			
		||||
    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>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as
 | 
			
		||||
@@ -20,15 +20,15 @@ config_webrx: configuration options for OpenWebRX
 | 
			
		||||
    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/>.
 | 
			
		||||
 | 
			
		||||
	++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
	In addition, as a special exception, the copyright holders
 | 
			
		||||
	state that config_rtl.py and config_webrx.py are not part of the
 | 
			
		||||
	Corresponding Source defined in GNU AGPL version 3 section 1.
 | 
			
		||||
    In addition, as a special exception, the copyright holders
 | 
			
		||||
    state that config_rtl.py and config_webrx.py are not part of the
 | 
			
		||||
    Corresponding Source defined in GNU AGPL version 3 section 1.
 | 
			
		||||
 | 
			
		||||
	(It means that you do not have to redistribute config_rtl.py and
 | 
			
		||||
	config_webrx.py if you make any changes to these two configuration files,
 | 
			
		||||
	and use them for running your web service with OpenWebRX.)
 | 
			
		||||
    (It means that you do not have to redistribute config_rtl.py and
 | 
			
		||||
    config_webrx.py if you make any changes to these two configuration files,
 | 
			
		||||
    and use them for running your web service with OpenWebRX.)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
 | 
			
		||||
@@ -67,34 +67,49 @@ sdrhu_key = ""
 | 
			
		||||
sdrhu_public_listing = False
 | 
			
		||||
 | 
			
		||||
# ==== DSP/RX settings ====
 | 
			
		||||
dsp_plugin="csdr"
 | 
			
		||||
fft_fps=9
 | 
			
		||||
fft_size=4096
 | 
			
		||||
samp_rate = 250000
 | 
			
		||||
fft_size=4096 #Should be power of 2
 | 
			
		||||
fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
 | 
			
		||||
 | 
			
		||||
center_freq = 145525000
 | 
			
		||||
# samp_rate = 250000
 | 
			
		||||
samp_rate = 2400000
 | 
			
		||||
center_freq = 144250000
 | 
			
		||||
rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode.
 | 
			
		||||
ppm = 0
 | 
			
		||||
 | 
			
		||||
audio_compression="adpcm" #valid values: "adpcm", "none"
 | 
			
		||||
fft_compression="adpcm" #valid values: "adpcm", "none"
 | 
			
		||||
 | 
			
		||||
digimodes_enable=True #Decoding digimodes come with higher CPU usage. 
 | 
			
		||||
digimodes_fft_size=1024
 | 
			
		||||
 | 
			
		||||
start_rtl_thread=True
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Note: if you experience audio underruns while CPU usage is 100%, you can: 
 | 
			
		||||
- decrease `samp_rate`,
 | 
			
		||||
- set `fft_voverlap_factor` to 0,
 | 
			
		||||
- decrease `fft_fps` and `fft_size`,
 | 
			
		||||
- limit the number of users by decreasing `max_clients`.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# ==== I/Q sources ====
 | 
			
		||||
# (Uncomment the appropriate by removing # characters at the beginning of the corresponding lines.)
 | 
			
		||||
 | 
			
		||||
# There are guides for setting may different SDR hardware including AirSpy, AFEDRI-SDR, RTL-SDR in direct sampling mode, etc. in the Wiki:
 | 
			
		||||
#       https://github.com/simonyiszk/openwebrx/wiki
 | 
			
		||||
#################################################################################################
 | 
			
		||||
# Is my SDR hardware supported?                                                                 #
 | 
			
		||||
# Check here: https://github.com/simonyiszk/openwebrx/wiki#guides-for-receiver-hardware-support #
 | 
			
		||||
#################################################################################################
 | 
			
		||||
 | 
			
		||||
# You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR):
 | 
			
		||||
 | 
			
		||||
# >> RTL-SDR via rtl_sdr
 | 
			
		||||
 | 
			
		||||
start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
 | 
			
		||||
format_conversion="csdr convert_u8_f"
 | 
			
		||||
 | 
			
		||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -q -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
 | 
			
		||||
#lna_gain=8
 | 
			
		||||
#rf_amp=1
 | 
			
		||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain)
 | 
			
		||||
#format_conversion="csdr convert_s8_f"
 | 
			
		||||
"""
 | 
			
		||||
To use a HackRF, compile the HackRF host tools from its "stdout" branch:
 | 
			
		||||
@@ -117,20 +132,29 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch:
 | 
			
		||||
#format_conversion="csdr convert_s16_f | csdr gain_ff 30"
 | 
			
		||||
 | 
			
		||||
# >> /dev/urandom test signal source
 | 
			
		||||
#samp_rate = 2400000
 | 
			
		||||
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
 | 
			
		||||
#format_conversion="csdr convert_u8_f"
 | 
			
		||||
# samp_rate = 2400000
 | 
			
		||||
# start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
 | 
			
		||||
# format_conversion="csdr convert_u8_f"
 | 
			
		||||
 | 
			
		||||
# >> Pre-recorded raw I/Q file as signal source
 | 
			
		||||
# You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file.
 | 
			
		||||
#start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05)
 | 
			
		||||
#format_conversion="csdr convert_u8_f"
 | 
			
		||||
 | 
			
		||||
#>> The rx_sdr command works with a variety of SDR harware: RTL-SDR, HackRF, SDRplay, UHD, Airspy, Red Pitaya, audio devices, etc. 
 | 
			
		||||
# It will auto-detect your SDR hardware if the following tools are installed:
 | 
			
		||||
# * the vendor provided driver and library, 
 | 
			
		||||
# * the vendor-specific SoapySDR wrapper library, 
 | 
			
		||||
# * and SoapySDR itself.
 | 
			
		||||
# Check out this article on the OpenWebRX Wiki: https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX/
 | 
			
		||||
#start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
 | 
			
		||||
#format_conversion=""
 | 
			
		||||
 | 
			
		||||
# >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source)
 | 
			
		||||
#start_rtl_command="cat /tmp/osmocom_fifo"
 | 
			
		||||
#format_conversion=""
 | 
			
		||||
 | 
			
		||||
# ==== Misc options ====
 | 
			
		||||
# ==== Misc settings ====
 | 
			
		||||
 | 
			
		||||
shown_center_freq = center_freq #you can change this if you use an upconverter
 | 
			
		||||
 | 
			
		||||
@@ -146,16 +170,43 @@ iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data ove
 | 
			
		||||
 | 
			
		||||
#access_log = "~/openwebrx_access.log"
 | 
			
		||||
 | 
			
		||||
waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]"
 | 
			
		||||
waterfall_min_level = -115 #in dB
 | 
			
		||||
waterfall_max_level = 0
 | 
			
		||||
# ==== Color themes ====
 | 
			
		||||
 | 
			
		||||
#A guide is available to help you set these values: https://github.com/simonyiszk/openwebrx/wiki/Calibrating-waterfall-display-levels
 | 
			
		||||
 | 
			
		||||
### default theme by teejez:
 | 
			
		||||
waterfall_colors = "[0x000000ff,0x0000ffff,0x00ffffff,0x00ff00ff,0xffff00ff,0xff0000ff,0xff00ffff,0xffffffff]"
 | 
			
		||||
waterfall_min_level = -88 #in dB
 | 
			
		||||
waterfall_max_level = -20
 | 
			
		||||
waterfall_auto_level_margin = (5, 40)
 | 
			
		||||
### old theme by HA7ILM:
 | 
			
		||||
#waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]"
 | 
			
		||||
#waterfall_min_level = -115 #in dB
 | 
			
		||||
#waterfall_max_level = 0
 | 
			
		||||
#waterfall_auto_level_margin = (20, 30)
 | 
			
		||||
##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
 | 
			
		||||
 | 
			
		||||
#Note: When the auto waterfall level button is clicked, the following happens:
 | 
			
		||||
#   [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
 | 
			
		||||
#   [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
 | 
			
		||||
#
 | 
			
		||||
#   ___|____________________________________|____________________________________|____________________________________|___> signal power
 | 
			
		||||
#        \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level          | \_waterfall_auto_level_margin[1]_/
 | 
			
		||||
#                                                      current_max_power_level __|
 | 
			
		||||
 | 
			
		||||
# 3D view settings
 | 
			
		||||
mathbox_waterfall_frequency_resolution = 128 #bins
 | 
			
		||||
mathbox_waterfall_history_length = 10 #seconds
 | 
			
		||||
mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff,  0xfff775ff, 0xff8a8aff, 0xb20000ff]"
 | 
			
		||||
 | 
			
		||||
# === Experimental settings ===
 | 
			
		||||
#Warning! The settings below are very experimental.
 | 
			
		||||
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr.
 | 
			
		||||
csdr_print_bufsizes = False  # This prints the buffer sizes used for csdr processes.
 | 
			
		||||
csdr_through = False # Setting this True will print out how much data is going into the DSP chains.
 | 
			
		||||
 | 
			
		||||
nmux_memory = 50 #in megabytes. This sets the approximate size of the circular buffer used by nmux.
 | 
			
		||||
 | 
			
		||||
#Look up external IP address automatically from icanhazip.com, and use it as [server_hostname]
 | 
			
		||||
"""
 | 
			
		||||
print "[openwebrx-config] Detecting external IP address..."
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										453
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										453
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,453 @@
 | 
			
		||||
"""
 | 
			
		||||
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>
 | 
			
		||||
 | 
			
		||||
    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 time
 | 
			
		||||
import os
 | 
			
		||||
import code
 | 
			
		||||
import signal
 | 
			
		||||
import fcntl
 | 
			
		||||
 | 
			
		||||
class dsp:
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.samp_rate = 250000
 | 
			
		||||
        self.output_rate = 11025 #this is default, and cannot be set at the moment
 | 
			
		||||
        self.fft_size = 1024
 | 
			
		||||
        self.fft_fps = 5
 | 
			
		||||
        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.format_conversion = "csdr convert_u8_f"
 | 
			
		||||
        self.base_bufsize = 512
 | 
			
		||||
        self.nc_port = 4951
 | 
			
		||||
        self.csdr_dynamic_bufsize = False
 | 
			
		||||
        self.csdr_print_bufsizes = False
 | 
			
		||||
        self.csdr_through = False
 | 
			
		||||
        self.squelch_level = 0
 | 
			
		||||
        self.fft_averages = 50
 | 
			
		||||
        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", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"]
 | 
			
		||||
        self.secondary_pipe_names=["secondary_shift_pipe"]
 | 
			
		||||
        self.secondary_offset_freq = 1000
 | 
			
		||||
 | 
			
		||||
    def chain(self,which):
 | 
			
		||||
        if which in [ "dmr", "dstar", "nxdn", "ysf" ]:
 | 
			
		||||
            self.set_output_rate(48000)
 | 
			
		||||
        else:
 | 
			
		||||
            self.set_output_rate(11025)
 | 
			
		||||
        any_chain_base="nc -v 127.0.0.1 {nc_port} | "
 | 
			
		||||
        if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
 | 
			
		||||
        if self.csdr_through: any_chain_base+="csdr through | "
 | 
			
		||||
        any_chain_base+=self.format_conversion+(" | " if  self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
 | 
			
		||||
        if which == "fft":
 | 
			
		||||
            fft_chain_base = any_chain_base+"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":
 | 
			
		||||
                return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
 | 
			
		||||
            else:
 | 
			
		||||
                return fft_chain_base
 | 
			
		||||
        chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
 | 
			
		||||
        if self.secondary_demodulator:
 | 
			
		||||
            chain_begin+="csdr tee {iqtee_pipe} | "
 | 
			
		||||
            chain_begin+="csdr tee {iqtee2_pipe} | " 
 | 
			
		||||
        chain_end = ""
 | 
			
		||||
        if self.audio_compression=="adpcm":
 | 
			
		||||
            chain_end = " | csdr encode_ima_adpcm_i16_u8"
 | 
			
		||||
        if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end
 | 
			
		||||
        if which in [ "dstar", "nxdn" ]:
 | 
			
		||||
            c = chain_begin
 | 
			
		||||
            c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
            if which == "dstar":
 | 
			
		||||
                c += " | dsd -fd"
 | 
			
		||||
            elif which == "nxdn":
 | 
			
		||||
                c += " | dsd -fi"
 | 
			
		||||
            c += " -i - -o - -u 2 -g 10"
 | 
			
		||||
            c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220"
 | 
			
		||||
            c += chain_end
 | 
			
		||||
            return c
 | 
			
		||||
        elif which == "dmr":
 | 
			
		||||
            c = chain_begin
 | 
			
		||||
            c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
            c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer"
 | 
			
		||||
            c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
 | 
			
		||||
            c += chain_end
 | 
			
		||||
            return c
 | 
			
		||||
        elif which == "ysf":
 | 
			
		||||
            c = chain_begin
 | 
			
		||||
            c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
            c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y"
 | 
			
		||||
            c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
 | 
			
		||||
            c += chain_end
 | 
			
		||||
            return c
 | 
			
		||||
        elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
 | 
			
		||||
        elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
 | 
			
		||||
 | 
			
		||||
    def secondary_chain(self, which):
 | 
			
		||||
        secondary_chain_base="cat {input_pipe} | "
 | 
			
		||||
        if which == "fft":
 | 
			
		||||
            return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "")
 | 
			
		||||
        elif which == "bpsk31":
 | 
			
		||||
            return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \
 | 
			
		||||
                    "csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \
 | 
			
		||||
                    "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"
 | 
			
		||||
 | 
			
		||||
    def set_secondary_demodulator(self, what):
 | 
			
		||||
        self.secondary_demodulator = what
 | 
			
		||||
 | 
			
		||||
    def secondary_fft_block_size(self):
 | 
			
		||||
        return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here
 | 
			
		||||
 | 
			
		||||
    def secondary_decimation(self):
 | 
			
		||||
        return 1 #currently unused
 | 
			
		||||
 | 
			
		||||
    def secondary_bpf_cutoff(self):
 | 
			
		||||
        if self.secondary_demodulator == "bpsk31":
 | 
			
		||||
             return (31.25/2) / self.if_samp_rate()
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def secondary_bpf_transition_bw(self):
 | 
			
		||||
        if self.secondary_demodulator == "bpsk31":
 | 
			
		||||
            return (31.25/2) / 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
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def secondary_bw(self):
 | 
			
		||||
        if self.secondary_demodulator == "bpsk31":
 | 
			
		||||
            return 31.25
 | 
			
		||||
 | 
			
		||||
    def start_secondary_demodulator(self):
 | 
			
		||||
        if(not self.secondary_demodulator): return
 | 
			
		||||
        print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate()
 | 
			
		||||
        secondary_command_fft=self.secondary_chain("fft")
 | 
			
		||||
        secondary_command_demod=self.secondary_chain(self.secondary_demodulator)
 | 
			
		||||
        self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft)
 | 
			
		||||
 | 
			
		||||
        secondary_command_fft=secondary_command_fft.format( \
 | 
			
		||||
            input_pipe=self.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(), \
 | 
			
		||||
            )
 | 
			
		||||
        secondary_command_demod=secondary_command_demod.format( \
 | 
			
		||||
            input_pipe=self.iqtee2_pipe, \
 | 
			
		||||
            secondary_shift_pipe=self.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()
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        print "[openwebrx-dsp-plugin:csdr] secondary command (fft) =", secondary_command_fft
 | 
			
		||||
        print "[openwebrx-dsp-plugin:csdr] secondary command (demod) =", secondary_command_demod
 | 
			
		||||
        #code.interact(local=locals())
 | 
			
		||||
        my_env=os.environ.copy()
 | 
			
		||||
        #if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
 | 
			
		||||
        if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
 | 
			
		||||
        self.secondary_process_fft = subprocess.Popen(secondary_command_fft, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
 | 
			
		||||
        print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (fft)"
 | 
			
		||||
        self.secondary_process_demod = subprocess.Popen(secondary_command_demod, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) #TODO digimodes
 | 
			
		||||
        print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (demod)" #TODO digimodes
 | 
			
		||||
        self.secondary_processes_running = True
 | 
			
		||||
 | 
			
		||||
        #open control pipes for csdr and send initialization data
 | 
			
		||||
        # print "==========> 1"
 | 
			
		||||
        if self.secondary_shift_pipe != None: #TODO digimodes
 | 
			
		||||
            # print "==========> 2", self.secondary_shift_pipe
 | 
			
		||||
            self.secondary_shift_pipe_file=open(self.secondary_shift_pipe,"w") #TODO digimodes
 | 
			
		||||
            # print "==========> 3"
 | 
			
		||||
            self.set_secondary_offset_freq(self.secondary_offset_freq) #TODO digimodes
 | 
			
		||||
            # print "==========> 4"
 | 
			
		||||
 | 
			
		||||
        self.set_pipe_nonblocking(self.secondary_process_demod.stdout)
 | 
			
		||||
        self.set_pipe_nonblocking(self.secondary_process_fft.stdout)
 | 
			
		||||
 | 
			
		||||
    def set_secondary_offset_freq(self, value):
 | 
			
		||||
        self.secondary_offset_freq=value
 | 
			
		||||
        if self.secondary_processes_running:
 | 
			
		||||
            self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate()))
 | 
			
		||||
            self.secondary_shift_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
    def stop_secondary_demodulator(self):
 | 
			
		||||
        if self.secondary_processes_running == False: return
 | 
			
		||||
        self.try_delete_pipes(self.secondary_pipe_names)
 | 
			
		||||
        if self.secondary_process_fft: os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
 | 
			
		||||
        if self.secondary_process_demod: os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
 | 
			
		||||
        self.secondary_processes_running = False
 | 
			
		||||
 | 
			
		||||
    def read_secondary_demod(self, size):
 | 
			
		||||
        return self.secondary_process_demod.stdout.read(size)
 | 
			
		||||
 | 
			
		||||
    def read_secondary_fft(self, size):
 | 
			
		||||
        return self.secondary_process_fft.stdout.read(size)
 | 
			
		||||
 | 
			
		||||
    def get_secondary_demodulator(self):
 | 
			
		||||
        return self.secondary_demodulator
 | 
			
		||||
 | 
			
		||||
    def set_secondary_fft_size(self,secondary_fft_size):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.secondary_fft_size=secondary_fft_size
 | 
			
		||||
 | 
			
		||||
    def set_audio_compression(self,what):
 | 
			
		||||
        self.audio_compression = what
 | 
			
		||||
 | 
			
		||||
    def set_fft_compression(self,what):
 | 
			
		||||
        self.fft_compression = what
 | 
			
		||||
 | 
			
		||||
    def get_fft_bytes_to_read(self):
 | 
			
		||||
        if self.fft_compression=="none": return self.fft_size*4
 | 
			
		||||
        if self.fft_compression=="adpcm": return (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):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.samp_rate=samp_rate
 | 
			
		||||
        self.decimation=1
 | 
			
		||||
        while self.samp_rate/(self.decimation+1)>self.output_rate:
 | 
			
		||||
            self.decimation+=1
 | 
			
		||||
        self.last_decimation=float(self.if_samp_rate())/self.output_rate
 | 
			
		||||
 | 
			
		||||
    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 set_output_rate(self,output_rate):
 | 
			
		||||
        self.output_rate=output_rate
 | 
			
		||||
        self.set_samp_rate(self.samp_rate) #as it depends on output_rate
 | 
			
		||||
 | 
			
		||||
    def set_demodulator(self,demodulator):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.demodulator=demodulator
 | 
			
		||||
 | 
			
		||||
    def get_demodulator(self):
 | 
			
		||||
        return self.demodulator
 | 
			
		||||
 | 
			
		||||
    def set_fft_size(self,fft_size):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.fft_size=fft_size
 | 
			
		||||
 | 
			
		||||
    def set_fft_fps(self,fft_fps):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.fft_fps=fft_fps
 | 
			
		||||
 | 
			
		||||
    def set_fft_averages(self,fft_averages):
 | 
			
		||||
        #to change this, restart is required
 | 
			
		||||
        self.fft_averages=fft_averages
 | 
			
		||||
 | 
			
		||||
    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_format_conversion(self,format_conversion):
 | 
			
		||||
        self.format_conversion=format_conversion
 | 
			
		||||
 | 
			
		||||
    def set_offset_freq(self,offset_freq):
 | 
			
		||||
        self.offset_freq=offset_freq
 | 
			
		||||
        if self.running:
 | 
			
		||||
            self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
 | 
			
		||||
            self.shift_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
    def set_bpf(self,low_cut,high_cut):
 | 
			
		||||
        self.low_cut=low_cut
 | 
			
		||||
        self.high_cut=high_cut
 | 
			
		||||
        if self.running:
 | 
			
		||||
            self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
 | 
			
		||||
            self.bpf_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
    def get_bpf(self):
 | 
			
		||||
        return [self.low_cut, self.high_cut]
 | 
			
		||||
 | 
			
		||||
    def set_squelch_level(self, squelch_level):
 | 
			
		||||
        self.squelch_level=squelch_level
 | 
			
		||||
        if self.running:
 | 
			
		||||
            self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
 | 
			
		||||
            self.squelch_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
    def get_smeter_level(self):
 | 
			
		||||
        if self.running:
 | 
			
		||||
            line=self.smeter_pipe_file.readline()
 | 
			
		||||
            return float(line[:-1])
 | 
			
		||||
 | 
			
		||||
    def mkfifo(self,path):
 | 
			
		||||
        try:
 | 
			
		||||
            os.unlink(path)
 | 
			
		||||
        except:
 | 
			
		||||
            pass
 | 
			
		||||
        os.mkfifo(path)
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        # print "try_create_pipes"
 | 
			
		||||
        for pipe_name in pipe_names:
 | 
			
		||||
            # print "\t"+pipe_name
 | 
			
		||||
            if "{"+pipe_name+"}" in command_base:
 | 
			
		||||
                setattr(self, pipe_name, self.pipe_base_path+pipe_name)
 | 
			
		||||
                self.mkfifo(getattr(self, pipe_name))
 | 
			
		||||
            else:
 | 
			
		||||
                setattr(self, pipe_name, None)
 | 
			
		||||
 | 
			
		||||
    def try_delete_pipes(self, pipe_names):
 | 
			
		||||
        for pipe_name in pipe_names:
 | 
			
		||||
            pipe_path = getattr(self,pipe_name,None)
 | 
			
		||||
            if pipe_path:
 | 
			
		||||
                try: os.unlink(pipe_path)
 | 
			
		||||
                except Exception as e: print "[openwebrx-dsp-plugin:csdr] try_delete_pipes() ::", e
 | 
			
		||||
 | 
			
		||||
    def set_pipe_nonblocking(self, pipe):
 | 
			
		||||
        flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
 | 
			
		||||
        fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK)
 | 
			
		||||
 | 
			
		||||
    def start(self):
 | 
			
		||||
        command_base=self.chain(self.demodulator)
 | 
			
		||||
 | 
			
		||||
        #create control pipes for csdr
 | 
			
		||||
        self.pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
 | 
			
		||||
        # self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
 | 
			
		||||
 | 
			
		||||
        self.try_create_pipes(self.pipe_names, command_base)
 | 
			
		||||
 | 
			
		||||
        # if "{bpf_pipe}" in command_base:
 | 
			
		||||
            # self.bpf_pipe=pipe_base_path+"bpf"
 | 
			
		||||
            # self.mkfifo(self.bpf_pipe)
 | 
			
		||||
        # if "{shift_pipe}" in command_base:
 | 
			
		||||
            # self.shift_pipe=pipe_base_path+"shift"
 | 
			
		||||
            # self.mkfifo(self.shift_pipe)
 | 
			
		||||
        # if "{squelch_pipe}" in command_base:
 | 
			
		||||
            # self.squelch_pipe=pipe_base_path+"squelch"
 | 
			
		||||
            # self.mkfifo(self.squelch_pipe)
 | 
			
		||||
        # if "{smeter_pipe}" in command_base:
 | 
			
		||||
            # self.smeter_pipe=pipe_base_path+"smeter"
 | 
			
		||||
            # self.mkfifo(self.smeter_pipe)
 | 
			
		||||
        # if "{iqtee_pipe}" in command_base:
 | 
			
		||||
            # self.iqtee_pipe=pipe_base_path+"iqtee"
 | 
			
		||||
            # self.mkfifo(self.iqtee_pipe)
 | 
			
		||||
        # if "{iqtee2_pipe}" in command_base:
 | 
			
		||||
            # self.iqtee2_pipe=pipe_base_path+"iqtee2"
 | 
			
		||||
            # self.mkfifo(self.iqtee2_pipe)
 | 
			
		||||
 | 
			
		||||
        #run the command
 | 
			
		||||
        command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_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, \
 | 
			
		||||
            squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, iqtee_pipe=self.iqtee_pipe, iqtee2_pipe=self.iqtee2_pipe )
 | 
			
		||||
 | 
			
		||||
        print "[openwebrx-dsp-plugin:csdr] Command =",command
 | 
			
		||||
        #code.interact(local=locals())
 | 
			
		||||
        my_env=os.environ.copy()
 | 
			
		||||
        if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
 | 
			
		||||
        if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
 | 
			
		||||
        self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
 | 
			
		||||
        self.running = True
 | 
			
		||||
 | 
			
		||||
        #open control pipes for csdr and send initialization data
 | 
			
		||||
        if self.bpf_pipe != None:
 | 
			
		||||
            self.bpf_pipe_file=open(self.bpf_pipe,"w")
 | 
			
		||||
            self.set_bpf(self.low_cut,self.high_cut)
 | 
			
		||||
        if self.shift_pipe != None:
 | 
			
		||||
            self.shift_pipe_file=open(self.shift_pipe,"w")
 | 
			
		||||
            self.set_offset_freq(self.offset_freq)
 | 
			
		||||
        if self.squelch_pipe != None:
 | 
			
		||||
            self.squelch_pipe_file=open(self.squelch_pipe,"w")
 | 
			
		||||
            self.set_squelch_level(self.squelch_level)
 | 
			
		||||
        if self.smeter_pipe != None:
 | 
			
		||||
            self.smeter_pipe_file=open(self.smeter_pipe,"r")
 | 
			
		||||
            self.set_pipe_nonblocking(self.smeter_pipe_file)
 | 
			
		||||
 | 
			
		||||
        self.start_secondary_demodulator()
 | 
			
		||||
 | 
			
		||||
    def read(self,size):
 | 
			
		||||
        return self.process.stdout.read(size)
 | 
			
		||||
 | 
			
		||||
    def stop(self):
 | 
			
		||||
        os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
 | 
			
		||||
        self.stop_secondary_demodulator()
 | 
			
		||||
        #if(self.process.poll()!=None):return # returns None while subprocess is running
 | 
			
		||||
        #while(self.process.poll()==None):
 | 
			
		||||
        #   #self.process.kill()
 | 
			
		||||
        #   print "killproc",os.getpgid(self.process.pid),self.process.pid
 | 
			
		||||
        #   os.killpg(self.process.pid, signal.SIGTERM)
 | 
			
		||||
        #
 | 
			
		||||
        #   time.sleep(0.1)
 | 
			
		||||
 | 
			
		||||
        self.try_delete_pipes(self.pipe_names)
 | 
			
		||||
 | 
			
		||||
        # if self.bpf_pipe:
 | 
			
		||||
            # try: os.unlink(self.bpf_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
 | 
			
		||||
        # if self.shift_pipe:
 | 
			
		||||
            # try: os.unlink(self.shift_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
 | 
			
		||||
        # if self.squelch_pipe:
 | 
			
		||||
            # try: os.unlink(self.squelch_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
 | 
			
		||||
        # if self.smeter_pipe:
 | 
			
		||||
            # try: os.unlink(self.smeter_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
 | 
			
		||||
        # if self.iqtee_pipe:
 | 
			
		||||
            # try: os.unlink(self.iqtee_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee_pipe
 | 
			
		||||
        # if self.iqtee2_pipe:
 | 
			
		||||
            # try: os.unlink(self.iqtee2_pipe)
 | 
			
		||||
            # except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee2_pipe
 | 
			
		||||
 | 
			
		||||
        self.running = False
 | 
			
		||||
 | 
			
		||||
    def restart(self):
 | 
			
		||||
        self.stop()
 | 
			
		||||
        self.start()
 | 
			
		||||
 | 
			
		||||
    def __del__(self):
 | 
			
		||||
        self.stop()
 | 
			
		||||
        del(self.process)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-3d-spectrum.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								htdocs/gfx/openwebrx-3d-spectrum.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 9.9 KiB  | 
							
								
								
									
										294
									
								
								htdocs/index.wrx
									
									
									
									
									
								
							
							
						
						
									
										294
									
								
								htdocs/index.wrx
									
									
									
									
									
								
							@@ -1,9 +1,9 @@
 | 
			
		||||
<!DOCTYPE HTML>
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
	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>
 | 
			
		||||
    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>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as
 | 
			
		||||
@@ -20,133 +20,171 @@
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
 | 
			
		||||
		<script type="text/javascript">
 | 
			
		||||
			//Global variables
 | 
			
		||||
			var client_id="%[CLIENT_ID]";
 | 
			
		||||
			var ws_url="%[WS_URL]";
 | 
			
		||||
			var rx_photo_height=%[RX_PHOTO_HEIGHT];
 | 
			
		||||
			var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
 | 
			
		||||
			var starting_mod = "%[START_MOD]";
 | 
			
		||||
			var starting_offset_frequency = %[START_OFFSET_FREQ];
 | 
			
		||||
			var waterfall_colors=%[WATERFALL_COLORS];
 | 
			
		||||
			var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
 | 
			
		||||
			var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
 | 
			
		||||
		</script>
 | 
			
		||||
		<script src="sdr.js"></script>
 | 
			
		||||
		<script src="openwebrx.js"></script>
 | 
			
		||||
		<link rel="stylesheet" type="text/css" href="openwebrx.css" />
 | 
			
		||||
		<meta charset="utf-8">
 | 
			
		||||
	</head>
 | 
			
		||||
	<body onload="openwebrx_init();">
 | 
			
		||||
    <head>
 | 
			
		||||
        <title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
 | 
			
		||||
        <script type="text/javascript">
 | 
			
		||||
            //Global variables
 | 
			
		||||
            var client_id="%[CLIENT_ID]";
 | 
			
		||||
            var ws_url="%[WS_URL]";
 | 
			
		||||
            var rx_photo_height=%[RX_PHOTO_HEIGHT];
 | 
			
		||||
            var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
 | 
			
		||||
			var starting_mod="%[START_MOD]";
 | 
			
		||||
            var starting_offset_frequency = %[START_OFFSET_FREQ];
 | 
			
		||||
            var waterfall_colors=%[WATERFALL_COLORS];
 | 
			
		||||
            var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
 | 
			
		||||
            var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
 | 
			
		||||
            var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN];
 | 
			
		||||
            var server_enable_digimodes=%[DIGIMODES_ENABLE];
 | 
			
		||||
			var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
 | 
			
		||||
			var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
 | 
			
		||||
			var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
 | 
			
		||||
        </script>
 | 
			
		||||
        <script src="sdr.js"></script>
 | 
			
		||||
		<script src="mathbox-bundle.min.js"></script>
 | 
			
		||||
        <script src="openwebrx.js"></script>
 | 
			
		||||
        <script src="jquery-3.2.1.min.js"></script>
 | 
			
		||||
        <script src="jquery.nanoscroller.js"></script>
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="nanoscroller.css" />
 | 
			
		||||
        <link rel="stylesheet" type="text/css" href="openwebrx.css" />
 | 
			
		||||
        <meta charset="utf-8">
 | 
			
		||||
    </head>
 | 
			
		||||
    <body onload="openwebrx_init();">
 | 
			
		||||
<div id="webrx-page-container">
 | 
			
		||||
	<div id="webrx-top-container">
 | 
			
		||||
		<div id="webrx-top-photo-clip">
 | 
			
		||||
			<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
 | 
			
		||||
			<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
 | 
			
		||||
			<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
 | 
			
		||||
		<div id="webrx-top-bar" class="webrx-top-bar-parts">
 | 
			
		||||
			<a href="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
 | 
			
		||||
			<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
 | 
			
		||||
			<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
 | 
			
		||||
			<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
 | 
			
		||||
			<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
 | 
			
		||||
			<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
 | 
			
		||||
			<div id="openwebrx-rx-details-arrow">
 | 
			
		||||
				<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
 | 
			
		||||
				<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<section id="openwebrx-main-buttons">
 | 
			
		||||
				<ul>
 | 
			
		||||
					<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
 | 
			
		||||
					<li onmouseup="toggle_panel('openwebrx-panel-log');"><img  src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
 | 
			
		||||
					<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
 | 
			
		||||
				</ul>
 | 
			
		||||
			</section>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div id="webrx-main-container">
 | 
			
		||||
			<div id="openwebrx-scale-container">
 | 
			
		||||
				<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div id="webrx-canvas-container">
 | 
			
		||||
 | 
			
		||||
				<div id="openwebrx-phantom-canvas"></div>
 | 
			
		||||
				<!-- add canvas here by javascript -->
 | 
			
		||||
			</div>
 | 
			
		||||
			<div id="openwebrx-panels-container">
 | 
			
		||||
				<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,260">
 | 
			
		||||
					<div id="webrx-actual-freq">---.--- MHz</div>
 | 
			
		||||
					<div id="webrx-mouse-freq">---.--- MHz</div>
 | 
			
		||||
					<!--<div class="openwebrx-button" onclick="ws.send('SET mod=wfm');" >WFM</div>-->
 | 
			
		||||
					<div class="openwebrx-panel-line">
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dmr');">DMR</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('dstar');">DStar</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nxdn');">NXDN</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('ysf');">YSF</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="openwebrx-panel-line">
 | 
			
		||||
						<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></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" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></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" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
 | 
			
		||||
						<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
 | 
			
		||||
						<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></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">  <img src="gfx/openwebrx-zoom-in.png" /></div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();"  title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
 | 
			
		||||
						<div id="openwebrx-smeter-db">0 dB</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="openwebrx-panel-line">
 | 
			
		||||
						<div id="openwebrx-smeter-outer">
 | 
			
		||||
							<div id="openwebrx-smeter-bar"></div>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="2" data-panel-size="619,142">
 | 
			
		||||
					<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
 | 
			
		||||
						<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
 | 
			
		||||
						<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
 | 
			
		||||
						<div id="openwebrx-debugdiv"></div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
					<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
				</div>
 | 
			
		||||
    <div id="webrx-top-container">
 | 
			
		||||
        <div id="webrx-top-photo-clip">
 | 
			
		||||
            <img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
 | 
			
		||||
            <div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
 | 
			
		||||
            <div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
 | 
			
		||||
        <div id="webrx-top-bar" class="webrx-top-bar-parts">
 | 
			
		||||
            <a href="https://sdr.hu/openwebrx" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
 | 
			
		||||
            <a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
 | 
			
		||||
            <img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
 | 
			
		||||
            <img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
 | 
			
		||||
            <div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
 | 
			
		||||
            <div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
 | 
			
		||||
            <div id="openwebrx-rx-details-arrow">
 | 
			
		||||
                <a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
 | 
			
		||||
                <a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <section id="openwebrx-main-buttons">
 | 
			
		||||
                <ul>
 | 
			
		||||
                    <li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
 | 
			
		||||
                    <li onmouseup="toggle_panel('openwebrx-panel-log');"><img  src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
 | 
			
		||||
                    <li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </section>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div id="webrx-main-container">
 | 
			
		||||
            <div id="openwebrx-scale-container">
 | 
			
		||||
                <canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
 | 
			
		||||
            </div>
 | 
			
		||||
			<div id="openwebrx-mathbox-container"> </div>
 | 
			
		||||
            <div id="webrx-canvas-container">
 | 
			
		||||
                <div id="openwebrx-phantom-canvas"></div>
 | 
			
		||||
                <!-- add canvas here by javascript -->
 | 
			
		||||
            </div>
 | 
			
		||||
            <div id="openwebrx-panels-container">
 | 
			
		||||
                <div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115">
 | 
			
		||||
                    <div id="webrx-actual-freq">---.--- MHz</div>
 | 
			
		||||
                    <div id="webrx-mouse-freq">---.--- MHz</div>
 | 
			
		||||
                    <div class="openwebrx-panel-line">
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
 | 
			
		||||
                            onclick="demodulator_analog_replace('nfm');">FM</div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
 | 
			
		||||
                            onclick="demodulator_analog_replace('am');">AM</div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
 | 
			
		||||
                            onclick="demodulator_analog_replace('lsb');">LSB</div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
 | 
			
		||||
                            onclick="demodulator_analog_replace('usb');">USB</div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
 | 
			
		||||
                            onclick="demodulator_analog_replace('cw');">CW</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dmr"
 | 
			
		||||
						    onclick="demodulator_analog_replace('dmr');">DMR</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dstar"
 | 
			
		||||
						    onclick="demodulator_analog_replace('dstar');">DStar</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nxdn"
 | 
			
		||||
						    onclick="demodulator_analog_replace('nxdn');">NXDN</div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-ysf"
 | 
			
		||||
						    onclick="demodulator_analog_replace('ysf');">YSF</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="openwebrx-panel-line">
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
 | 
			
		||||
                        <select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
 | 
			
		||||
                            <option value="none"></option>
 | 
			
		||||
                            <option value="bpsk31">BPSK31</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="openwebrx-panel-line">
 | 
			
		||||
                        <div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></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" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></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" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
 | 
			
		||||
                        <input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
 | 
			
		||||
                        <div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></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">  <img src="gfx/openwebrx-zoom-in.png" /></div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();"  title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
 | 
			
		||||
                        <div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
 | 
			
		||||
						<div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
 | 
			
		||||
                        <div id="openwebrx-smeter-db">0 dB</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="openwebrx-panel-line">
 | 
			
		||||
                        <div id="openwebrx-smeter-outer">
 | 
			
		||||
                            <div id="openwebrx-smeter-bar"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,137">
 | 
			
		||||
                    <div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
 | 
			
		||||
                        <div class="nano-content">
 | 
			
		||||
                            <div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
 | 
			
		||||
                            <span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
 | 
			
		||||
                            <div id="openwebrx-debugdiv"></div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                    <div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="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" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210">
 | 
			
		||||
                    <div id="openwebrx-digimode-canvas-container">
 | 
			
		||||
                        <div id="openwebrx-digimode-select-channel"></div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div id="openwebrx-digimode-content-container">
 | 
			
		||||
                        <div class="gradient"></div>
 | 
			
		||||
                        <div id="openwebrx-digimode-content">
 | 
			
		||||
                        <span id="openwebrx-cursor-blink"></span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="openwebrx-panel" data-panel-name="metadata" data-panel-pos="left" data-panel-order="1" data-panel-size="615,36">
 | 
			
		||||
                </div>
 | 
			
		||||
				<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="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>
 | 
			
		||||
	</div>
 | 
			
		||||
            </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
	<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
 | 
			
		||||
		<div id="openwebrx-play-button-text">
 | 
			
		||||
		<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
 | 
			
		||||
		<br /><br />Start OpenWebRX
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	</body>
 | 
			
		||||
    <div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
 | 
			
		||||
        <div id="openwebrx-play-button-text">
 | 
			
		||||
        <img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
 | 
			
		||||
        <br /><br />Start OpenWebRX
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								htdocs/jquery-3.2.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								htdocs/jquery-3.2.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1000
									
								
								htdocs/jquery.nanoscroller.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1000
									
								
								htdocs/jquery.nanoscroller.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33
									
								
								htdocs/mathbox-bundle.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								htdocs/mathbox-bundle.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										461
									
								
								htdocs/mathbox.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								htdocs/mathbox.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,461 @@
 | 
			
		||||
.shadergraph-graph {
 | 
			
		||||
  font: 12px sans-serif;
 | 
			
		||||
  line-height: 25px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph:after {
 | 
			
		||||
  content: ' ';
 | 
			
		||||
  display: block;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  font-size: 0;
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph svg {
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-clear {
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph svg {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-column {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node .shadergraph-graph {
 | 
			
		||||
  float: left;
 | 
			
		||||
  clear: both;
 | 
			
		||||
  overflow: visible;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node .shadergraph-graph .shadergraph-node {
 | 
			
		||||
  margin: 5px 15px 15px; 
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node {
 | 
			
		||||
  margin: 5px 15px 25px; 
 | 
			
		||||
  background: rgba(0, 0, 0, .1);
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  box-shadow: 0 1px  2px rgba(0, 0, 0, .2),
 | 
			
		||||
              0 1px  10px rgba(0, 0, 0, .2);
 | 
			
		||||
  min-height: 35px;
 | 
			
		||||
  float: left;
 | 
			
		||||
  clear: left;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-type {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-header {
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  height: 25px;
 | 
			
		||||
  background: rgba(0, 0, 0, .3);
 | 
			
		||||
  text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  border-top-left-radius: 5px;
 | 
			
		||||
  border-top-right-radius: 5px;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
  padding: 0 10px;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet div {
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-in .shadergraph-name {
 | 
			
		||||
  margin-right: 7px;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-out .shadergraph-name {
 | 
			
		||||
  margin-left: 7px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-name {
 | 
			
		||||
  margin: 0 4px;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-point {
 | 
			
		||||
  margin: 6px;
 | 
			
		||||
  width:  11px;
 | 
			
		||||
  height: 11px;
 | 
			
		||||
  border-radius: 7.5px;
 | 
			
		||||
  background: rgba(255, 255, 255, 1);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-in {
 | 
			
		||||
  float: left;
 | 
			
		||||
  clear: left;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-in div {
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-out {
 | 
			
		||||
  float: right;
 | 
			
		||||
  clear: right;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-outlet-out div {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-node-callback {
 | 
			
		||||
  background: rgba(205, 209, 221, .5);
 | 
			
		||||
  box-shadow: 0 1px  2px rgba(0, 10, 40, .2),
 | 
			
		||||
              0 1px  10px rgba(0, 10, 40, .2);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node-callback > .shadergraph-header {
 | 
			
		||||
  background: rgba(0, 20, 80, .3);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-callback {
 | 
			
		||||
  background: rgba(0, 20, 80, .1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-node-call {
 | 
			
		||||
  background: rgba(209, 221, 205, .5);
 | 
			
		||||
  box-shadow: 0 1px  2px rgba(10, 40, 0, .2),
 | 
			
		||||
              0 1px  10px rgba(10, 40, 0, .2);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node-call > .shadergraph-header {
 | 
			
		||||
  background: rgba(20, 80, 0, .3);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-call {
 | 
			
		||||
  background: rgba(20, 80, 0, .1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-node-isolate {
 | 
			
		||||
  background: rgba(221, 205, 209, .5);
 | 
			
		||||
  box-shadow: 0 1px  2px rgba(40, 0, 10, .2),
 | 
			
		||||
              0 1px  10px rgba(40, 0, 10, .2);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node-isolate > .shadergraph-header {
 | 
			
		||||
  background: rgba(80, 0, 20, .3);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-isolate {
 | 
			
		||||
  background: rgba(80, 0, 20, .1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-node.shadergraph-has-code {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node.shadergraph-has-code::before {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  content: ' ';
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  display: none;
 | 
			
		||||
  border: 2px solid rgba(0, 0, 0, .25);
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-node.shadergraph-has-code:hover::before {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-code {
 | 
			
		||||
  z-index: 10000;
 | 
			
		||||
  display: none;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  color: #000;
 | 
			
		||||
  white-space: pre;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  box-shadow: 0 1px  2px rgba(0, 0, 0, .2),
 | 
			
		||||
              0 1px  10px rgba(0, 0, 0, .2);
 | 
			
		||||
  font-family: monospace;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  line-height: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.shadergraph-overlay {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  border-top: 1px solid #CCC;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay .shadergraph-view {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay .shadergraph-inside {
 | 
			
		||||
  width: 4000px;
 | 
			
		||||
  min-height: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay .shadergraph-close {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 5px;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
  padding: 4px;
 | 
			
		||||
  border-radius: 16px;
 | 
			
		||||
  background: rgba(255,255,255,.3);
 | 
			
		||||
  color: rgba(0, 0, 0, .3);
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  line-height: 24px;
 | 
			
		||||
  width: 24px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay .shadergraph-close:hover {
 | 
			
		||||
  background: rgba(255,255,255,1);
 | 
			
		||||
  color: rgba(0, 0, 0, 1);
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay .shadergraph-graph {
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  overflow: visible;
 | 
			
		||||
  min-height: 100%;
 | 
			
		||||
}
 | 
			
		||||
.shadergraph-overlay span {
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding: 5px 15px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  background: rgba(0, 0, 0, .1);
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-loader {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  -webkit-transform: translate(-50%, -50%);
 | 
			
		||||
  transform: translate(-50%, -50%);
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-loader.mathbox-exit {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  -webkit-transition:
 | 
			
		||||
    opacity .15s ease-in-out;
 | 
			
		||||
  transition:
 | 
			
		||||
    opacity .15s ease-in-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-progress {
 | 
			
		||||
  height: 10px;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  width: 80px;
 | 
			
		||||
  margin: 0 auto 20px;
 | 
			
		||||
  box-shadow:
 | 
			
		||||
     1px  1px 1px rgba(255, 255, 255, .2),
 | 
			
		||||
     1px -1px 1px rgba(255, 255, 255, .2),
 | 
			
		||||
    -1px  1px 1px rgba(255, 255, 255, .2),
 | 
			
		||||
    -1px -1px 1px rgba(255, 255, 255, .2);
 | 
			
		||||
  background: #ccc;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-progress > div {
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 0px;
 | 
			
		||||
  height: 10px;
 | 
			
		||||
  background: #888;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 140px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
  margin: 0 auto 10px;
 | 
			
		||||
  -webkit-perspective: 200px;
 | 
			
		||||
  perspective: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > div {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  -webkit-transform-style: preserve-3d;
 | 
			
		||||
  transform-style:         preserve-3d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > :nth-child(1) {
 | 
			
		||||
  -webkit-transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
 | 
			
		||||
  transform:         rotateZ(22deg) rotateX(24deg) rotateY(30deg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > :nth-child(2) {
 | 
			
		||||
  -webkit-transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
 | 
			
		||||
  transform:         rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > div > div {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
  margin-left: -100px;
 | 
			
		||||
  margin-top: -100px;
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  height: 200px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > div > :nth-child(1) {
 | 
			
		||||
  -webkit-transform: scale(0.5, 0.5);
 | 
			
		||||
  transform:         rotateX(30deg) scale(0.5, 0.5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > div > :nth-child(2) {
 | 
			
		||||
  -webkit-transform: rotateX(90deg) scale(0.42, 0.42);
 | 
			
		||||
  transform:         rotateX(90deg) scale(0.42, 0.42);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > div > :nth-child(3) {
 | 
			
		||||
  -webkit-transform: rotateY(90deg) scale(0.35, 0.35);
 | 
			
		||||
  transform:         rotateY(90deg) scale(0.35, 0.35);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-logo > :nth-child(1) > :nth-child(1) {
 | 
			
		||||
  border: 16px solid #808080;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-logo > :nth-child(1) > :nth-child(2) {
 | 
			
		||||
  border: 19px solid #A0A0A0;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-logo > :nth-child(1) > :nth-child(3) {
 | 
			
		||||
  border: 23px solid #C0C0C0;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-logo > :nth-child(2) > :nth-child(1) {
 | 
			
		||||
  border: 27px solid #808080;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-logo > :nth-child(2) > :nth-child(2) {
 | 
			
		||||
  border: 32px solid #A0A0A0;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-logo > :nth-child(2) > :nth-child(3) {
 | 
			
		||||
  border: 38px solid #C0C0C0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mathbox-splash-blue .mathbox-progress {
 | 
			
		||||
  background: #def;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-progress > div {
 | 
			
		||||
  background: #1979e7;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(1) {
 | 
			
		||||
  border-color: #1979e7;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(2) {
 | 
			
		||||
  border-color: #33b0ff;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(3) {
 | 
			
		||||
  border-color: #75eaff;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(1) {
 | 
			
		||||
  border-color: #18487F;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(2) {
 | 
			
		||||
  border-color: #33b0ff;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(3) {
 | 
			
		||||
  border-color: #75eaff;
 | 
			
		||||
}  
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.mathbox-overlays {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left:   0;
 | 
			
		||||
  top:    0;
 | 
			
		||||
  right:  0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  transform-style: preserve-3d;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-overlays > div {
 | 
			
		||||
  transform-style: preserve-3d;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-overlay > div {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  will-change: transform, opacity;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-label {
 | 
			
		||||
  font-family: sans-serif;
 | 
			
		||||
}
 | 
			
		||||
.mathbox-outline-1 {
 | 
			
		||||
  text-shadow:
 | 
			
		||||
    -1px -1px 0px rgb(255, 255, 255),
 | 
			
		||||
     1px  1px 0px rgb(255, 255, 255),
 | 
			
		||||
    -1px  1px 0px rgb(255, 255, 255),
 | 
			
		||||
     1px -1px 0px rgb(255, 255, 255),
 | 
			
		||||
     1px  0px 1px rgb(255, 255, 255),
 | 
			
		||||
    -1px  0px 1px rgb(255, 255, 255),
 | 
			
		||||
     0px -1px 1px rgb(255, 255, 255),
 | 
			
		||||
     0px  1px 1px rgb(255, 255, 255);
 | 
			
		||||
}
 | 
			
		||||
.mathbox-outline-2 {
 | 
			
		||||
  text-shadow:
 | 
			
		||||
     0px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
     0px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
    -2px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
    -1px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
    -2px -1px 0px rgb(255, 255, 255),
 | 
			
		||||
    -1px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
    -2px  1px 0px rgb(255, 255, 255),
 | 
			
		||||
     1px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px  1px 0px rgb(255, 255, 255),
 | 
			
		||||
     1px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px -1px 0px rgb(255, 255, 255);
 | 
			
		||||
}
 | 
			
		||||
.mathbox-outline-3 {
 | 
			
		||||
  text-shadow:
 | 
			
		||||
     3px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
    -3px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
     0px  3px 0px rgb(255, 255, 255),
 | 
			
		||||
     0px -3px 0px rgb(255, 255, 255),
 | 
			
		||||
 | 
			
		||||
    -2px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
    -2px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
 | 
			
		||||
    -1px -2px 1px rgb(255, 255, 255),
 | 
			
		||||
    -2px -1px 1px rgb(255, 255, 255),
 | 
			
		||||
    -1px  2px 1px rgb(255, 255, 255),
 | 
			
		||||
    -2px  1px 1px rgb(255, 255, 255),
 | 
			
		||||
     1px  2px 1px rgb(255, 255, 255),
 | 
			
		||||
     2px  1px 1px rgb(255, 255, 255),
 | 
			
		||||
     1px -2px 1px rgb(255, 255, 255),
 | 
			
		||||
     2px -1px 1px rgb(255, 255, 255);
 | 
			
		||||
}
 | 
			
		||||
.mathbox-outline-4 {
 | 
			
		||||
  text-shadow:
 | 
			
		||||
     4px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
    -4px  0px 0px rgb(255, 255, 255),
 | 
			
		||||
     0px  4px 0px rgb(255, 255, 255),
 | 
			
		||||
     0px -4px 0px rgb(255, 255, 255),
 | 
			
		||||
   
 | 
			
		||||
    -3px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
    -3px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
     3px  2px 0px rgb(255, 255, 255),
 | 
			
		||||
     3px -2px 0px rgb(255, 255, 255),
 | 
			
		||||
   
 | 
			
		||||
    -2px -3px 0px rgb(255, 255, 255),
 | 
			
		||||
    -2px  3px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px  3px 0px rgb(255, 255, 255),
 | 
			
		||||
     2px -3px 0px rgb(255, 255, 255),
 | 
			
		||||
   
 | 
			
		||||
    -1px -2px 1px rgb(255, 255, 255),
 | 
			
		||||
    -2px -1px 1px rgb(255, 255, 255),
 | 
			
		||||
    -1px  2px 1px rgb(255, 255, 255),
 | 
			
		||||
    -2px  1px 1px rgb(255, 255, 255),
 | 
			
		||||
     1px  2px 1px rgb(255, 255, 255),
 | 
			
		||||
     2px  1px 1px rgb(255, 255, 255),
 | 
			
		||||
     1px -2px 1px rgb(255, 255, 255),
 | 
			
		||||
     2px -1px 1px rgb(255, 255, 255);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.mathbox-outline-fill, .mathbox-outline-fill * {
 | 
			
		||||
  color: #fff !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								htdocs/nanoscroller.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								htdocs/nanoscroller.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
/** initial setup **/
 | 
			
		||||
.nano {
 | 
			
		||||
  position : relative;
 | 
			
		||||
  width    : 100%;
 | 
			
		||||
  height   : 100%;
 | 
			
		||||
  overflow : hidden;
 | 
			
		||||
}
 | 
			
		||||
.nano > .nano-content {
 | 
			
		||||
  position      : absolute;
 | 
			
		||||
  overflow      : scroll;
 | 
			
		||||
  overflow-x    : hidden;
 | 
			
		||||
  top           : 0;
 | 
			
		||||
  right         : 0;
 | 
			
		||||
  bottom        : 0;
 | 
			
		||||
  left          : 0;
 | 
			
		||||
}
 | 
			
		||||
.nano > .nano-content:focus {
 | 
			
		||||
  outline: thin dotted;
 | 
			
		||||
}
 | 
			
		||||
.nano > .nano-content::-webkit-scrollbar {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
.has-scrollbar > .nano-content::-webkit-scrollbar {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.nano > .nano-pane {
 | 
			
		||||
  background : rgba(0,0,0,.25);
 | 
			
		||||
  position   : absolute;
 | 
			
		||||
  width      : 8px;
 | 
			
		||||
  right      : 0;
 | 
			
		||||
  top        : 0;
 | 
			
		||||
  bottom     : 0;
 | 
			
		||||
  visibility : hidden\9; /* Target only IE7 and IE8 with this hack */
 | 
			
		||||
  opacity    : .01;
 | 
			
		||||
  -webkit-transition    : .2s;
 | 
			
		||||
  -moz-transition       : .2s;
 | 
			
		||||
  -o-transition         : .2s;
 | 
			
		||||
  transition            : .2s;
 | 
			
		||||
  -moz-border-radius    : 3px;
 | 
			
		||||
  -webkit-border-radius : 3px;
 | 
			
		||||
  border-radius         : 3px;
 | 
			
		||||
}
 | 
			
		||||
.nano > .nano-pane > .nano-slider {
 | 
			
		||||
  background: #444;
 | 
			
		||||
  background: rgba(0,0,0,.5);
 | 
			
		||||
  position              : relative;
 | 
			
		||||
  margin                : 0 0px;
 | 
			
		||||
  -moz-border-radius    : 4px;
 | 
			
		||||
  -webkit-border-radius : 4px;
 | 
			
		||||
  border-radius         : 4px;
 | 
			
		||||
}
 | 
			
		||||
.nano:hover > .nano-pane, .nano-pane.active, .nano-pane.flashed {
 | 
			
		||||
  visibility : visible\9; /* Target only IE7 and IE8 with this hack */
 | 
			
		||||
  opacity    : 0.99;
 | 
			
		||||
}
 | 
			
		||||
@@ -28,6 +28,11 @@ html, body
 | 
			
		||||
	overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
select
 | 
			
		||||
{
 | 
			
		||||
	font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input
 | 
			
		||||
{
 | 
			
		||||
	vertical-align:middle;
 | 
			
		||||
@@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
	border-style: none;
 | 
			
		||||
	image-rendering: crisp-edges;
 | 
			
		||||
	image-rendering: -webkit-optimize-contrast;
 | 
			
		||||
    /*transition: left 200ms, width 200ms;*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-mathbox-container
 | 
			
		||||
{
 | 
			
		||||
	overflow: none;
 | 
			
		||||
	display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-phantom-canvas
 | 
			
		||||
@@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
	height: 396px;
 | 
			
		||||
}*/
 | 
			
		||||
 | 
			
		||||
/*#webrx-debugdiv
 | 
			
		||||
#openwebrx-log-scroll
 | 
			
		||||
{
 | 
			
		||||
	font-size: 10pt;
 | 
			
		||||
	/*overflow-y:scroll;*/
 | 
			
		||||
/*}*/
 | 
			
		||||
    /*overflow-y:auto;*/
 | 
			
		||||
    height: 125px;
 | 
			
		||||
    width: 619px
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nano .nano-pane   { background: #444; }
 | 
			
		||||
.nano .nano-slider { background: #eee !important;  }
 | 
			
		||||
 | 
			
		||||
#webrx-main-container
 | 
			
		||||
{
 | 
			
		||||
@@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
 | 
			
		||||
.openwebrx-panel
 | 
			
		||||
{
 | 
			
		||||
	transform: perspective( 600px );
 | 
			
		||||
	transform: perspective( 600px ) rotateX( 90deg );
 | 
			
		||||
	visibility: hidden;
 | 
			
		||||
	background-color: #575757;
 | 
			
		||||
	padding: 10px;
 | 
			
		||||
@@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
.openwebrx-button
 | 
			
		||||
{
 | 
			
		||||
	background-color: #373737;
 | 
			
		||||
	padding: 5px;
 | 
			
		||||
	padding: 4.2px;
 | 
			
		||||
	border-radius: 5px;
 | 
			
		||||
	-moz-border-radius: 5px;
 | 
			
		||||
	color: White;
 | 
			
		||||
@@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.openwebrx-button:hover
 | 
			
		||||
.openwebrx-button:hover, .openwebrx-demodulator-button.highlighted
 | 
			
		||||
{
 | 
			
		||||
	/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #3F3F3F), color-stop(1, #777777) );
 | 
			
		||||
	background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/
 | 
			
		||||
@@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
 | 
			
		||||
.openwebrx-demodulator-button
 | 
			
		||||
{
 | 
			
		||||
	width: 50px;
 | 
			
		||||
	width: 38px;
 | 
			
		||||
	height: 19px;
 | 
			
		||||
	font-size: 12pt;
 | 
			
		||||
	text-align: center;
 | 
			
		||||
@@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper
 | 
			
		||||
 | 
			
		||||
.openwebrx-square-button img
 | 
			
		||||
{
 | 
			
		||||
	height: 30px;
 | 
			
		||||
	height: 27px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.openwebrx-round-button
 | 
			
		||||
@@ -732,7 +748,7 @@ img.openwebrx-mirror-img
 | 
			
		||||
{
 | 
			
		||||
	position: relative;
 | 
			
		||||
	top: -2px;
 | 
			
		||||
	width:91px;
 | 
			
		||||
	width: 95px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.openwebrx-sliderbtn-img
 | 
			
		||||
@@ -776,7 +792,7 @@ img.openwebrx-mirror-img
 | 
			
		||||
	font-size: 10pt;
 | 
			
		||||
	float: right;
 | 
			
		||||
	margin-right: 5px;
 | 
			
		||||
	margin-top: 29px;
 | 
			
		||||
	margin-top: 24px;
 | 
			
		||||
	font-family: 'expletus-sans-medium';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -806,3 +822,152 @@ img.openwebrx-mirror-img
 | 
			
		||||
{
 | 
			
		||||
	width: 150px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-canvas-container
 | 
			
		||||
{
 | 
			
		||||
	/*margin: -10px -10px 10px -10px;*/
 | 
			
		||||
    margin: -10px -10px 0px -10px;
 | 
			
		||||
	border-radius: 15px;
 | 
			
		||||
	height: 150px;
 | 
			
		||||
	background-color: #333;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-canvas-container canvas
 | 
			
		||||
{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    transition: width 500ms, left 500ms;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-secondary-demod-listbox
 | 
			
		||||
{
 | 
			
		||||
	width: 201px;
 | 
			
		||||
	height: 27px;
 | 
			
		||||
	border-radius: 5px;
 | 
			
		||||
	background-color: #373737;
 | 
			
		||||
	color: White;
 | 
			
		||||
	font-weight: normal;
 | 
			
		||||
	font-size: 13pt;
 | 
			
		||||
	margin-right: 1px;
 | 
			
		||||
	background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0	, #373737), color-stop(1, #4F4F4F) );
 | 
			
		||||
	background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% );
 | 
			
		||||
	border-color: transparent;
 | 
			
		||||
	border-width: 0px;
 | 
			
		||||
	-moz-appearance: none;
 | 
			
		||||
	padding-left:3px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-secondary-demod-listbox option
 | 
			
		||||
{
 | 
			
		||||
	border-width: 0px;
 | 
			
		||||
	background-color: #373737;
 | 
			
		||||
	color: White;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-cursor-blink
 | 
			
		||||
{
 | 
			
		||||
	animation: cursor-blink 1s infinite;
 | 
			
		||||
	/*animation: cursor-3d 2s infinite;*/
 | 
			
		||||
	animation-timing-function: linear;
 | 
			
		||||
	animation-direction: alternate;
 | 
			
		||||
	height: 1em;
 | 
			
		||||
	width: 8px;
 | 
			
		||||
	background-color: White;
 | 
			
		||||
	display: inline-block;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    top: 1px;
 | 
			
		||||
	/*perspective: 60px;*/
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes cursor-blink
 | 
			
		||||
{
 | 
			
		||||
	0%{ opacity: 0; }
 | 
			
		||||
	50% { opacity: 1; }
 | 
			
		||||
	100%{ opacity: 0; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes cursor-3d
 | 
			
		||||
{
 | 
			
		||||
	0%{ transform: rotateX(0deg) rotateX(Ydeg); }
 | 
			
		||||
	50% { transform: rotateX(180deg) rotateY(360deg); opacity: 0.1; }
 | 
			
		||||
	100%{ transform: rotateX(360deg) rotateY(720deg); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content
 | 
			
		||||
{
 | 
			
		||||
    word-wrap: break-word;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content-container
 | 
			
		||||
{
 | 
			
		||||
    overflow-y: hidden;
 | 
			
		||||
    display: block;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content-container .gradient
 | 
			
		||||
{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    background: linear-gradient(to top,  rgba(87,87,87,0) 0%,rgba(87,87,87,1) 100%);
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content .part
 | 
			
		||||
{
 | 
			
		||||
    perspective: 700px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content .part
 | 
			
		||||
{
 | 
			
		||||
    animation: new-digimode-data-3d 100ms;
 | 
			
		||||
	animation-timing-function: linear;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    perspective-origin: 50% 50%;
 | 
			
		||||
    transform-origin: 0% 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-content .part .subpart
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@keyframes new-digimode-data
 | 
			
		||||
{
 | 
			
		||||
	0%{ opacity: 0; }
 | 
			
		||||
	100%{ opacity: 1; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes new-digimode-data-3d
 | 
			
		||||
{
 | 
			
		||||
	0%{ transform: rotateX(0deg) rotateY(-90deg) translateX(-5px) scale(1.3); }
 | 
			
		||||
	100%{ transform: rotateX(0deg) rotateY(0deg) translateX(0) scale(1); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#openwebrx-digimode-select-channel
 | 
			
		||||
{
 | 
			
		||||
    transition: all 500ms;
 | 
			
		||||
    background-color: Yellow;
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    pointer-events: none;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    width: 0px;
 | 
			
		||||
    top: 0px;
 | 
			
		||||
    left: 0px;
 | 
			
		||||
    opacity: 0.7;
 | 
			
		||||
    border-style: solid;
 | 
			
		||||
    border-width: 0px;
 | 
			
		||||
    border-color: Red;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,7 @@ var audio_compression="none";
 | 
			
		||||
var waterfall_setup_done=0;
 | 
			
		||||
var waterfall_queue = [];
 | 
			
		||||
var waterfall_timer;
 | 
			
		||||
var secondary_fft_size;
 | 
			
		||||
 | 
			
		||||
/*function fade(something,from,to,time_ms,fps)
 | 
			
		||||
{
 | 
			
		||||
@@ -72,7 +73,8 @@ var rx_photo_state=1;
 | 
			
		||||
 | 
			
		||||
function e(what) { return document.getElementById(what); }
 | 
			
		||||
 | 
			
		||||
ios = /iPad|iPod|iPhone/.test(navigator.userAgent);
 | 
			
		||||
ios = /iPad|iPod|iPhone|Chrome/.test(navigator.userAgent);
 | 
			
		||||
is_chrome = /Chrome/.test(navigator.userAgent);
 | 
			
		||||
//alert("ios="+ios.toString()+"  "+navigator.userAgent);
 | 
			
		||||
 | 
			
		||||
function init_rx_photo()
 | 
			
		||||
@@ -183,8 +185,8 @@ function waterfallColorsDefault()
 | 
			
		||||
 | 
			
		||||
function waterfallColorsAuto()
 | 
			
		||||
{
 | 
			
		||||
	e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-20).toString();
 | 
			
		||||
	e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+30).toString();
 | 
			
		||||
	e("openwebrx-waterfall-color-min").value=(waterfall_measure_minmax_min-waterfall_auto_level_margin[0]).toString();
 | 
			
		||||
	e("openwebrx-waterfall-color-max").value=(waterfall_measure_minmax_max+waterfall_auto_level_margin[1]).toString();
 | 
			
		||||
	updateWaterfallColors(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -559,6 +561,7 @@ function demodulator_default_analog(offset_frequency,subtype)
 | 
			
		||||
 | 
			
		||||
	this.envelope.drag_end=function(x)
 | 
			
		||||
	{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
 | 
			
		||||
		demodulator_buttons_update();
 | 
			
		||||
		to_return=this.dragged_range!=demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset
 | 
			
		||||
		this.dragged_range=demodulator.draggable_ranges.none;
 | 
			
		||||
		return to_return;
 | 
			
		||||
@@ -575,6 +578,7 @@ function mkenvelopes(visible_range) //called from mkscale
 | 
			
		||||
	{
 | 
			
		||||
		demodulators[i].envelope.draw(visible_range);
 | 
			
		||||
	}
 | 
			
		||||
    if(demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function demodulator_remove(which)
 | 
			
		||||
@@ -589,8 +593,17 @@ function demodulator_add(what)
 | 
			
		||||
	mkenvelopes(get_visible_freq_range());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function demodulator_analog_replace(subtype)
 | 
			
		||||
last_analog_demodulator_subtype = 'nfm';
 | 
			
		||||
last_digital_demodulator_subtype = 'bpsk31';
 | 
			
		||||
 | 
			
		||||
function demodulator_analog_replace(subtype, for_digital)
 | 
			
		||||
{ //this function should only exist until the multi-demodulator capability is added
 | 
			
		||||
    if(!(typeof for_digital !== "undefined" && for_digital && secondary_demod)) 
 | 
			
		||||
    { 
 | 
			
		||||
        secondary_demod_close_window(); 
 | 
			
		||||
        secondary_demod_listbox_update(); 
 | 
			
		||||
    }
 | 
			
		||||
    last_analog_demodulator_subtype = subtype;
 | 
			
		||||
	var temp_offset=0;
 | 
			
		||||
	if(demodulators.length)
 | 
			
		||||
	{
 | 
			
		||||
@@ -598,6 +611,7 @@ function demodulator_analog_replace(subtype)
 | 
			
		||||
		demodulator_remove(0);
 | 
			
		||||
	}
 | 
			
		||||
	demodulator_add(new demodulator_default_analog(temp_offset,subtype));
 | 
			
		||||
	demodulator_buttons_update();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function demodulator_set_offset_frequency(which,to_what)
 | 
			
		||||
@@ -1028,7 +1042,7 @@ function canvas_mousewheel(evt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
zoom_max_level_hps=33; //Hz/pixel
 | 
			
		||||
zoom_levels_count=5;
 | 
			
		||||
zoom_levels_count=14;
 | 
			
		||||
 | 
			
		||||
function get_zoom_coeff_from_hps(hps)
 | 
			
		||||
{
 | 
			
		||||
@@ -1050,8 +1064,10 @@ function mkzoomlevels()
 | 
			
		||||
	zoom_levels=[1];
 | 
			
		||||
	maxc=get_zoom_coeff_from_hps(zoom_max_level_hps);
 | 
			
		||||
	if(maxc<1) return;
 | 
			
		||||
	// logarithmic interpolation
 | 
			
		||||
	zoom_ratio = Math.pow(maxc, 1/zoom_levels_count);
 | 
			
		||||
	for(i=1;i<zoom_levels_count;i++)
 | 
			
		||||
		zoom_levels.push(1+(maxc-1)*(i/(zoom_levels_count-1)));
 | 
			
		||||
		zoom_levels.push(Math.pow(zoom_ratio, i));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function zoom_step(out, where, onscreen)
 | 
			
		||||
@@ -1063,7 +1079,7 @@ function zoom_step(out, where, onscreen)
 | 
			
		||||
	zoom_center_rel=canvas_get_freq_offset(where);
 | 
			
		||||
	//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString());
 | 
			
		||||
	zoom_center_where=onscreen;
 | 
			
		||||
	console.log(zoom_center_where, zoom_center_rel, where);
 | 
			
		||||
	//console.log(zoom_center_where, zoom_center_rel, where);
 | 
			
		||||
	resize_canvases(true);
 | 
			
		||||
	mkscale();
 | 
			
		||||
}
 | 
			
		||||
@@ -1095,9 +1111,17 @@ function zoom_calc()
 | 
			
		||||
function resize_waterfall_container(check_init)
 | 
			
		||||
{
 | 
			
		||||
	if(check_init&&!waterfall_setup_done) return;
 | 
			
		||||
	canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
 | 
			
		||||
}
 | 
			
		||||
	var numHeight;
 | 
			
		||||
	mathbox_container.style.height=canvas_container.style.height=(numHeight=window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
 | 
			
		||||
	if(mathbox)
 | 
			
		||||
	{
 | 
			
		||||
		//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight;
 | 
			
		||||
  		//mathbox.three.camera.updateProjectionMatrix();
 | 
			
		||||
		mathbox.three.renderer.setSize(document.body.offsetWidth, numHeight);
 | 
			
		||||
		console.log(document.body.offsetWidth, numHeight);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
audio_server_output_rate=11025;
 | 
			
		||||
audio_client_resampling_factor=4;
 | 
			
		||||
@@ -1134,14 +1158,15 @@ function on_ws_recv(evt)
 | 
			
		||||
	if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
 | 
			
		||||
	//
 | 
			
		||||
	debug_ws_data_received+=evt.data.byteLength/1000;
 | 
			
		||||
	firstChars=getFirstChars(evt.data,3);
 | 
			
		||||
	if(firstChars=="CLI")
 | 
			
		||||
	first4Chars=getFirstChars(evt.data,4);
 | 
			
		||||
    first3Chars=first4Chars.slice(0,3);
 | 
			
		||||
	if(first3Chars=="CLI")
 | 
			
		||||
	{
 | 
			
		||||
		var stringData=arrayBufferToString(evt.data);
 | 
			
		||||
		if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData);
 | 
			
		||||
		if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Server acknowledged WebSocket connection.");
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	if(firstChars=="AUD")
 | 
			
		||||
	if(first3Chars=="AUD")
 | 
			
		||||
	{
 | 
			
		||||
		var audio_data;
 | 
			
		||||
		if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4)
 | 
			
		||||
@@ -1149,11 +1174,12 @@ function on_ws_recv(evt)
 | 
			
		||||
		audio_prepare(audio_data);
 | 
			
		||||
		audio_buffer_current_size_debug+=audio_data.length;
 | 
			
		||||
		audio_buffer_all_size_debug+=audio_data.length;
 | 
			
		||||
		if(!ios && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init()
 | 
			
		||||
		if(!(ios||is_chrome) && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init()
 | 
			
		||||
	}
 | 
			
		||||
	else if(firstChars=="FFT")
 | 
			
		||||
	else if(first3Chars=="FFT")
 | 
			
		||||
	{
 | 
			
		||||
		//alert("Yupee! Doing FFT");
 | 
			
		||||
        //if(first4Chars=="FFTS") console.log("FFTS"); 
 | 
			
		||||
		if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
 | 
			
		||||
		else if(fft_compression="adpcm")
 | 
			
		||||
		{
 | 
			
		||||
@@ -1162,9 +1188,17 @@ function on_ws_recv(evt)
 | 
			
		||||
			var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
 | 
			
		||||
			var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
 | 
			
		||||
			for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
 | 
			
		||||
			waterfall_add_queue(waterfall_f32);
 | 
			
		||||
            if(first4Chars=="FFTS") secondary_demod_waterfall_add_queue(waterfall_f32); //TODO digimodes
 | 
			
		||||
            else waterfall_add_queue(waterfall_f32);
 | 
			
		||||
		}
 | 
			
		||||
	} else if(firstChars=="MSG")
 | 
			
		||||
	} 
 | 
			
		||||
    else if(first3Chars=="DAT")
 | 
			
		||||
    {
 | 
			
		||||
        //secondary_demod_push_binary_data(new Uint8Array(evt.data,4));
 | 
			
		||||
        secondary_demod_push_data(arrayBufferToString(evt.data).substring(4));
 | 
			
		||||
        //console.log("DAT");
 | 
			
		||||
	} 
 | 
			
		||||
    else if(first3Chars=="MSG")
 | 
			
		||||
	{
 | 
			
		||||
		/*try
 | 
			
		||||
		{*/
 | 
			
		||||
@@ -1188,6 +1222,18 @@ function on_ws_recv(evt)
 | 
			
		||||
					case "fft_size":
 | 
			
		||||
						fft_size=parseInt(param[1]);
 | 
			
		||||
						break;
 | 
			
		||||
					case "secondary_fft_size":
 | 
			
		||||
						secondary_fft_size=parseInt(param[1]);
 | 
			
		||||
						break;
 | 
			
		||||
                    case "secondary_setup":
 | 
			
		||||
                        secondary_demod_init_canvases();
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "if_samp_rate":
 | 
			
		||||
                        if_samp_rate=parseInt(param[1]);
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "secondary_bw":
 | 
			
		||||
                        secondary_bw=parseFloat(param[1]);
 | 
			
		||||
                        break;
 | 
			
		||||
					case "fft_fps":
 | 
			
		||||
						fft_fps=parseInt(param[1]);
 | 
			
		||||
						break;
 | 
			
		||||
@@ -1336,8 +1382,10 @@ function divlog(what, is_error)
 | 
			
		||||
		if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
 | 
			
		||||
	}
 | 
			
		||||
	e("openwebrx-debugdiv").innerHTML+=what+"<br />";
 | 
			
		||||
	var wls=e("openwebrx-log-scroll");
 | 
			
		||||
	wls.scrollTop=wls.scrollHeight; //scroll to bottom
 | 
			
		||||
	//var wls=e("openwebrx-log-scroll");
 | 
			
		||||
	//wls.scrollTop=wls.scrollHeight; //scroll to bottom
 | 
			
		||||
    $(".nano").nanoScroller();
 | 
			
		||||
    $(".nano").nanoScroller({ scroll: 'bottom' });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var audio_context;
 | 
			
		||||
@@ -1585,19 +1633,27 @@ function webrx_set_param(what, value)
 | 
			
		||||
	ws.send("SET "+what+"="+value.toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var starting_mute = false;
 | 
			
		||||
 | 
			
		||||
function parsehash()
 | 
			
		||||
{
 | 
			
		||||
	if(h=window.location.hash)
 | 
			
		||||
	{
 | 
			
		||||
		h.substring(1).split(",").forEach(function(x){
 | 
			
		||||
			harr=x.split("=");
 | 
			
		||||
			console.log(harr);
 | 
			
		||||
			if(harr[0]=="mod") starting_mod = harr[1];
 | 
			
		||||
			if(harr[0]=="sql") { e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); }
 | 
			
		||||
			if(harr[0]=="freq") {
 | 
			
		||||
			console.log(parseInt(harr[1]));
 | 
			
		||||
			console.log(center_freq);
 | 
			
		||||
			starting_offset_frequency = parseInt(harr[1])-center_freq;
 | 
			
		||||
			//console.log(harr);
 | 
			
		||||
			if(harr[0]=="mute") toggleMute();
 | 
			
		||||
			else if(harr[0]=="mod") starting_mod = harr[1];
 | 
			
		||||
			else if(harr[0]=="sql") 
 | 
			
		||||
			{ 
 | 
			
		||||
				e("openwebrx-panel-squelch").value=harr[1]; 
 | 
			
		||||
				updateSquelch(); 
 | 
			
		||||
			}
 | 
			
		||||
			else if(harr[0]=="freq") 
 | 
			
		||||
			{
 | 
			
		||||
				console.log(parseInt(harr[1]));
 | 
			
		||||
				console.log(center_freq);
 | 
			
		||||
				starting_offset_frequency = parseInt(harr[1])-center_freq;
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
@@ -1638,6 +1694,9 @@ function audio_preinit()
 | 
			
		||||
 | 
			
		||||
function audio_init()
 | 
			
		||||
{
 | 
			
		||||
    if(is_chrome) audio_context.resume()
 | 
			
		||||
	if(starting_mute) toggleMute();
 | 
			
		||||
 | 
			
		||||
	if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor...
 | 
			
		||||
 | 
			
		||||
	audio_debug_time_start=(new Date()).getTime();
 | 
			
		||||
@@ -1682,6 +1741,7 @@ function audio_init()
 | 
			
		||||
			//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
 | 
			
		||||
		}
 | 
			
		||||
	},2000);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function on_ws_closed()
 | 
			
		||||
@@ -1722,17 +1782,18 @@ function open_websocket()
 | 
			
		||||
	ws.onerror = on_ws_error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function waterfall_mkcolor(db_value)
 | 
			
		||||
function waterfall_mkcolor(db_value, waterfall_colors_arg)
 | 
			
		||||
{
 | 
			
		||||
	if(typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors;
 | 
			
		||||
	if(db_value<waterfall_min_level) db_value=waterfall_min_level;
 | 
			
		||||
	if(db_value>waterfall_max_level) db_value=waterfall_max_level;
 | 
			
		||||
	full_scale=waterfall_max_level-waterfall_min_level;
 | 
			
		||||
	relative_value=db_value-waterfall_min_level;
 | 
			
		||||
	value_percent=relative_value/full_scale;
 | 
			
		||||
	percent_for_one_color=1/(waterfall_colors.length-1);
 | 
			
		||||
	percent_for_one_color=1/(waterfall_colors_arg.length-1);
 | 
			
		||||
	index=Math.floor(value_percent/percent_for_one_color);
 | 
			
		||||
	remain=(value_percent-percent_for_one_color*index)/percent_for_one_color;
 | 
			
		||||
	return color_between(waterfall_colors[index+1],waterfall_colors[index],remain);
 | 
			
		||||
	return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function color_between(first, second, percent)
 | 
			
		||||
@@ -1755,7 +1816,7 @@ var canvas_phantom;
 | 
			
		||||
 | 
			
		||||
function add_canvas()
 | 
			
		||||
{
 | 
			
		||||
	new_canvas = document.createElement("canvas");
 | 
			
		||||
	var new_canvas = document.createElement("canvas");
 | 
			
		||||
	new_canvas.width=fft_size;
 | 
			
		||||
	new_canvas.height=canvas_default_height;
 | 
			
		||||
	canvas_actual_line=canvas_default_height-1;
 | 
			
		||||
@@ -1780,9 +1841,11 @@ function add_canvas()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function init_canvas_container()
 | 
			
		||||
{
 | 
			
		||||
	canvas_container=e("webrx-canvas-container");
 | 
			
		||||
	mathbox_container=e("openwebrx-mathbox-container");
 | 
			
		||||
	canvas_container.addEventListener("mouseout",canvas_container_mouseout, false);
 | 
			
		||||
	//window.addEventListener("mouseout",window_mouseout,false);
 | 
			
		||||
	//document.body.addEventListener("mouseup",body_mouseup,false);
 | 
			
		||||
@@ -1839,7 +1902,7 @@ function resize_canvases(zoom)
 | 
			
		||||
function waterfall_init()
 | 
			
		||||
{
 | 
			
		||||
	init_canvas_container();
 | 
			
		||||
	waterfall_timer = window.setInterval(waterfall_dequeue,900/fft_fps);
 | 
			
		||||
	waterfall_timer = window.setInterval(()=>{waterfall_dequeue(); secondary_demod_waterfall_dequeue();},900/fft_fps);
 | 
			
		||||
	resize_waterfall_container(false); /* then */ resize_canvases();
 | 
			
		||||
	scale_setup();
 | 
			
		||||
	mkzoomlevels();
 | 
			
		||||
@@ -1848,6 +1911,42 @@ function waterfall_init()
 | 
			
		||||
 | 
			
		||||
var waterfall_dont_scale=0;
 | 
			
		||||
 | 
			
		||||
var mathbox_shift = function()
 | 
			
		||||
{
 | 
			
		||||
	if(mathbox_data_current_depth < mathbox_data_max_depth) mathbox_data_current_depth++;
 | 
			
		||||
	if(mathbox_data_index+1>=mathbox_data_max_depth) mathbox_data_index = 0;
 | 
			
		||||
	else mathbox_data_index++;
 | 
			
		||||
	mathbox_data_global_index++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var mathbox_clear_data = function()
 | 
			
		||||
{
 | 
			
		||||
	mathbox_data_index = 50;
 | 
			
		||||
	mathbox_data_current_depth = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//var mathbox_get_data_line = function(x) //x counts from 0 to mathbox_data_current_depth
 | 
			
		||||
//{
 | 
			
		||||
//	return (mathbox_data_max_depth + mathbox_data_index - mathbox_data_current_depth + x - 1) % mathbox_data_max_depth;
 | 
			
		||||
//}
 | 
			
		||||
//
 | 
			
		||||
//var mathbox_data_index_valid = function(x) //x counts from 0 to mathbox_data_current_depth
 | 
			
		||||
//{
 | 
			
		||||
//	return x<mathbox_data_current_depth;
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
var mathbox_get_data_line = function(x)
 | 
			
		||||
{
 | 
			
		||||
	return (mathbox_data_max_depth + mathbox_data_index + x - 1) % mathbox_data_max_depth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var mathbox_data_index_valid = function(x)
 | 
			
		||||
{
 | 
			
		||||
	return x>mathbox_data_max_depth-mathbox_data_current_depth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function waterfall_add(data)
 | 
			
		||||
{
 | 
			
		||||
	if(!waterfall_setup_done) return;
 | 
			
		||||
@@ -1924,6 +2023,14 @@ function waterfall_add(data)
 | 
			
		||||
			waterfall_image.data[base+x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
 | 
			
		||||
	}*/
 | 
			
		||||
 | 
			
		||||
	if(mathbox_mode==MATHBOX_MODES.WATERFALL)
 | 
			
		||||
	{
 | 
			
		||||
		//Handle mathbox
 | 
			
		||||
		for(var i=0;i<fft_size;i++) mathbox_data[i+mathbox_data_index*fft_size]=data[i];
 | 
			
		||||
		mathbox_shift();
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
	//Add line to waterfall image
 | 
			
		||||
	oneline_image = canvas_context.createImageData(w,1);
 | 
			
		||||
	for(x=0;x<w;x++)
 | 
			
		||||
@@ -1933,13 +2040,13 @@ function waterfall_add(data)
 | 
			
		||||
			oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	//Draw image
 | 
			
		||||
	canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
 | 
			
		||||
	shift_canvases();
 | 
			
		||||
	if(canvas_actual_line<0) add_canvas();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	//divlog("Drawn FFT");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
@@ -1969,6 +2076,180 @@ function check_top_bar_congestion()
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var MATHBOX_MODES =
 | 
			
		||||
{
 | 
			
		||||
	UNINITIALIZED: 0,
 | 
			
		||||
	NONE: 1,
 | 
			
		||||
	WATERFALL: 2,
 | 
			
		||||
	CONSTELLATION: 3
 | 
			
		||||
};
 | 
			
		||||
var mathbox_mode = MATHBOX_MODES.UNINITIALIZED;
 | 
			
		||||
var mathbox;
 | 
			
		||||
var mathbox_element;
 | 
			
		||||
 | 
			
		||||
function mathbox_init()
 | 
			
		||||
{
 | 
			
		||||
	//mathbox_waterfall_history_length is defined in the config
 | 
			
		||||
	mathbox_data_max_depth = fft_fps * mathbox_waterfall_history_length; //how many lines can the buffer store
 | 
			
		||||
	mathbox_data_current_depth = 0; //how many lines are in the buffer currently
 | 
			
		||||
	mathbox_data_index = 0; //the index of the last empty line / the line to be overwritten
 | 
			
		||||
	mathbox_data = new Float32Array(fft_size * mathbox_data_max_depth);
 | 
			
		||||
	mathbox_data_global_index = 0;
 | 
			
		||||
	mathbox_correction_for_z = 0;
 | 
			
		||||
 | 
			
		||||
	mathbox = mathBox({
 | 
			
		||||
      plugins: ['core', 'controls', 'cursor', 'stats'],
 | 
			
		||||
      controls: { klass: THREE.OrbitControls },
 | 
			
		||||
    });
 | 
			
		||||
    three = mathbox.three;
 | 
			
		||||
    if(typeof three == "undefined") divlog("3D waterfall cannot be initialized because WebGL is not supported in your browser.", true);
 | 
			
		||||
 | 
			
		||||
    three.renderer.setClearColor(new THREE.Color(0x808080), 1.0);
 | 
			
		||||
	mathbox_container.appendChild((mathbox_element=three.renderer.domElement));
 | 
			
		||||
    view = mathbox
 | 
			
		||||
    .set({
 | 
			
		||||
      scale: 1080,
 | 
			
		||||
      focus: 3,
 | 
			
		||||
    })
 | 
			
		||||
    .camera({
 | 
			
		||||
      proxy: true,
 | 
			
		||||
      position: [-2, 1, 3],
 | 
			
		||||
    })
 | 
			
		||||
    .cartesian({
 | 
			
		||||
      range: [[-1, 1], [0, 1], [0, 1]],
 | 
			
		||||
      scale: [2, 2/3, 1],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    view.axis({
 | 
			
		||||
      axis: 1,
 | 
			
		||||
      width: 3,
 | 
			
		||||
	  color: "#fff",
 | 
			
		||||
  });
 | 
			
		||||
    view.axis({
 | 
			
		||||
      axis: 2,
 | 
			
		||||
      width: 3,
 | 
			
		||||
	  color: "#fff",
 | 
			
		||||
	  //offset: [0, 0, 0],
 | 
			
		||||
  });
 | 
			
		||||
    view.axis({
 | 
			
		||||
      axis: 3,
 | 
			
		||||
      width: 3,
 | 
			
		||||
	  color: "#fff",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    view.grid({
 | 
			
		||||
      width: 2,
 | 
			
		||||
      opacity: 0.5,
 | 
			
		||||
      axes: [1, 3],
 | 
			
		||||
      zOrder: 1,
 | 
			
		||||
	  color: "#fff",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    //var remap = function (v) { return Math.sqrt(.5 + .5 * v); };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	var remap = function(x,z,t)
 | 
			
		||||
	{
 | 
			
		||||
		var currentTimePos = mathbox_data_global_index/(fft_fps*1.0);
 | 
			
		||||
		var realZAdd = (-(t-currentTimePos)/mathbox_waterfall_history_length);
 | 
			
		||||
		var zAdd = realZAdd - mathbox_correction_for_z;
 | 
			
		||||
		if(zAdd<-0.2 || zAdd>0.2) { mathbox_correction_for_z = realZAdd; }
 | 
			
		||||
 | 
			
		||||
		var xIndex = Math.trunc(((x+1)/2.0)*fft_size); //x: frequency
 | 
			
		||||
		var zIndex = Math.trunc(z*(mathbox_data_max_depth-1)); //z: time
 | 
			
		||||
		var realZIndex = mathbox_get_data_line(zIndex);
 | 
			
		||||
		if(!mathbox_data_index_valid(zIndex)) return {y: undefined, dBValue: undefined, zAdd: 0 };
 | 
			
		||||
		//if(realZIndex>=(mathbox_data_max_depth-1)) console.log("realZIndexundef", realZIndex, zIndex);
 | 
			
		||||
		var index = Math.trunc(xIndex + realZIndex * fft_size);
 | 
			
		||||
		/*if(mathbox_data[index]==undefined) console.log("Undef", index, mathbox_data.length, zIndex,
 | 
			
		||||
				realZIndex, mathbox_data_max_depth,
 | 
			
		||||
				mathbox_data_current_depth, mathbox_data_index);*/
 | 
			
		||||
		var dBValue = mathbox_data[index];
 | 
			
		||||
		//y=1;
 | 
			
		||||
		if(dBValue>waterfall_max_level) y = 1;
 | 
			
		||||
		else if(dBValue<waterfall_min_level) y = 0;
 | 
			
		||||
		else y = (dBValue-waterfall_min_level)/(waterfall_max_level-waterfall_min_level);
 | 
			
		||||
		mathbox_dbg = { dbv: dBValue, indexval: index, mbd: mathbox_data.length, yval: y };
 | 
			
		||||
		if(!y) y=0;
 | 
			
		||||
		return {y: y, dBValue: dBValue, zAdd: zAdd};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    var points = view.area({
 | 
			
		||||
      expr: function (emit, x, z, i, j, t) {
 | 
			
		||||
		var y;
 | 
			
		||||
		remapResult=remap(x,z,t);
 | 
			
		||||
		if((y=remapResult.y)==undefined) return;
 | 
			
		||||
        emit(x, y, z+remapResult.zAdd);
 | 
			
		||||
      },
 | 
			
		||||
      width:  mathbox_waterfall_frequency_resolution,
 | 
			
		||||
      height: mathbox_data_max_depth - 1,
 | 
			
		||||
      channels: 3,
 | 
			
		||||
      axes: [1, 3],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    var colors = view.area({
 | 
			
		||||
      expr: function (emit, x, z, i, j, t) {
 | 
			
		||||
		var dBValue;
 | 
			
		||||
		if((dBValue=remap(x,z,t).dBValue)==undefined) return;
 | 
			
		||||
		var color=waterfall_mkcolor(dBValue, mathbox_waterfall_colors);
 | 
			
		||||
        var b = (color&0xff)/255.0;
 | 
			
		||||
        var g = ((color&0xff00)>>8)/255.0;
 | 
			
		||||
        var r = ((color&0xff0000)>>16)/255.0;
 | 
			
		||||
        emit(r, g, b, 1.0);
 | 
			
		||||
      },
 | 
			
		||||
      width:  mathbox_waterfall_frequency_resolution,
 | 
			
		||||
      height: mathbox_data_max_depth - 1,
 | 
			
		||||
      channels: 4,
 | 
			
		||||
      axes: [1, 3],
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    view.surface({
 | 
			
		||||
      shaded: true,
 | 
			
		||||
      points: '<<',
 | 
			
		||||
      colors: '<',
 | 
			
		||||
      color: 0xFFFFFF,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    view.surface({
 | 
			
		||||
      fill: false,
 | 
			
		||||
      lineX: false,
 | 
			
		||||
      lineY: false,
 | 
			
		||||
      points: '<<',
 | 
			
		||||
      colors: '<',
 | 
			
		||||
      color: 0xFFFFFF,
 | 
			
		||||
      width: 2,
 | 
			
		||||
      blending: 'add',
 | 
			
		||||
      opacity: .25,
 | 
			
		||||
      zBias: 5,
 | 
			
		||||
    });
 | 
			
		||||
	mathbox_mode = MATHBOX_MODES.NONE;
 | 
			
		||||
 | 
			
		||||
	//mathbox_element.style.width="100%";
 | 
			
		||||
	//mathbox_element.style.height="100%";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function mathbox_toggle()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	if(mathbox_mode == MATHBOX_MODES.UNINITIALIZED) mathbox_init();
 | 
			
		||||
	mathbox_mode = (mathbox_mode == MATHBOX_MODES.NONE) ? MATHBOX_MODES.WATERFALL : MATHBOX_MODES.NONE;
 | 
			
		||||
	mathbox_container.style.display = (mathbox_mode == MATHBOX_MODES.WATERFALL) ? "block" : "none";
 | 
			
		||||
	mathbox_clear_data();
 | 
			
		||||
	waterfall_clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function waterfall_clear()
 | 
			
		||||
{
 | 
			
		||||
	while(canvases.length) //delete all canvases
 | 
			
		||||
	{
 | 
			
		||||
		var x=canvases.shift();
 | 
			
		||||
		x.parentNode.removeChild(x);
 | 
			
		||||
		delete x;
 | 
			
		||||
	}
 | 
			
		||||
	add_canvas();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openwebrx_resize()
 | 
			
		||||
{
 | 
			
		||||
	resize_canvases();
 | 
			
		||||
@@ -1979,10 +2260,11 @@ function openwebrx_resize()
 | 
			
		||||
 | 
			
		||||
function openwebrx_init()
 | 
			
		||||
{
 | 
			
		||||
	if(ios) e("openwebrx-big-grey").style.display="table-cell";
 | 
			
		||||
	if(ios||is_chrome) e("openwebrx-big-grey").style.display="table-cell";
 | 
			
		||||
	(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px";
 | 
			
		||||
	init_rx_photo();
 | 
			
		||||
	open_websocket();
 | 
			
		||||
    secondary_demod_init();
 | 
			
		||||
	place_panels(first_show_panel);
 | 
			
		||||
	window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
 | 
			
		||||
	window.addEventListener("resize",openwebrx_resize);
 | 
			
		||||
@@ -2076,9 +2358,14 @@ function pop_bottommost_panel(from)
 | 
			
		||||
	return to_return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggle_panel(what)
 | 
			
		||||
function toggle_panel(what, on)
 | 
			
		||||
{
 | 
			
		||||
	var item=e(what);
 | 
			
		||||
    var item=e(what);
 | 
			
		||||
    if(typeof on !== "undefined") 
 | 
			
		||||
    {
 | 
			
		||||
        if(item.openwebrxHidden && !on) return;
 | 
			
		||||
        if(!item.openwebrxHidden && on) return;
 | 
			
		||||
    }
 | 
			
		||||
	if(item.openwebrxDisableClick) return;
 | 
			
		||||
	item.style.transitionDuration="599ms";
 | 
			
		||||
	item.style.transitionDelay="0ms";
 | 
			
		||||
@@ -2167,6 +2454,7 @@ function place_panels(function_apply)
 | 
			
		||||
		p.style.visibility="visible";
 | 
			
		||||
		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
 | 
			
		||||
		if(function_apply) function_apply(p);
 | 
			
		||||
        //console.log(p.id, y, p.openwebrxPanelTransparent);
 | 
			
		||||
	}
 | 
			
		||||
	y=hoffset;
 | 
			
		||||
	while(right_col.length>0)
 | 
			
		||||
@@ -2175,7 +2463,7 @@ function place_panels(function_apply)
 | 
			
		||||
		p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
 | 
			
		||||
		p.style.bottom=y.toString()+"px";
 | 
			
		||||
		p.style.visibility="visible";
 | 
			
		||||
		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
 | 
			
		||||
        y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
 | 
			
		||||
		if(function_apply) function_apply(p);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -2199,3 +2487,331 @@ function progressbar_set(obj,val,text,over)
 | 
			
		||||
	if(innerText==null) return;
 | 
			
		||||
	innerText.innerHTML=text;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function demodulator_buttons_update()
 | 
			
		||||
{
 | 
			
		||||
	$(".openwebrx-demodulator-button").removeClass("highlighted");
 | 
			
		||||
    if(secondary_demod) $("#openwebrx-button-dig").addClass("highlighted");
 | 
			
		||||
    else switch(demodulators[0].subtype)
 | 
			
		||||
	{
 | 
			
		||||
	case "nfm":
 | 
			
		||||
		$("#openwebrx-button-nfm").addClass("highlighted");
 | 
			
		||||
		break;
 | 
			
		||||
	case "am":
 | 
			
		||||
		$("#openwebrx-button-am").addClass("highlighted");
 | 
			
		||||
		break;
 | 
			
		||||
	case "lsb":
 | 
			
		||||
	case "usb":
 | 
			
		||||
	case "cw":
 | 
			
		||||
		if(demodulators[0].high_cut-demodulators[0].low_cut<300)
 | 
			
		||||
			$("#openwebrx-button-cw").addClass("highlighted");
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			if(demodulators[0].high_cut<0) 
 | 
			
		||||
				$("#openwebrx-button-lsb").addClass("highlighted");
 | 
			
		||||
			else if(demodulators[0].low_cut>0) 
 | 
			
		||||
				$("#openwebrx-button-usb").addClass("highlighted");
 | 
			
		||||
			else $("#openwebrx-button-lsb, #openwebrx-button-usb").addClass("highlighted");
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
function demodulator_analog_replace_last() { demodulator_analog_replace(last_analog_demodulator_subtype); }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  _____  _       _                     _           
 | 
			
		||||
 |  __ \(_)     (_)                   | |          
 | 
			
		||||
 | |  | |_  __ _ _ _ __ ___   ___   __| | ___  ___ 
 | 
			
		||||
 | |  | | |/ _` | | '_ ` _ \ / _ \ / _` |/ _ \/ __|
 | 
			
		||||
 | |__| | | (_| | | | | | | | (_) | (_| |  __/\__ \
 | 
			
		||||
 |_____/|_|\__, |_|_| |_| |_|\___/ \__,_|\___||___/
 | 
			
		||||
            __/ |                                  
 | 
			
		||||
           |___/                                   
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
secondary_demod = false;
 | 
			
		||||
secondary_demod_offset_freq = 0;
 | 
			
		||||
secondary_demod_waterfall_queue = [];
 | 
			
		||||
 | 
			
		||||
function demodulator_digital_replace_last() 
 | 
			
		||||
{ 
 | 
			
		||||
    demodulator_digital_replace(last_digital_demodulator_subtype); 
 | 
			
		||||
    secondary_demod_listbox_update();
 | 
			
		||||
}
 | 
			
		||||
function demodulator_digital_replace(subtype)
 | 
			
		||||
{
 | 
			
		||||
    switch(subtype) 
 | 
			
		||||
    {
 | 
			
		||||
    case "bpsk31":
 | 
			
		||||
    case "rtty":
 | 
			
		||||
        secondary_demod_start(subtype);
 | 
			
		||||
        demodulator_analog_replace('usb', true);
 | 
			
		||||
        demodulator_buttons_update();
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    toggle_panel("openwebrx-panel-digimodes", true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_create_canvas()
 | 
			
		||||
{
 | 
			
		||||
	var new_canvas = document.createElement("canvas");
 | 
			
		||||
	new_canvas.width=secondary_fft_size;
 | 
			
		||||
	new_canvas.height=$(secondary_demod_canvas_container).height();
 | 
			
		||||
	new_canvas.style.width=$(secondary_demod_canvas_container).width()+"px";
 | 
			
		||||
	new_canvas.style.height=$(secondary_demod_canvas_container).height()+"px";
 | 
			
		||||
    console.log(new_canvas.width, new_canvas.height, new_canvas.style.width, new_canvas.style.height);
 | 
			
		||||
	secondary_demod_current_canvas_actual_line=new_canvas.height-1;
 | 
			
		||||
	$(secondary_demod_canvas_container).children().last().before(new_canvas);
 | 
			
		||||
    return new_canvas;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_remove_canvases()
 | 
			
		||||
{
 | 
			
		||||
    $(secondary_demod_canvas_container).children("canvas").remove();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_init_canvases()
 | 
			
		||||
{
 | 
			
		||||
    secondary_demod_remove_canvases();
 | 
			
		||||
    secondary_demod_canvases=[];
 | 
			
		||||
    secondary_demod_canvases.push(secondary_demod_create_canvas());
 | 
			
		||||
    secondary_demod_canvases.push(secondary_demod_create_canvas());
 | 
			
		||||
    secondary_demod_canvases[0].openwebrx_top=-$(secondary_demod_canvas_container).height();
 | 
			
		||||
    secondary_demod_canvases[1].openwebrx_top=0;
 | 
			
		||||
    secondary_demod_canvases_update_top();
 | 
			
		||||
    secondary_demod_current_canvas_context = secondary_demod_canvases[0].getContext("2d");
 | 
			
		||||
    secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
 | 
			
		||||
    secondary_demod_current_canvas_index=0;
 | 
			
		||||
    secondary_demod_canvases_initialized=true;
 | 
			
		||||
    //secondary_demod_update_channel_freq_from_event();
 | 
			
		||||
    mkscale(); //so that the secondary waterfall zoom level will be initialized
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_canvases_update_top()
 | 
			
		||||
{
 | 
			
		||||
    for(var i=0;i<2;i++) secondary_demod_canvases[i].style.top=secondary_demod_canvases[i].openwebrx_top+"px";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_swap_canvases()
 | 
			
		||||
{
 | 
			
		||||
    console.log("swap");
 | 
			
		||||
    secondary_demod_canvases[0+!secondary_demod_current_canvas_index].openwebrx_top-=$(secondary_demod_canvas_container).height()*2;
 | 
			
		||||
    secondary_demod_current_canvas_index=0+!secondary_demod_current_canvas_index;
 | 
			
		||||
    secondary_demod_current_canvas_context = secondary_demod_canvases[secondary_demod_current_canvas_index].getContext("2d");
 | 
			
		||||
    secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_init()
 | 
			
		||||
{
 | 
			
		||||
    $("#openwebrx-panel-digimodes")[0].openwebrxHidden = true;
 | 
			
		||||
    secondary_demod_canvas_container = $("#openwebrx-digimode-canvas-container")[0];
 | 
			
		||||
    $(secondary_demod_canvas_container)
 | 
			
		||||
        .mousemove(secondary_demod_canvas_container_mousemove)
 | 
			
		||||
        .mouseup(secondary_demod_canvas_container_mouseup)
 | 
			
		||||
        .mousedown(secondary_demod_canvas_container_mousedown)
 | 
			
		||||
        .mouseenter(secondary_demod_canvas_container_mousein)
 | 
			
		||||
        .mouseleave(secondary_demod_canvas_container_mouseout);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_start(subtype) 
 | 
			
		||||
{ 
 | 
			
		||||
    secondary_demod_canvases_initialized = false;
 | 
			
		||||
    ws.send("SET secondary_mod="+subtype); 
 | 
			
		||||
    secondary_demod = subtype; 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_set()
 | 
			
		||||
{
 | 
			
		||||
    ws.send("SET secondary_offset_freq="+secondary_demod_offset_freq.toString());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_stop()
 | 
			
		||||
{
 | 
			
		||||
    ws.send("SET secondary_mod=off");
 | 
			
		||||
    secondary_demod = false; 
 | 
			
		||||
    secondary_demod_waterfall_queue = [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_waterfall_add_queue(x)
 | 
			
		||||
{
 | 
			
		||||
    secondary_demod_waterfall_queue.push(x);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_push_binary_data(x)
 | 
			
		||||
{
 | 
			
		||||
    secondary_demod_push_data(Array.from(x).map( y => (y)?"1":"0" ).join(""));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_push_data(x)
 | 
			
		||||
{
 | 
			
		||||
    x=Array.from(x).map((y)=>{
 | 
			
		||||
        var c=y.charCodeAt(0);
 | 
			
		||||
        if(y=="\r") return " ";
 | 
			
		||||
        if(y=="\n") return " ";
 | 
			
		||||
        //if(y=="\n") return "<br />";
 | 
			
		||||
        if(c<32||c>126) return "";
 | 
			
		||||
        if(y=="&") return "&";
 | 
			
		||||
        if(y=="<") return "<";
 | 
			
		||||
        if(y==">") return ">";
 | 
			
		||||
        if(y==" ") return " ";
 | 
			
		||||
        return y;
 | 
			
		||||
    }).join("");
 | 
			
		||||
    $("#openwebrx-cursor-blink").before("<span class=\"part\"><span class=\"subpart\">"+x+"</span></span>");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_data_clear()
 | 
			
		||||
{
 | 
			
		||||
    $("#openwebrx-cursor-blink").prevAll().remove();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_close_window()
 | 
			
		||||
{
 | 
			
		||||
    secondary_demod_stop();
 | 
			
		||||
    toggle_panel("openwebrx-panel-digimodes", false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
secondary_demod_fft_offset_db=30; //need to calculate that later
 | 
			
		||||
 | 
			
		||||
function secondary_demod_waterfall_add(data)
 | 
			
		||||
{
 | 
			
		||||
    if(!secondary_demod) return;
 | 
			
		||||
	var w=secondary_fft_size;
 | 
			
		||||
 | 
			
		||||
	//Add line to waterfall image
 | 
			
		||||
	var oneline_image = secondary_demod_current_canvas_context.createImageData(w,1);
 | 
			
		||||
	for(x=0;x<w;x++)
 | 
			
		||||
	{
 | 
			
		||||
		var color=waterfall_mkcolor(data[x]+secondary_demod_fft_offset_db);
 | 
			
		||||
		for(i=0;i<4;i++) oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Draw image
 | 
			
		||||
	secondary_demod_current_canvas_context.putImageData(oneline_image, 0, secondary_demod_current_canvas_actual_line--);
 | 
			
		||||
    secondary_demod_canvases.map((x)=>{x.openwebrx_top += 1;});
 | 
			
		||||
    secondary_demod_canvases_update_top();
 | 
			
		||||
	if(secondary_demod_current_canvas_actual_line<0) secondary_demod_swap_canvases();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var secondary_demod_canvases_initialized = false;
 | 
			
		||||
 | 
			
		||||
function secondary_demod_waterfall_dequeue()
 | 
			
		||||
{
 | 
			
		||||
    if(!secondary_demod || !secondary_demod_canvases_initialized) return;
 | 
			
		||||
	if(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
 | 
			
		||||
	if(secondary_demod_waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of fft overflow
 | 
			
		||||
	{
 | 
			
		||||
		console.log("secondary waterfall overflow, queue length:", secondary_demod_waterfall_queue.length);
 | 
			
		||||
		while(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
 | 
			
		||||
	}
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
secondary_demod_listbox_updating = false;
 | 
			
		||||
function secondary_demod_listbox_changed()
 | 
			
		||||
{
 | 
			
		||||
    if(secondary_demod_listbox_updating) return;
 | 
			
		||||
    switch ($("#openwebrx-secondary-demod-listbox")[0].value)
 | 
			
		||||
    {
 | 
			
		||||
        case "none":
 | 
			
		||||
            demodulator_analog_replace_last();
 | 
			
		||||
            break;
 | 
			
		||||
        case "bpsk31":
 | 
			
		||||
            demodulator_digital_replace('bpsk31');
 | 
			
		||||
            break;
 | 
			
		||||
        case "rtty":
 | 
			
		||||
            demodulator_digital_replace('rtty');
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_listbox_update()
 | 
			
		||||
{
 | 
			
		||||
    secondary_demod_listbox_updating = true;
 | 
			
		||||
    $("#openwebrx-secondary-demod-listbox").val((secondary_demod)?secondary_demod:"none");
 | 
			
		||||
    console.log("update");
 | 
			
		||||
    secondary_demod_listbox_updating = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
secondary_demod_channel_freq=1000;
 | 
			
		||||
function secondary_demod_update_marker()
 | 
			
		||||
{
 | 
			
		||||
    var width =  Math.max( (secondary_bw / (if_samp_rate/2)) * secondary_demod_canvas_width, 5);
 | 
			
		||||
    var center_at = (secondary_demod_channel_freq / (if_samp_rate/2)) * secondary_demod_canvas_width + secondary_demod_canvas_left;
 | 
			
		||||
    var left = center_at-width/2;
 | 
			
		||||
    //console.log("sdum", width, left);
 | 
			
		||||
    $("#openwebrx-digimode-select-channel").width(width).css("left",left+"px") 
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
secondary_demod_waiting_for_set = false;
 | 
			
		||||
function secondary_demod_update_channel_freq_from_event(evt)
 | 
			
		||||
{
 | 
			
		||||
    if(typeof evt !== "undefined")
 | 
			
		||||
    {
 | 
			
		||||
        var relativeX=(evt.offsetX)?evt.offsetX:evt.layerX;
 | 
			
		||||
        secondary_demod_channel_freq=secondary_demod_low_cut + 
 | 
			
		||||
            (relativeX/$(secondary_demod_canvas_container).width()) * (secondary_demod_high_cut-secondary_demod_low_cut);
 | 
			
		||||
    }
 | 
			
		||||
    //console.log("toset:", secondary_demod_channel_freq);
 | 
			
		||||
    if(!secondary_demod_waiting_for_set)
 | 
			
		||||
    {
 | 
			
		||||
        secondary_demod_waiting_for_set = true;
 | 
			
		||||
        window.setTimeout(()=>{
 | 
			
		||||
            ws.send("SET secondary_offset_freq="+Math.floor(secondary_demod_channel_freq));
 | 
			
		||||
            //console.log("doneset:", secondary_demod_channel_freq);
 | 
			
		||||
            secondary_demod_waiting_for_set = false;
 | 
			
		||||
        }, 50);
 | 
			
		||||
    }
 | 
			
		||||
    secondary_demod_update_marker();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
secondary_demod_mousedown=false;
 | 
			
		||||
function secondary_demod_canvas_container_mousein()
 | 
			
		||||
{
 | 
			
		||||
    $("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_canvas_container_mouseout()
 | 
			
		||||
{
 | 
			
		||||
    $("#openwebrx-digimode-select-channel").css("opacity","0");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_canvas_container_mousemove(evt)
 | 
			
		||||
{
 | 
			
		||||
    if(secondary_demod_mousedown) secondary_demod_update_channel_freq_from_event(evt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_canvas_container_mousedown(evt)
 | 
			
		||||
{
 | 
			
		||||
    if(evt.which==1) secondary_demod_mousedown=true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function secondary_demod_canvas_container_mouseup(evt)
 | 
			
		||||
{
 | 
			
		||||
    if(evt.which==1) secondary_demod_mousedown=false;
 | 
			
		||||
    secondary_demod_update_channel_freq_from_event(evt);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function secondary_demod_waterfall_set_zoom(low_cut, high_cut)
 | 
			
		||||
{
 | 
			
		||||
    if(!secondary_demod || !secondary_demod_canvases_initialized) return;
 | 
			
		||||
    if(low_cut<0 && high_cut<0)
 | 
			
		||||
    {
 | 
			
		||||
        var hctmp = high_cut;
 | 
			
		||||
        var lctmp = low_cut;
 | 
			
		||||
        low_cut = -hctmp;
 | 
			
		||||
        low_cut = -lctmp;
 | 
			
		||||
    }
 | 
			
		||||
    else if(low_cut<0 && high_cut>0)
 | 
			
		||||
    {
 | 
			
		||||
        high_cut=Math.max(Math.abs(high_cut), Math.abs(low_cut));
 | 
			
		||||
        low_cut=0;
 | 
			
		||||
    }
 | 
			
		||||
    secondary_demod_low_cut = low_cut;
 | 
			
		||||
    secondary_demod_high_cut = high_cut;
 | 
			
		||||
    var shown_bw = high_cut-low_cut;
 | 
			
		||||
    secondary_demod_canvas_width = $(secondary_demod_canvas_container).width()  * (if_samp_rate/2)/shown_bw;
 | 
			
		||||
    secondary_demod_canvas_left = -secondary_demod_canvas_width*(low_cut/(if_samp_rate/2));
 | 
			
		||||
    //console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
 | 
			
		||||
    secondary_demod_canvases.map((x)=>{$(x).css("left",secondary_demod_canvas_left+"px").css("width",secondary_demod_canvas_width+"px");});
 | 
			
		||||
    secondary_demod_update_channel_freq_from_event();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1089
									
								
								openwebrx.py
									
									
									
									
									
								
							
							
						
						
									
										1089
									
								
								openwebrx.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,294 +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>
 | 
			
		||||
 | 
			
		||||
    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 time
 | 
			
		||||
import os
 | 
			
		||||
import code
 | 
			
		||||
import signal
 | 
			
		||||
import fcntl
 | 
			
		||||
import select
 | 
			
		||||
 | 
			
		||||
class dsp_plugin:
 | 
			
		||||
 | 
			
		||||
	def __init__(self):
 | 
			
		||||
		self.samp_rate = 250000
 | 
			
		||||
		self.output_rate = 11025 #this is default, and cannot be set at the moment
 | 
			
		||||
		self.fft_size = 1024
 | 
			
		||||
		self.fft_fps = 5
 | 
			
		||||
		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.audio_compression = "none"
 | 
			
		||||
		self.fft_compression = "none"
 | 
			
		||||
		self.demodulator = "nfm"
 | 
			
		||||
		self.name = "csdr"
 | 
			
		||||
		self.format_conversion = "csdr convert_u8_f"
 | 
			
		||||
		self.base_bufsize = 512
 | 
			
		||||
		self.nc_port = 4951
 | 
			
		||||
		self.csdr_dynamic_bufsize = False
 | 
			
		||||
		self.csdr_print_bufsizes = False
 | 
			
		||||
		self.csdr_through = False
 | 
			
		||||
		self.squelch_level = 0
 | 
			
		||||
 | 
			
		||||
	def chain(self,which):
 | 
			
		||||
		if which in [ "dmr", "dstar", "nxdn", "ysf" ]:
 | 
			
		||||
			self.set_output_rate(48000)
 | 
			
		||||
		else:
 | 
			
		||||
			self.set_output_rate(11025)
 | 
			
		||||
		any_chain_base="ncat -v 127.0.0.1 {nc_port} | "
 | 
			
		||||
		if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
 | 
			
		||||
		if self.csdr_through: any_chain_base+="csdr through | "
 | 
			
		||||
		any_chain_base+=self.format_conversion+(" | " if  self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
 | 
			
		||||
		if which == "fft":
 | 
			
		||||
			fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70 | csdr fft_exchange_sides_ff {fft_size}"
 | 
			
		||||
			if self.fft_compression=="adpcm":
 | 
			
		||||
				return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
 | 
			
		||||
			else:
 | 
			
		||||
				return fft_chain_base
 | 
			
		||||
		chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
 | 
			
		||||
		chain_end = ""
 | 
			
		||||
		if self.audio_compression=="adpcm":
 | 
			
		||||
			chain_end = " | csdr encode_ima_adpcm_i16_u8"
 | 
			
		||||
		if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr convert_f_s16"+chain_end
 | 
			
		||||
		if which in [ "dstar", "nxdn" ]:
 | 
			
		||||
			c = chain_begin
 | 
			
		||||
			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
			if which == "dstar":
 | 
			
		||||
				c += " | dsd -fd"
 | 
			
		||||
			elif which == "nxdn":
 | 
			
		||||
				c += " | dsd -fi"
 | 
			
		||||
			c += " -i - -o - -u 2 -g 10"
 | 
			
		||||
			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --input-buffer 160 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 220"
 | 
			
		||||
			c += chain_end
 | 
			
		||||
			return c
 | 
			
		||||
		elif which == "dmr":
 | 
			
		||||
			c = chain_begin
 | 
			
		||||
			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
			c += " | rrc_filter | gfsk_demodulator | dmr_decoder --fifo {meta_pipe} | mbe_synthesizer"
 | 
			
		||||
			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
 | 
			
		||||
			c += chain_end
 | 
			
		||||
			return c
 | 
			
		||||
		elif which == "ysf":
 | 
			
		||||
			c = chain_begin
 | 
			
		||||
			c += "csdr fmdemod_quadri_cf | csdr fastdcblock_ff | csdr convert_f_s16"
 | 
			
		||||
			c += " | rrc_filter | gfsk_demodulator | ysf_decoder --fifo {meta_pipe} | mbe_synthesizer -y"
 | 
			
		||||
			c += " | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 --buffer 32 - -t raw -r 11025 -e signed-integer -b 16 -c 1 - | csdr setbuf 256"
 | 
			
		||||
			c += chain_end
 | 
			
		||||
			return c
 | 
			
		||||
		elif which == "am":	return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
 | 
			
		||||
		elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
 | 
			
		||||
 | 
			
		||||
	def set_audio_compression(self,what):
 | 
			
		||||
		self.audio_compression = what
 | 
			
		||||
 | 
			
		||||
	def set_fft_compression(self,what):
 | 
			
		||||
		self.fft_compression = what
 | 
			
		||||
 | 
			
		||||
	def get_fft_bytes_to_read(self):
 | 
			
		||||
		if self.fft_compression=="none": return self.fft_size*4
 | 
			
		||||
		if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
 | 
			
		||||
 | 
			
		||||
	def set_samp_rate(self,samp_rate):
 | 
			
		||||
		#to change this, restart is required
 | 
			
		||||
		self.samp_rate=samp_rate
 | 
			
		||||
		self.decimation=1
 | 
			
		||||
		while self.samp_rate/(self.decimation+1)>=self.output_rate:
 | 
			
		||||
			self.decimation+=1
 | 
			
		||||
		self.last_decimation=float(self.if_samp_rate())/self.output_rate
 | 
			
		||||
 | 
			
		||||
	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 set_output_rate(self,output_rate):
 | 
			
		||||
		self.output_rate=output_rate
 | 
			
		||||
		self.set_samp_rate(self.samp_rate) #as it depends on output_rate
 | 
			
		||||
 | 
			
		||||
	def set_demodulator(self,demodulator):
 | 
			
		||||
		#to change this, restart is required
 | 
			
		||||
		self.demodulator=demodulator
 | 
			
		||||
 | 
			
		||||
	def get_demodulator(self):
 | 
			
		||||
		return self.demodulator
 | 
			
		||||
 | 
			
		||||
	def set_fft_size(self,fft_size):
 | 
			
		||||
		#to change this, restart is required
 | 
			
		||||
		self.fft_size=fft_size
 | 
			
		||||
 | 
			
		||||
	def set_fft_fps(self,fft_fps):
 | 
			
		||||
		#to change this, restart is required
 | 
			
		||||
		self.fft_fps=fft_fps
 | 
			
		||||
 | 
			
		||||
	def fft_block_size(self):
 | 
			
		||||
		return self.samp_rate/self.fft_fps
 | 
			
		||||
 | 
			
		||||
	def set_format_conversion(self,format_conversion):
 | 
			
		||||
		self.format_conversion=format_conversion
 | 
			
		||||
 | 
			
		||||
	def set_offset_freq(self,offset_freq):
 | 
			
		||||
		self.offset_freq=offset_freq
 | 
			
		||||
		if self.running:
 | 
			
		||||
			self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
 | 
			
		||||
			self.shift_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
	def set_bpf(self,low_cut,high_cut):
 | 
			
		||||
		self.low_cut=low_cut
 | 
			
		||||
		self.high_cut=high_cut
 | 
			
		||||
		if self.running:
 | 
			
		||||
			self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
 | 
			
		||||
			self.bpf_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
	def get_bpf(self):
 | 
			
		||||
		return [self.low_cut, self.high_cut]
 | 
			
		||||
 | 
			
		||||
	def set_squelch_level(self, squelch_level):
 | 
			
		||||
		self.squelch_level=squelch_level
 | 
			
		||||
		if self.running:
 | 
			
		||||
			self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
 | 
			
		||||
			self.squelch_pipe_file.flush()
 | 
			
		||||
 | 
			
		||||
	def get_smeter_level(self):
 | 
			
		||||
		if self.running:
 | 
			
		||||
			line=self.smeter_pipe_file.readline()
 | 
			
		||||
			return float(line[:-1])
 | 
			
		||||
 | 
			
		||||
	def get_metadata(self):
 | 
			
		||||
		if self.running and self.meta_pipe:
 | 
			
		||||
			return self.meta_pipe_file.readline()
 | 
			
		||||
 | 
			
		||||
	def mkfifo(self,path):
 | 
			
		||||
		try:
 | 
			
		||||
			os.unlink(path)
 | 
			
		||||
		except:
 | 
			
		||||
			pass
 | 
			
		||||
		os.mkfifo(path)
 | 
			
		||||
 | 
			
		||||
	def ddc_transition_bw(self):
 | 
			
		||||
		return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
 | 
			
		||||
 | 
			
		||||
	def start(self):
 | 
			
		||||
		command_base=self.chain(self.demodulator)
 | 
			
		||||
 | 
			
		||||
		#create control pipes for csdr
 | 
			
		||||
		pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
 | 
			
		||||
		self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = self.meta_pipe = None
 | 
			
		||||
		if "{bpf_pipe}" in command_base:
 | 
			
		||||
			self.bpf_pipe=pipe_base_path+"bpf"
 | 
			
		||||
			self.mkfifo(self.bpf_pipe)
 | 
			
		||||
		if "{shift_pipe}" in command_base:
 | 
			
		||||
			self.shift_pipe=pipe_base_path+"shift"
 | 
			
		||||
			self.mkfifo(self.shift_pipe)
 | 
			
		||||
		if "{squelch_pipe}" in command_base:
 | 
			
		||||
			self.squelch_pipe=pipe_base_path+"squelch"
 | 
			
		||||
			self.mkfifo(self.squelch_pipe)
 | 
			
		||||
		if "{smeter_pipe}" in command_base:
 | 
			
		||||
			self.smeter_pipe=pipe_base_path+"smeter"
 | 
			
		||||
			self.mkfifo(self.smeter_pipe)
 | 
			
		||||
		if "{meta_pipe}" in command_base:
 | 
			
		||||
			self.meta_pipe=pipe_base_path+"meta"
 | 
			
		||||
			self.mkfifo(self.meta_pipe)
 | 
			
		||||
		else:
 | 
			
		||||
			self.meta_pipe=None
 | 
			
		||||
 | 
			
		||||
		#run the command
 | 
			
		||||
		command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
 | 
			
		||||
			last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), \
 | 
			
		||||
			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, \
 | 
			
		||||
			squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, meta_pipe=self.meta_pipe )
 | 
			
		||||
 | 
			
		||||
		print "[openwebrx-dsp-plugin:csdr] Command =",command
 | 
			
		||||
		#code.interact(local=locals())
 | 
			
		||||
		my_env=os.environ.copy()
 | 
			
		||||
		if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
 | 
			
		||||
		if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
 | 
			
		||||
		self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
 | 
			
		||||
		self.running = True
 | 
			
		||||
 | 
			
		||||
		#open control pipes for csdr and send initialization data
 | 
			
		||||
		if self.bpf_pipe != None:
 | 
			
		||||
			self.bpf_pipe_file=open(self.bpf_pipe,"w")
 | 
			
		||||
			self.set_bpf(self.low_cut,self.high_cut)
 | 
			
		||||
		if self.shift_pipe != None:
 | 
			
		||||
			self.shift_pipe_file=open(self.shift_pipe,"w")
 | 
			
		||||
			self.set_offset_freq(self.offset_freq)
 | 
			
		||||
		if self.squelch_pipe != None:
 | 
			
		||||
			self.squelch_pipe_file=open(self.squelch_pipe,"w")
 | 
			
		||||
			self.set_squelch_level(self.squelch_level)
 | 
			
		||||
		if self.smeter_pipe != None:
 | 
			
		||||
			self.smeter_pipe_file=open(self.smeter_pipe,"r")
 | 
			
		||||
			fcntl.fcntl(self.smeter_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
 | 
			
		||||
		if self.meta_pipe != None:
 | 
			
		||||
			self.meta_pipe_file=open(self.meta_pipe,"r")
 | 
			
		||||
			fcntl.fcntl(self.meta_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
 | 
			
		||||
 | 
			
		||||
	def read(self,size):
 | 
			
		||||
		return self.process.stdout.read(size)
 | 
			
		||||
 | 
			
		||||
	def read_async(self, size):
 | 
			
		||||
		if (select.select([self.process.stdout], [], [], 0)[0] != []):
 | 
			
		||||
			return self.process.stdout.read(size)
 | 
			
		||||
		else:
 | 
			
		||||
			return None
 | 
			
		||||
 | 
			
		||||
	def stop(self):
 | 
			
		||||
		os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
 | 
			
		||||
		#if(self.process.poll()!=None):return # returns None while subprocess is running
 | 
			
		||||
		#while(self.process.poll()==None):
 | 
			
		||||
		#	#self.process.kill()
 | 
			
		||||
		#	print "killproc",os.getpgid(self.process.pid),self.process.pid
 | 
			
		||||
		#	os.killpg(self.process.pid, signal.SIGTERM)
 | 
			
		||||
		#
 | 
			
		||||
		#	time.sleep(0.1)
 | 
			
		||||
		if self.bpf_pipe:
 | 
			
		||||
			try: os.unlink(self.bpf_pipe)
 | 
			
		||||
			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
 | 
			
		||||
		if self.shift_pipe:
 | 
			
		||||
			try: os.unlink(self.shift_pipe)
 | 
			
		||||
			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
 | 
			
		||||
		if self.squelch_pipe:
 | 
			
		||||
			try: os.unlink(self.squelch_pipe)
 | 
			
		||||
			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
 | 
			
		||||
		if self.smeter_pipe:
 | 
			
		||||
			try: os.unlink(self.smeter_pipe)
 | 
			
		||||
			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
 | 
			
		||||
		if self.meta_pipe:
 | 
			
		||||
			try: os.unlink(self.meta_pipe)
 | 
			
		||||
			except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.meta_pipe
 | 
			
		||||
		self.running = False
 | 
			
		||||
 | 
			
		||||
	def restart(self):
 | 
			
		||||
		self.stop()
 | 
			
		||||
		self.start()
 | 
			
		||||
 | 
			
		||||
	def __del__(self):
 | 
			
		||||
		self.stop()
 | 
			
		||||
		del(self.process)
 | 
			
		||||
							
								
								
									
										236
									
								
								rxws.py
									
									
									
									
									
								
							
							
						
						
									
										236
									
								
								rxws.py
									
									
									
									
									
								
							@@ -1,9 +1,9 @@
 | 
			
		||||
"""
 | 
			
		||||
rxws: WebSocket methods implemented for OpenWebRX
 | 
			
		||||
 | 
			
		||||
	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>
 | 
			
		||||
    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>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as
 | 
			
		||||
@@ -26,146 +26,146 @@ import select
 | 
			
		||||
import code
 | 
			
		||||
 | 
			
		||||
class WebSocketException(Exception):
 | 
			
		||||
	pass
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
def handshake(myself):
 | 
			
		||||
	my_client_id=myself.path[4:]
 | 
			
		||||
	my_headers=myself.headers.items()
 | 
			
		||||
	my_header_keys=map(lambda x:x[0],my_headers)
 | 
			
		||||
	h_key_exists=lambda x:my_header_keys.count(x)
 | 
			
		||||
	h_value=lambda x:my_headers[my_header_keys.index(x)][1]
 | 
			
		||||
	#print "The Lambdas(tm)"
 | 
			
		||||
	#print h_key_exists("upgrade")
 | 
			
		||||
	#print h_value("upgrade")
 | 
			
		||||
	#print h_key_exists("sec-websocket-key")
 | 
			
		||||
	if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
 | 
			
		||||
		raise WebSocketException
 | 
			
		||||
	ws_key=h_value("sec-websocket-key")
 | 
			
		||||
	ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
 | 
			
		||||
	#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
 | 
			
		||||
	myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
 | 
			
		||||
    my_client_id=myself.path[4:]
 | 
			
		||||
    my_headers=myself.headers.items()
 | 
			
		||||
    my_header_keys=map(lambda x:x[0],my_headers)
 | 
			
		||||
    h_key_exists=lambda x:my_header_keys.count(x)
 | 
			
		||||
    h_value=lambda x:my_headers[my_header_keys.index(x)][1]
 | 
			
		||||
    #print "The Lambdas(tm)"
 | 
			
		||||
    #print h_key_exists("upgrade")
 | 
			
		||||
    #print h_value("upgrade")
 | 
			
		||||
    #print h_key_exists("sec-websocket-key")
 | 
			
		||||
    if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
 | 
			
		||||
        raise WebSocketException
 | 
			
		||||
    ws_key=h_value("sec-websocket-key")
 | 
			
		||||
    ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
 | 
			
		||||
    #A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
 | 
			
		||||
    myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
 | 
			
		||||
 | 
			
		||||
def get_header(size):
 | 
			
		||||
	#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
 | 
			
		||||
	ws_first_byte=0b10000010 # FIN=1, OP=2
 | 
			
		||||
	if(size>125):
 | 
			
		||||
		ws_second_byte=126 # The following two bytes will indicate frame size
 | 
			
		||||
		extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
 | 
			
		||||
	else:
 | 
			
		||||
		ws_second_byte=size
 | 
			
		||||
		#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
 | 
			
		||||
		extended_size=""
 | 
			
		||||
	return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
 | 
			
		||||
    #this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
 | 
			
		||||
    ws_first_byte=0b10000010 # FIN=1, OP=2
 | 
			
		||||
    if(size>125):
 | 
			
		||||
        ws_second_byte=126 # The following two bytes will indicate frame size
 | 
			
		||||
        extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
 | 
			
		||||
    else:
 | 
			
		||||
        ws_second_byte=size
 | 
			
		||||
        #256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
 | 
			
		||||
        extended_size=""
 | 
			
		||||
    return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
 | 
			
		||||
 | 
			
		||||
def code_payload(data, masking_key=""):
 | 
			
		||||
	# both encode or decode
 | 
			
		||||
	if masking_key=="":
 | 
			
		||||
		key = (61, 84, 35, 6)
 | 
			
		||||
	else:
 | 
			
		||||
		key = [ord(i) for i in masking_key]
 | 
			
		||||
	encoded=""
 | 
			
		||||
	for i in range(0,len(data)):
 | 
			
		||||
		encoded+=chr(ord(data[i])^key[i%4])
 | 
			
		||||
	return encoded
 | 
			
		||||
    # both encode or decode
 | 
			
		||||
    if masking_key=="":
 | 
			
		||||
        key = (61, 84, 35, 6)
 | 
			
		||||
    else:
 | 
			
		||||
        key = [ord(i) for i in masking_key]
 | 
			
		||||
    encoded=""
 | 
			
		||||
    for i in range(0,len(data)):
 | 
			
		||||
        encoded+=chr(ord(data[i])^key[i%4])
 | 
			
		||||
    return encoded
 | 
			
		||||
 | 
			
		||||
def xxdg(data):
 | 
			
		||||
	output=""
 | 
			
		||||
	for i in range(0,len(data)/8):
 | 
			
		||||
		output+=xxd(data[i:i+8])
 | 
			
		||||
		if i%2: output+="\n"
 | 
			
		||||
		else: output+="  "
 | 
			
		||||
	return output
 | 
			
		||||
		
 | 
			
		||||
    output=""
 | 
			
		||||
    for i in range(0,len(data)/8):
 | 
			
		||||
        output+=xxd(data[i:i+8])
 | 
			
		||||
        if i%2: output+="\n"
 | 
			
		||||
        else: output+="  "
 | 
			
		||||
    return output
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
def xxd(data):
 | 
			
		||||
	#diagnostic purposes only
 | 
			
		||||
	output=""
 | 
			
		||||
	for d in data:
 | 
			
		||||
		output+=hex(ord(d))[2:].zfill(2)+" " 
 | 
			
		||||
	return output
 | 
			
		||||
    #diagnostic purposes only
 | 
			
		||||
    output=""
 | 
			
		||||
    for d in data:
 | 
			
		||||
        output+=hex(ord(d))[2:].zfill(2)+" " 
 | 
			
		||||
    return output
 | 
			
		||||
 | 
			
		||||
#for R/W the WebSocket, use recv/send
 | 
			
		||||
#for reading the TCP socket, use readsock 
 | 
			
		||||
#for writing the TCP socket, use myself.wfile.write and flush
 | 
			
		||||
 | 
			
		||||
def readsock(myself,size,blocking):
 | 
			
		||||
	#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
 | 
			
		||||
	if blocking:
 | 
			
		||||
		return myself.rfile.read(size)
 | 
			
		||||
	else:
 | 
			
		||||
		poll = select.poll()
 | 
			
		||||
		poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
 | 
			
		||||
		fd = poll.poll(0) #timeout is 0
 | 
			
		||||
		if len(fd):
 | 
			
		||||
			f = fd[0]
 | 
			
		||||
			if f[1] > 0:
 | 
			
		||||
				return myself.rfile.read(size)
 | 
			
		||||
	return ""
 | 
			
		||||
    #http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
 | 
			
		||||
    if blocking:
 | 
			
		||||
        return myself.rfile.read(size)
 | 
			
		||||
    else:
 | 
			
		||||
        poll = select.poll()
 | 
			
		||||
        poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
 | 
			
		||||
        fd = poll.poll(0) #timeout is 0
 | 
			
		||||
        if len(fd):
 | 
			
		||||
            f = fd[0]
 | 
			
		||||
            if f[1] > 0:
 | 
			
		||||
                return myself.rfile.read(size)
 | 
			
		||||
    return ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def recv(myself, blocking=False, debug=False):
 | 
			
		||||
	bufsize=70000
 | 
			
		||||
	#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
 | 
			
		||||
	if debug: print "ws_recv begin"
 | 
			
		||||
	try:
 | 
			
		||||
		data=readsock(myself,6,blocking)
 | 
			
		||||
		#print "rxws.recv bytes:",xxd(data)	
 | 
			
		||||
	except:
 | 
			
		||||
		if debug: print "ws_recv error"	
 | 
			
		||||
		return ""
 | 
			
		||||
	if debug: print "ws_recv recved"
 | 
			
		||||
	if(len(data)==0): return ""
 | 
			
		||||
	fin=ord(data[0])&128!=0
 | 
			
		||||
	is_text_frame=ord(data[0])&15==1
 | 
			
		||||
	length=ord(data[1])&0x7f
 | 
			
		||||
	data+=readsock(myself,length,blocking)
 | 
			
		||||
	#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
 | 
			
		||||
	has_one_byte_length=length<125
 | 
			
		||||
	masked=ord(data[1])&0x80!=0
 | 
			
		||||
	#print "len=", length, len(data)-2
 | 
			
		||||
	#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
 | 
			
		||||
	#print xxd(data)
 | 
			
		||||
	if fin and is_text_frame and has_one_byte_length:
 | 
			
		||||
		if masked:
 | 
			
		||||
			return code_payload(data[6:], data[2:6])
 | 
			
		||||
		else:
 | 
			
		||||
			return data[2:]
 | 
			
		||||
    bufsize=70000
 | 
			
		||||
    #myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
 | 
			
		||||
    if debug: print "ws_recv begin"
 | 
			
		||||
    try:
 | 
			
		||||
        data=readsock(myself,6,blocking)
 | 
			
		||||
        #print "rxws.recv bytes:",xxd(data) 
 | 
			
		||||
    except:
 | 
			
		||||
        if debug: print "ws_recv error" 
 | 
			
		||||
        return ""
 | 
			
		||||
    if debug: print "ws_recv recved"
 | 
			
		||||
    if(len(data)==0): return ""
 | 
			
		||||
    fin=ord(data[0])&128!=0
 | 
			
		||||
    is_text_frame=ord(data[0])&15==1
 | 
			
		||||
    length=ord(data[1])&0x7f
 | 
			
		||||
    data+=readsock(myself,length,blocking)
 | 
			
		||||
    #print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
 | 
			
		||||
    has_one_byte_length=length<125
 | 
			
		||||
    masked=ord(data[1])&0x80!=0
 | 
			
		||||
    #print "len=", length, len(data)-2
 | 
			
		||||
    #print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
 | 
			
		||||
    #print xxd(data)
 | 
			
		||||
    if fin and is_text_frame and has_one_byte_length:
 | 
			
		||||
        if masked:
 | 
			
		||||
            return code_payload(data[6:], data[2:6])
 | 
			
		||||
        else:
 | 
			
		||||
            return data[2:]
 | 
			
		||||
 | 
			
		||||
#Useful links for ideas on WebSockets:
 | 
			
		||||
#  http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
 | 
			
		||||
#  https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server
 | 
			
		||||
#  http://tools.ietf.org/html/rfc6455#section-5.2	
 | 
			
		||||
#  http://tools.ietf.org/html/rfc6455#section-5.2   
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def flush(myself): 
 | 
			
		||||
	myself.wfile.flush()
 | 
			
		||||
	#or the socket, not the rfile:
 | 
			
		||||
	#lR,lW,lX = select.select([],[myself.connection,],[],60)
 | 
			
		||||
	
 | 
			
		||||
    myself.wfile.flush()
 | 
			
		||||
    #or the socket, not the rfile:
 | 
			
		||||
    #lR,lW,lX = select.select([],[myself.connection,],[],60)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
def send(myself, data, begin_id="", debug=0):
 | 
			
		||||
	base_frame_size=35000 #could guess by MTU?
 | 
			
		||||
	debug=0
 | 
			
		||||
	#try:
 | 
			
		||||
	while True:
 | 
			
		||||
		counter=0
 | 
			
		||||
		from_end=len(data)-counter
 | 
			
		||||
		if from_end+len(begin_id)>base_frame_size:
 | 
			
		||||
			data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
 | 
			
		||||
			header=get_header(len(data_to_send))
 | 
			
		||||
			flush(myself)
 | 
			
		||||
			myself.wfile.write(header+data_to_send)
 | 
			
		||||
			flush(myself)
 | 
			
		||||
			if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
 | 
			
		||||
		else:
 | 
			
		||||
			data_to_send=begin_id+data[counter:]
 | 
			
		||||
			header=get_header(len(data_to_send))
 | 
			
		||||
			flush(myself)
 | 
			
		||||
			myself.wfile.write(header+data_to_send)
 | 
			
		||||
			flush(myself)
 | 
			
		||||
			if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
 | 
			
		||||
			#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
 | 
			
		||||
			break
 | 
			
		||||
		counter+=base_frame_size-len(begin_id)
 | 
			
		||||
	#except:
 | 
			
		||||
	#	pass
 | 
			
		||||
    base_frame_size=35000 #could guess by MTU?
 | 
			
		||||
    debug=0
 | 
			
		||||
    #try:
 | 
			
		||||
    while True:
 | 
			
		||||
        counter=0
 | 
			
		||||
        from_end=len(data)-counter
 | 
			
		||||
        if from_end+len(begin_id)>base_frame_size:
 | 
			
		||||
            data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
 | 
			
		||||
            header=get_header(len(data_to_send))
 | 
			
		||||
            flush(myself)
 | 
			
		||||
            myself.wfile.write(header+data_to_send)
 | 
			
		||||
            flush(myself)
 | 
			
		||||
            if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
 | 
			
		||||
        else:
 | 
			
		||||
            data_to_send=begin_id+data[counter:]
 | 
			
		||||
            header=get_header(len(data_to_send))
 | 
			
		||||
            flush(myself)
 | 
			
		||||
            myself.wfile.write(header+data_to_send)
 | 
			
		||||
            flush(myself)
 | 
			
		||||
            if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
 | 
			
		||||
            #if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
 | 
			
		||||
            break
 | 
			
		||||
        counter+=base_frame_size-len(begin_id)
 | 
			
		||||
    #except:
 | 
			
		||||
    #   pass
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 511 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								screenshot.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								screenshot.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 1.4 MiB  | 
							
								
								
									
										51
									
								
								sdrhu.py
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								sdrhu.py
									
									
									
									
									
								
							@@ -1,9 +1,9 @@
 | 
			
		||||
#!/usr/bin/python2
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
	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>
 | 
			
		||||
    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>
 | 
			
		||||
 | 
			
		||||
    This program is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Affero General Public License as
 | 
			
		||||
@@ -23,29 +23,28 @@
 | 
			
		||||
import config_webrx as cfg, time, subprocess
 | 
			
		||||
 | 
			
		||||
def run(continuously=True):
 | 
			
		||||
	if not cfg.sdrhu_key: return 
 | 
			
		||||
	firsttime="(Your receiver is soon getting listed on sdr.hu!)"
 | 
			
		||||
	while True:
 | 
			
		||||
		cmd = "wget --timeout=15 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
 | 
			
		||||
		#print "[openwebrx-sdrhu]", cmd
 | 
			
		||||
		returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
 | 
			
		||||
		returned=returned[0]
 | 
			
		||||
		#print returned
 | 
			
		||||
		if "UPDATE:" in returned:
 | 
			
		||||
			retrytime_mins = 20
 | 
			
		||||
			value=returned.split("UPDATE:")[1].split("\n",1)[0]
 | 
			
		||||
			if value.startswith("SUCCESS"):
 | 
			
		||||
				print "[openwebrx-sdrhu] Update succeeded! "+firsttime
 | 
			
		||||
				firsttime=""
 | 
			
		||||
			else:
 | 
			
		||||
				print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
 | 
			
		||||
		else:
 | 
			
		||||
			retrytime_mins = 2
 | 
			
		||||
			print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
 | 
			
		||||
		if not continuously: break
 | 
			
		||||
		time.sleep(60*retrytime_mins)
 | 
			
		||||
    if not cfg.sdrhu_key: return 
 | 
			
		||||
    firsttime="(Your receiver is soon getting listed on sdr.hu!)"
 | 
			
		||||
    while True:
 | 
			
		||||
        cmd = "wget --timeout=15 -4qO- https://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
 | 
			
		||||
        print "[openwebrx-sdrhu]", cmd
 | 
			
		||||
        returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
 | 
			
		||||
        returned=returned[0]
 | 
			
		||||
        #print returned
 | 
			
		||||
        if "UPDATE:" in returned:
 | 
			
		||||
            retrytime_mins = 20
 | 
			
		||||
            value=returned.split("UPDATE:")[1].split("\n",1)[0]
 | 
			
		||||
            if value.startswith("SUCCESS"):
 | 
			
		||||
                print "[openwebrx-sdrhu] Update succeeded! "+firsttime
 | 
			
		||||
                firsttime=""
 | 
			
		||||
            else:
 | 
			
		||||
                print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
 | 
			
		||||
        else:
 | 
			
		||||
            retrytime_mins = 2
 | 
			
		||||
            print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
 | 
			
		||||
        if not continuously: break
 | 
			
		||||
        time.sleep(60*retrytime_mins)
 | 
			
		||||
 | 
			
		||||
if __name__=="__main__":
 | 
			
		||||
	run(False)
 | 
			
		||||
    run(False)
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user