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 | ||||
|  | ||||
|   | ||||
| @@ -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. | ||||
| @@ -188,9 +193,14 @@ waterfall_auto_level_margin = (5, 40) | ||||
| #   ___|____________________________________|____________________________________|____________________________________|___> 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 | 
| @@ -28,15 +28,23 @@ | ||||
|             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_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> | ||||
| @@ -73,8 +81,8 @@ | ||||
|             <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> | ||||
| @@ -82,13 +90,24 @@ | ||||
|                 <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 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> | ||||
| @@ -107,6 +126,7 @@ | ||||
|                         <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"> | ||||
| @@ -115,13 +135,15 @@ | ||||
|                         </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 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> | ||||
| @@ -134,6 +156,17 @@ | ||||
|                     <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> | ||||
|   | ||||
							
								
								
									
										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 | ||||
| 		{*/ | ||||
| @@ -1179,6 +1210,18 @@ function on_ws_recv(evt) | ||||
| 						break; | ||||
| 					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]); | ||||
| @@ -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,16 +1561,24 @@ 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(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); | ||||
|     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) | ||||
| @@ -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(); | ||||
| } | ||||
|   | ||||
							
								
								
									
										94
									
								
								openwebrx.py
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								openwebrx.py
									
									
									
									
									
								
							| @@ -20,14 +20,13 @@ print "" # python2.7 is required to run OpenWebRX instead of python3. Please run | ||||
|     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| """ | ||||
| sw_version="v0.15" | ||||
| sw_version="v0.17" | ||||
| #0.15 (added nmux) | ||||
|  | ||||
| import os | ||||
| import code | ||||
| import importlib | ||||
| import plugins | ||||
| import plugins.dsp | ||||
| import csdr | ||||
| import thread | ||||
| import time | ||||
| import datetime | ||||
| @@ -63,6 +62,7 @@ try: import __pypy__ | ||||
| except: pass | ||||
| pypy="__pypy__" in globals() | ||||
|  | ||||
| """ | ||||
| def import_all_plugins(directory): | ||||
|     for subdir in os.listdir(directory): | ||||
|         if os.path.isdir(directory+subdir) and not subdir[0]=="_": | ||||
| @@ -71,6 +71,7 @@ def import_all_plugins(directory): | ||||
|                 importname=(directory+subdir+"/plugin").replace("/",".") | ||||
|                 print "[openwebrx-import] Found plugin:",importname | ||||
|                 importlib.import_module(importname) | ||||
| """ | ||||
|  | ||||
| class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): | ||||
|     pass | ||||
| @@ -91,7 +92,8 @@ def handle_signal(sig, frame): | ||||
|             print | ||||
|             for key in client._fields: | ||||
|                 print "\t%s = %s"%(key,str(getattr(client,key))) | ||||
|  | ||||
|     elif sig == signal.SIGUSR2: | ||||
|         code.interact(local=globals()) | ||||
|     else: | ||||
|         print "[openwebrx] Ctrl+C: aborting." | ||||
|         cleanup_clients(True) | ||||
| @@ -127,9 +129,7 @@ def main(): | ||||
|     #Set signal handler | ||||
|     signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python | ||||
|     signal.signal(signal.SIGUSR1, handle_signal) | ||||
|  | ||||
| 	#Load plugins | ||||
| 	import_all_plugins("plugins/dsp/") | ||||
|     signal.signal(signal.SIGUSR2, handle_signal) | ||||
|  | ||||
|     #Pypy | ||||
|     if pypy: print "pypy detected (and now something completely different: c code is expected to run at a speed of 3*10^8 m/s?)" | ||||
| @@ -292,7 +292,7 @@ def apply_csdr_cfg_to_dsp(dsp): | ||||
|  | ||||
| def spectrum_thread_function(): | ||||
|     global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick | ||||
| 	spectrum_dsp=dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() | ||||
|     spectrum_dsp=dsp=csdr.dsp() | ||||
|     dsp.nc_port=cfg.iq_server_port | ||||
|     dsp.set_demodulator("fft") | ||||
|     dsp.set_samp_rate(cfg.samp_rate) | ||||
| @@ -427,6 +427,7 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|             # there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python | ||||
|             #if self.path[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment! | ||||
|             if self.path[:4]=="/ws/": | ||||
|                 print "[openwebrx-ws] Client requested WebSocket connection" | ||||
|                 if receiver_failed: self.send_error(500,"Internal server error") | ||||
|                 try: | ||||
|                     # ========= WebSocket handshake  ========= | ||||
| @@ -459,21 +460,22 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                     rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients)) | ||||
|  | ||||
|                     # ========= Initialize DSP ========= | ||||
| 					dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() | ||||
|                     dsp=csdr.dsp() | ||||
|                     dsp_initialized=False | ||||
|                     dsp.set_audio_compression(cfg.audio_compression) | ||||
|                     dsp.set_fft_compression(cfg.fft_compression) #used by secondary chains | ||||
|                     dsp.set_format_conversion(cfg.format_conversion) | ||||
|                     dsp.set_offset_freq(0) | ||||
|                     dsp.set_bpf(-4000,4000) | ||||
|                     dsp.set_secondary_fft_size(cfg.digimodes_fft_size) | ||||
|                     dsp.nc_port=cfg.iq_server_port | ||||
|                     apply_csdr_cfg_to_dsp(dsp) | ||||
|                     myclient.dsp=dsp | ||||
|  | ||||
|                     do_secondary_demod=False | ||||
|                     access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")") | ||||
|  | ||||
| 					myclient.loopstat=0 | ||||
|  | ||||
|                     while True: | ||||
|                         myclient.loopstat=0 | ||||
|                         if myclient.closed[0]: | ||||
|                             print "[openwebrx-httpd:ws] client closed by other thread" | ||||
|                             break | ||||
| @@ -514,12 +516,34 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                             rxws.send(self,myclient.bcastmsg) | ||||
|                             myclient.bcastmsg="" | ||||
|  | ||||
|                         # ========= send secondary ========= | ||||
|                         if do_secondary_demod: | ||||
|                             myclient.loopstat=41 | ||||
|                             while True: | ||||
|                                 try:  | ||||
|                                     secondary_spectrum_data=dsp.read_secondary_fft(dsp.get_secondary_fft_bytes_to_read()) | ||||
|                                     if len(secondary_spectrum_data) == 0: break | ||||
|                                     # print "len(secondary_spectrum_data)", len(secondary_spectrum_data) #TODO digimodes | ||||
|                                     rxws.send(self, secondary_spectrum_data, "FFTS") | ||||
|                                 except: break | ||||
|                             myclient.loopstat=42 | ||||
|                             while True: | ||||
|                                 try: | ||||
|                                     myclient.loopstat=422 | ||||
|                                     secondary_demod_data=dsp.read_secondary_demod(1) | ||||
|                                     myclient.loopstat=423 | ||||
|                                     if len(secondary_demod_data) == 0: break | ||||
|                                     # print "len(secondary_demod_data)", len(secondary_demod_data), secondary_demod_data #TODO digimodes | ||||
|                                     rxws.send(self, secondary_demod_data, "DAT ") | ||||
|                                 except: break | ||||
|  | ||||
|                         # ========= process commands ========= | ||||
|                         while True: | ||||
|                             myclient.loopstat=50 | ||||
|                             rdata=rxws.recv(self, False) | ||||
| 							if not rdata: break | ||||
|                             myclient.loopstat=51 | ||||
|                             #try: | ||||
|                             if not rdata: break | ||||
|                             elif rdata[:3]=="SET": | ||||
|                                 print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata) | ||||
|                                 pairs=rdata[4:].split(" ") | ||||
| @@ -528,13 +552,13 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                                 filter_limit=dsp.get_output_rate()/2 | ||||
|                                 for pair in pairs: | ||||
|                                     param_name, param_value = pair.split("=") | ||||
| 									if param_name == "low_cut" and -filter_limit <= float(param_value) <= filter_limit: | ||||
|                                     if param_name == "low_cut" and -filter_limit <= int(param_value) <= filter_limit: | ||||
|                                         bpf_set=True | ||||
|                                         new_bpf[0]=int(param_value) | ||||
| 									elif param_name == "high_cut" and -filter_limit <= float(param_value) <= filter_limit: | ||||
|                                     elif param_name == "high_cut" and -filter_limit <= int(param_value) <= filter_limit: | ||||
|                                         bpf_set=True | ||||
|                                         new_bpf[1]=int(param_value) | ||||
| 									elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2: | ||||
|                                     elif param_name == "offset_freq" and -cfg.samp_rate/2 <= int(param_value) <= cfg.samp_rate/2: | ||||
|                                         myclient.loopstat=510 | ||||
|                                         dsp.set_offset_freq(int(param_value)) | ||||
|                                     elif param_name == "squelch_level" and float(param_value) >= 0: | ||||
| @@ -557,6 +581,19 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                                             myclient.loopstat=550 | ||||
|                                             dsp.start() | ||||
|                                             dsp_initialized=True | ||||
|                                     elif param_name=="secondary_mod" and cfg.digimodes_enable: | ||||
|                                         if (dsp.get_secondary_demodulator() != param_value): | ||||
|                                             if dsp_initialized: dsp.stop() | ||||
|                                             if param_value == "off": | ||||
|                                                 dsp.set_secondary_demodulator(None) | ||||
|                                                 do_secondary_demod = False | ||||
|                                             else: | ||||
|                                                 dsp.set_secondary_demodulator(param_value) | ||||
|                                                 do_secondary_demod = True | ||||
|                                                 rxws.send(self, "MSG secondary_fft_size={0} if_samp_rate={1} secondary_bw={2} secondary_setup".format(cfg.digimodes_fft_size, dsp.if_samp_rate(), dsp.secondary_bw())) | ||||
|                                             if dsp_initialized: dsp.start() | ||||
|                                     elif param_name=="secondary_offset_freq" and 0 <= int(param_value) <= dsp.if_samp_rate()/2 and cfg.digimodes_enable: | ||||
|                                         dsp.set_secondary_offset_freq(int(param_value)) | ||||
|                                     else: | ||||
|                                         print "[openwebrx-httpd:ws] invalid parameter" | ||||
|                                 if bpf_set: | ||||
| @@ -564,16 +601,20 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                                     dsp.set_bpf(*new_bpf) | ||||
|                                 #code.interact(local=locals()) | ||||
|                 except: | ||||
|                     myclient.loopstat=990 | ||||
|                     exc_type, exc_value, exc_traceback = sys.exc_info() | ||||
| 					if exc_value[0]==32: #"broken pipe", client disconnected | ||||
| 						pass | ||||
| 					elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected | ||||
| 						pass | ||||
| 					else: | ||||
| 						print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value | ||||
| 						traceback.print_tb(exc_traceback) | ||||
|                     print "[openwebrx-httpd:ws] exception: ",exc_type,exc_value | ||||
|                     traceback.print_tb(exc_traceback) #TODO digimodes | ||||
|                     #if exc_value[0]==32: #"broken pipe", client disconnected | ||||
|                     #    pass | ||||
|                     #elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected | ||||
|                     #    pass | ||||
|                     #else: | ||||
|                     #    print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value | ||||
|                     #    traceback.print_tb(exc_traceback) | ||||
|  | ||||
|                 #stop dsp for the disconnected client | ||||
|                 myclient.loopstat=991 | ||||
|                 try: | ||||
|                     dsp.stop() | ||||
|                     del dsp | ||||
| @@ -581,6 +622,7 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                     print "[openwebrx-httpd] error in dsp.stop()" | ||||
|  | ||||
|                 #delete disconnected client | ||||
|                 myclient.loopstat=992 | ||||
|                 try: | ||||
|                     cma("do_GET /ws/ delete disconnected") | ||||
|                     id_to_close=get_client_by_id(myclient.id,False) | ||||
| @@ -644,7 +686,11 @@ class WebRXHandler(BaseHTTPRequestHandler): | ||||
|                         ("%[WATERFALL_COLORS]",cfg.waterfall_colors), | ||||
|                         ("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)), | ||||
|                         ("%[WATERFALL_MAX_LEVEL]",str(cfg.waterfall_max_level)), | ||||
| 						("%[WATERFALL_AUTO_LEVEL_MARGIN]","[%d,%d]"%cfg.waterfall_auto_level_margin) | ||||
|                         ("%[WATERFALL_AUTO_LEVEL_MARGIN]","[%d,%d]"%cfg.waterfall_auto_level_margin), | ||||
|                         ("%[DIGIMODES_ENABLE]",("true" if cfg.digimodes_enable else "false")), | ||||
|                         ("%[MATHBOX_WATERFALL_FRES]",str(cfg.mathbox_waterfall_frequency_resolution)), | ||||
|                         ("%[MATHBOX_WATERFALL_THIST]",str(cfg.mathbox_waterfall_history_length)), | ||||
|                         ("%[MATHBOX_WATERFALL_COLORS]",cfg.mathbox_waterfall_colors) | ||||
|                     ) | ||||
|                     for rule in replace_dictionary: | ||||
|                         while data.find(rule[0])!=-1: | ||||
|   | ||||
| @@ -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) | ||||
										
											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 | 
		Reference in New Issue
	
	Block a user
	 ha7ilm
					ha7ilm