Merged feature/digitalmods
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,3 @@ | ||||
| *.pyc | ||||
| *.swp | ||||
| tags | ||||
|   | ||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,16 +3,19 @@ OpenWebRX | ||||
|  | ||||
| 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, 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> | ||||
| @@ -32,13 +35,16 @@ It has the following features: | ||||
| - *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-07)** | ||||
| - 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 | ||||
|  | ||||
|   | ||||
| @@ -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,19 +67,22 @@ sdrhu_key = "" | ||||
| sdrhu_public_listing = False | ||||
|  | ||||
| # ==== DSP/RX settings ==== | ||||
| dsp_plugin="csdr" | ||||
| fft_fps=9 | ||||
| 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. | ||||
|  | ||||
| samp_rate = 250000 | ||||
| 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 | ||||
|  | ||||
| """ | ||||
| @@ -104,7 +107,9 @@ Note: if you experience audio underruns while CPU usage is 100%, you can: | ||||
| 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: | ||||
| @@ -127,9 +132,9 @@ 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. | ||||
| @@ -182,15 +187,20 @@ waterfall_auto_level_margin = (5, 40) | ||||
| ##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]] | ||||
| #   [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 __| | ||||
| # ==== Experimental settings === | ||||
|  | ||||
| #Warning! These are very experimental. | ||||
| # 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. | ||||
|   | ||||
							
								
								
									
										424
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										424
									
								
								csdr.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,424 @@ | ||||
| """ | ||||
| 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): | ||||
|         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 fastagc_ff 1024 | csdr convert_f_s16"+chain_end | ||||
|         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 | 
							
								
								
									
										283
									
								
								htdocs/index.wrx
									
									
									
									
									
								
							
							
						
						
									
										283
									
								
								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,128 +20,161 @@ | ||||
|  | ||||
| --> | ||||
| <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]; | ||||
| 			var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN]; | ||||
| 		</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,115"> | ||||
| 					<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> | ||||
| 					<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="1" 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 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 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="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> | ||||
|                     <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> | ||||
|     </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: 35px; | ||||
| 	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) | ||||
| { | ||||
| @@ -549,6 +550,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; | ||||
| @@ -565,6 +567,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) | ||||
| @@ -579,8 +582,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) | ||||
| 	{ | ||||
| @@ -588,6 +600,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) | ||||
| @@ -1055,7 +1068,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(); | ||||
| } | ||||
| @@ -1087,9 +1100,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; | ||||
| @@ -1126,14 +1147,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) | ||||
| @@ -1143,9 +1165,10 @@ function on_ws_recv(evt) | ||||
| 		audio_buffer_all_size_debug+=audio_data.length; | ||||
| 		if(!ios && (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") | ||||
| 		{ | ||||
| @@ -1154,9 +1177,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 | ||||
| 		{*/ | ||||
| @@ -1180,6 +1211,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; | ||||
| @@ -1279,8 +1322,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; | ||||
| @@ -1516,19 +1561,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; | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| @@ -1569,6 +1622,8 @@ function audio_preinit() | ||||
|  | ||||
| function audio_init() | ||||
| { | ||||
| 	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(); | ||||
| @@ -1613,6 +1668,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() | ||||
| @@ -1653,17 +1709,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) | ||||
| @@ -1686,7 +1743,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; | ||||
| @@ -1706,9 +1763,11 @@ function add_canvas() | ||||
| 	canvases.push(new_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); | ||||
| @@ -1765,7 +1824,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(); | ||||
| @@ -1774,6 +1833,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; | ||||
| @@ -1850,6 +1945,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++) | ||||
| @@ -1859,13 +1962,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"); | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -1895,6 +1998,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(); | ||||
| @@ -1909,6 +2186,7 @@ function openwebrx_init() | ||||
| 	(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); | ||||
| @@ -2002,9 +2280,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"; | ||||
| @@ -2093,6 +2376,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) | ||||
| @@ -2101,7 +2385,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); | ||||
| 	} | ||||
| } | ||||
| @@ -2125,3 +2409,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(); | ||||
| } | ||||
|   | ||||
							
								
								
									
										1132
									
								
								openwebrx.py
									
									
									
									
									
								
							
							
						
						
									
										1132
									
								
								openwebrx.py
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,251 +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 | ||||
|  | ||||
| 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 | ||||
| 		self.fft_averages = 50 | ||||
|  | ||||
| 	def chain(self,which): | ||||
| 		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 | " | ||||
| 		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 fastagc_ff 1024 | csdr convert_f_s16"+chain_end | ||||
| 		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 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 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 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 = 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) | ||||
|  | ||||
| 		#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 ) | ||||
|  | ||||
| 		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) | ||||
|  | ||||
| 	def read(self,size): | ||||
| 		return self.process.stdout.read(size) | ||||
|  | ||||
| 	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 | ||||
| 		self.running = False | ||||
|  | ||||
| 	def restart(self): | ||||
| 		self.stop() | ||||
| 		self.start() | ||||
|  | ||||
| 	def __del__(self): | ||||
| 		self.stop() | ||||
| 		del(self.process) | ||||
							
								
								
									
										230
									
								
								rxws.py
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								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,110 +26,110 @@ 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 | ||||
| @@ -138,34 +138,34 @@ def recv(myself, blocking=False, debug=False): | ||||
|  | ||||
|  | ||||
| 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 -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 __name__=="__main__": | ||||
| 	run(False) | ||||
|  | ||||
|     run(False) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 ha7ilm
					ha7ilm