Merged feature/digitalmods
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,3 @@ | |||||||
| *.pyc | *.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. | OpenWebRX is a multi-user SDR receiver software with a web interface. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| It has the following features: | 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, | - filter passband can be set from GUI, | ||||||
| - waterfall display can be shifted back in time, | - 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), | - 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>. | - 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)** | **News (2015-08-18)** | ||||||
| - My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a> | - My BSc. thesis written on OpenWebRX is <a href="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. | - *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. | - 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*! | > When upgrading OpenWebRX, please make sure that you also upgrade *csdr*! | ||||||
|  |  | ||||||
| ## OpenWebRX servers on SDR.hu | ## 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. | [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 | ## Setup | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,19 +67,22 @@ sdrhu_key = "" | |||||||
| sdrhu_public_listing = False | sdrhu_public_listing = False | ||||||
|  |  | ||||||
| # ==== DSP/RX settings ==== | # ==== DSP/RX settings ==== | ||||||
| dsp_plugin="csdr" |  | ||||||
| fft_fps=9 | fft_fps=9 | ||||||
| fft_size=4096 #Should be power of 2 | 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. | 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 | # samp_rate = 250000 | ||||||
| center_freq = 145525000 | 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. | 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 | ppm = 0 | ||||||
|  |  | ||||||
| audio_compression="adpcm" #valid values: "adpcm", "none" | audio_compression="adpcm" #valid values: "adpcm", "none" | ||||||
| fft_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 | 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) | 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" | 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" | #format_conversion="csdr convert_s8_f" | ||||||
| """ | """ | ||||||
| To use a HackRF, compile the HackRF host tools from its "stdout" branch: | 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" | #format_conversion="csdr convert_s16_f | csdr gain_ff 30" | ||||||
|  |  | ||||||
| # >> /dev/urandom test signal source | # >> /dev/urandom test signal source | ||||||
| #samp_rate = 2400000 | # 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) | # 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" | # format_conversion="csdr convert_u8_f" | ||||||
|  |  | ||||||
| # >> Pre-recorded raw I/Q file as signal source | # >> 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. | # 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 | #   ___|____________________________________|____________________________________|____________________________________|___> signal power | ||||||
| #        \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level          | \_waterfall_auto_level_margin[1]_/ | #        \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level          | \_waterfall_auto_level_margin[1]_/ | ||||||
| #                                                      current_max_power_level __| | #                                                      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_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_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. | 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 ws_url="%[WS_URL]"; | ||||||
|             var rx_photo_height=%[RX_PHOTO_HEIGHT]; |             var rx_photo_height=%[RX_PHOTO_HEIGHT]; | ||||||
|             var audio_buffering_fill_to=%[AUDIO_BUFSIZE]; |             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 starting_offset_frequency = %[START_OFFSET_FREQ]; | ||||||
|             var waterfall_colors=%[WATERFALL_COLORS]; |             var waterfall_colors=%[WATERFALL_COLORS]; | ||||||
|             var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL]; |             var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL]; | ||||||
|             var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL]; |             var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL]; | ||||||
|             var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN]; |             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> | ||||||
|         <script src="sdr.js"></script> |         <script src="sdr.js"></script> | ||||||
|  | 		<script src="mathbox-bundle.min.js"></script> | ||||||
|         <script src="openwebrx.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" /> |         <link rel="stylesheet" type="text/css" href="openwebrx.css" /> | ||||||
|         <meta charset="utf-8"> |         <meta charset="utf-8"> | ||||||
|     </head> |     </head> | ||||||
| @@ -73,8 +81,8 @@ | |||||||
|             <div id="openwebrx-scale-container"> |             <div id="openwebrx-scale-container"> | ||||||
|                 <canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas> |                 <canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas> | ||||||
|             </div> |             </div> | ||||||
|  | 			<div id="openwebrx-mathbox-container"> </div> | ||||||
|             <div id="webrx-canvas-container"> |             <div id="webrx-canvas-container"> | ||||||
|  |  | ||||||
|                 <div id="openwebrx-phantom-canvas"></div> |                 <div id="openwebrx-phantom-canvas"></div> | ||||||
|                 <!-- add canvas here by javascript --> |                 <!-- add canvas here by javascript --> | ||||||
|             </div> |             </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 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-actual-freq">---.--- MHz</div> | ||||||
|                     <div id="webrx-mouse-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-panel-line"> | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div> |                         <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"  | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div> |                             onclick="demodulator_analog_replace('nfm');">FM</div> | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div> |                         <div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"  | ||||||
| 						<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div> |                             onclick="demodulator_analog_replace('am');">AM</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-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> | ||||||
|                     <div class="openwebrx-panel-line"> |                     <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> |                         <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="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="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="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 id="openwebrx-smeter-db">0 dB</div> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div class="openwebrx-panel-line"> |                     <div class="openwebrx-panel-line"> | ||||||
| @@ -115,13 +135,15 @@ | |||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
| 				<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142"> |                 <div class="openwebrx-panel" 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" id="openwebrx-log-scroll"> |                     <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> |                             <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/> |                             <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 id="openwebrx-debugdiv"></div> | ||||||
|                         </div> |                         </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-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-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div> | ||||||
|                     <div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div> |                     <div class="openwebrx-progressbar" id="openwebrx-bar-audio-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> |                     <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. |                     <br />We're working on the code right now, so the application might fail. | ||||||
|                 </div> |                 </div> | ||||||
|  |                 <div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210"> | ||||||
|  |                     <div id="openwebrx-digimode-canvas-container"> | ||||||
|  |                         <div id="openwebrx-digimode-select-channel"></div> | ||||||
|  |                     </div> | ||||||
|  |                     <div id="openwebrx-digimode-content-container"> | ||||||
|  |                         <div class="gradient"></div> | ||||||
|  |                         <div id="openwebrx-digimode-content"> | ||||||
|  |                         <span id="openwebrx-cursor-blink"></span> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|     </div> |     </div> | ||||||
| </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; | 	overflow: hidden; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | select | ||||||
|  | { | ||||||
|  | 	font-family: "DejaVu Sans", Verdana, Geneva, sans-serif; | ||||||
|  | } | ||||||
|  |  | ||||||
| input | input | ||||||
| { | { | ||||||
| 	vertical-align:middle; | 	vertical-align:middle; | ||||||
| @@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	border-style: none; | 	border-style: none; | ||||||
| 	image-rendering: crisp-edges; | 	image-rendering: crisp-edges; | ||||||
| 	image-rendering: -webkit-optimize-contrast; | 	image-rendering: -webkit-optimize-contrast; | ||||||
|  |     /*transition: left 200ms, width 200ms;*/ | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #openwebrx-mathbox-container | ||||||
|  | { | ||||||
|  | 	overflow: none; | ||||||
|  | 	display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #openwebrx-phantom-canvas | #openwebrx-phantom-canvas | ||||||
| @@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	height: 396px; | 	height: 396px; | ||||||
| }*/ | }*/ | ||||||
|  |  | ||||||
| /*#webrx-debugdiv | #openwebrx-log-scroll | ||||||
| { | { | ||||||
| 	font-size: 10pt; |     /*overflow-y:auto;*/ | ||||||
| 	/*overflow-y:scroll;*/ |     height: 125px; | ||||||
| /*}*/ |     width: 619px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .nano .nano-pane   { background: #444; } | ||||||
|  | .nano .nano-slider { background: #eee !important;  } | ||||||
|  |  | ||||||
| #webrx-main-container | #webrx-main-container | ||||||
| { | { | ||||||
| @@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-panel | .openwebrx-panel | ||||||
| { | { | ||||||
| 	transform: perspective( 600px ); | 	transform: perspective( 600px ) rotateX( 90deg ); | ||||||
| 	visibility: hidden; | 	visibility: hidden; | ||||||
| 	background-color: #575757; | 	background-color: #575757; | ||||||
| 	padding: 10px; | 	padding: 10px; | ||||||
| @@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| .openwebrx-button | .openwebrx-button | ||||||
| { | { | ||||||
| 	background-color: #373737; | 	background-color: #373737; | ||||||
| 	padding: 5px; | 	padding: 4.2px; | ||||||
| 	border-radius: 5px; | 	border-radius: 5px; | ||||||
| 	-moz-border-radius: 5px; | 	-moz-border-radius: 5px; | ||||||
| 	color: White; | 	color: White; | ||||||
| @@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
| 	display: inline-block; | 	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:-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% );*/ | 	background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/ | ||||||
| @@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-demodulator-button | .openwebrx-demodulator-button | ||||||
| { | { | ||||||
| 	width: 35px; | 	width: 38px; | ||||||
| 	height: 19px; | 	height: 19px; | ||||||
| 	font-size: 12pt; | 	font-size: 12pt; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| @@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper | |||||||
|  |  | ||||||
| .openwebrx-square-button img | .openwebrx-square-button img | ||||||
| { | { | ||||||
| 	height: 30px; | 	height: 27px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .openwebrx-round-button | .openwebrx-round-button | ||||||
| @@ -732,7 +748,7 @@ img.openwebrx-mirror-img | |||||||
| { | { | ||||||
| 	position: relative; | 	position: relative; | ||||||
| 	top: -2px; | 	top: -2px; | ||||||
| 	width:91px; | 	width: 95px; | ||||||
| } | } | ||||||
|  |  | ||||||
| .openwebrx-sliderbtn-img | .openwebrx-sliderbtn-img | ||||||
| @@ -776,7 +792,7 @@ img.openwebrx-mirror-img | |||||||
| 	font-size: 10pt; | 	font-size: 10pt; | ||||||
| 	float: right; | 	float: right; | ||||||
| 	margin-right: 5px; | 	margin-right: 5px; | ||||||
| 	margin-top: 29px; | 	margin-top: 24px; | ||||||
| 	font-family: 'expletus-sans-medium'; | 	font-family: 'expletus-sans-medium'; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -806,3 +822,152 @@ img.openwebrx-mirror-img | |||||||
| { | { | ||||||
| 	width: 150px; | 	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_setup_done=0; | ||||||
| var waterfall_queue = []; | var waterfall_queue = []; | ||||||
| var waterfall_timer; | var waterfall_timer; | ||||||
|  | var secondary_fft_size; | ||||||
|  |  | ||||||
| /*function fade(something,from,to,time_ms,fps) | /*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) | 	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. | 	{ //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 | 		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; | 		this.dragged_range=demodulator.draggable_ranges.none; | ||||||
| 		return to_return; | 		return to_return; | ||||||
| @@ -565,6 +567,7 @@ function mkenvelopes(visible_range) //called from mkscale | |||||||
| 	{ | 	{ | ||||||
| 		demodulators[i].envelope.draw(visible_range); | 		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) | function demodulator_remove(which) | ||||||
| @@ -579,8 +582,17 @@ function demodulator_add(what) | |||||||
| 	mkenvelopes(get_visible_freq_range()); | 	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 | { //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; | 	var temp_offset=0; | ||||||
| 	if(demodulators.length) | 	if(demodulators.length) | ||||||
| 	{ | 	{ | ||||||
| @@ -588,6 +600,7 @@ function demodulator_analog_replace(subtype) | |||||||
| 		demodulator_remove(0); | 		demodulator_remove(0); | ||||||
| 	} | 	} | ||||||
| 	demodulator_add(new demodulator_default_analog(temp_offset,subtype)); | 	demodulator_add(new demodulator_default_analog(temp_offset,subtype)); | ||||||
|  | 	demodulator_buttons_update(); | ||||||
| } | } | ||||||
|  |  | ||||||
| function demodulator_set_offset_frequency(which,to_what) | 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); | 	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()); | 	//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; | 	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); | 	resize_canvases(true); | ||||||
| 	mkscale(); | 	mkscale(); | ||||||
| } | } | ||||||
| @@ -1087,9 +1100,17 @@ function zoom_calc() | |||||||
| function resize_waterfall_container(check_init) | function resize_waterfall_container(check_init) | ||||||
| { | { | ||||||
| 	if(check_init&&!waterfall_setup_done) return; | 	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_server_output_rate=11025; | ||||||
| audio_client_resampling_factor=4; | 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; } | 	if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; } | ||||||
| 	// | 	// | ||||||
| 	debug_ws_data_received+=evt.data.byteLength/1000; | 	debug_ws_data_received+=evt.data.byteLength/1000; | ||||||
| 	firstChars=getFirstChars(evt.data,3); | 	first4Chars=getFirstChars(evt.data,4); | ||||||
| 	if(firstChars=="CLI") |     first3Chars=first4Chars.slice(0,3); | ||||||
|  | 	if(first3Chars=="CLI") | ||||||
| 	{ | 	{ | ||||||
| 		var stringData=arrayBufferToString(evt.data); | 		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; | 		var audio_data; | ||||||
| 		if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4) | 		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; | 		audio_buffer_all_size_debug+=audio_data.length; | ||||||
| 		if(!ios && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init() | 		if(!ios && (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"); | 		//alert("Yupee! Doing FFT"); | ||||||
|  |         //if(first4Chars=="FFTS") console.log("FFTS");  | ||||||
| 		if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); | 		if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); | ||||||
| 		else if(fft_compression="adpcm") | 		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_i16=fft_codec.decode(new Uint8Array(evt.data,4)); | ||||||
| 			var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N); | 			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; | 			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 | 		/*try | ||||||
| 		{*/ | 		{*/ | ||||||
| @@ -1179,6 +1210,18 @@ function on_ws_recv(evt) | |||||||
| 						break; | 						break; | ||||||
| 					case "fft_size": | 					case "fft_size": | ||||||
| 						fft_size=parseInt(param[1]); | 						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; |                         break; | ||||||
| 					case "fft_fps": | 					case "fft_fps": | ||||||
| 						fft_fps=parseInt(param[1]); | 						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 | 		if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present | ||||||
| 	} | 	} | ||||||
| 	e("openwebrx-debugdiv").innerHTML+=what+"<br />"; | 	e("openwebrx-debugdiv").innerHTML+=what+"<br />"; | ||||||
| 	var wls=e("openwebrx-log-scroll"); | 	//var wls=e("openwebrx-log-scroll"); | ||||||
| 	wls.scrollTop=wls.scrollHeight; //scroll to bottom | 	//wls.scrollTop=wls.scrollHeight; //scroll to bottom | ||||||
|  |     $(".nano").nanoScroller(); | ||||||
|  |     $(".nano").nanoScroller({ scroll: 'bottom' }); | ||||||
| } | } | ||||||
|  |  | ||||||
| var audio_context; | var audio_context; | ||||||
| @@ -1516,16 +1561,24 @@ function webrx_set_param(what, value) | |||||||
| 	ws.send("SET "+what+"="+value.toString()); | 	ws.send("SET "+what+"="+value.toString()); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var starting_mute = false; | ||||||
|  |  | ||||||
| function parsehash() | function parsehash() | ||||||
| { | { | ||||||
| 	if(h=window.location.hash) | 	if(h=window.location.hash) | ||||||
| 	{ | 	{ | ||||||
| 		h.substring(1).split(",").forEach(function(x){ | 		h.substring(1).split(",").forEach(function(x){ | ||||||
| 			harr=x.split("="); | 			harr=x.split("="); | ||||||
| 			console.log(harr); | 			//console.log(harr); | ||||||
| 			if(harr[0]=="mod") starting_mod = harr[1]; | 			if(harr[0]=="mute") toggleMute(); | ||||||
| 			if(harr[0]=="sql") { e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); } | 			else if(harr[0]=="mod") starting_mod = harr[1]; | ||||||
| 			if(harr[0]=="freq") { | 			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(parseInt(harr[1])); | ||||||
| 				console.log(center_freq); | 				console.log(center_freq); | ||||||
| 				starting_offset_frequency = parseInt(harr[1])-center_freq; | 				starting_offset_frequency = parseInt(harr[1])-center_freq; | ||||||
| @@ -1569,6 +1622,8 @@ function audio_preinit() | |||||||
|  |  | ||||||
| function audio_init() | function audio_init() | ||||||
| { | { | ||||||
|  | 	if(starting_mute) toggleMute(); | ||||||
|  |  | ||||||
| 	if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor... | 	if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor... | ||||||
|  |  | ||||||
| 	audio_debug_time_start=(new Date()).getTime(); | 	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) | 			//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200) | ||||||
| 		} | 		} | ||||||
| 	},2000); | 	},2000); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| function on_ws_closed() | function on_ws_closed() | ||||||
| @@ -1653,17 +1709,18 @@ function open_websocket() | |||||||
| 	ws.onerror = on_ws_error; | 	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_min_level) db_value=waterfall_min_level; | ||||||
| 	if(db_value>waterfall_max_level) db_value=waterfall_max_level; | 	if(db_value>waterfall_max_level) db_value=waterfall_max_level; | ||||||
| 	full_scale=waterfall_max_level-waterfall_min_level; | 	full_scale=waterfall_max_level-waterfall_min_level; | ||||||
| 	relative_value=db_value-waterfall_min_level; | 	relative_value=db_value-waterfall_min_level; | ||||||
| 	value_percent=relative_value/full_scale; | 	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); | 	index=Math.floor(value_percent/percent_for_one_color); | ||||||
| 	remain=(value_percent-percent_for_one_color*index)/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) | function color_between(first, second, percent) | ||||||
| @@ -1686,7 +1743,7 @@ var canvas_phantom; | |||||||
|  |  | ||||||
| function add_canvas() | function add_canvas() | ||||||
| { | { | ||||||
| 	new_canvas = document.createElement("canvas"); | 	var new_canvas = document.createElement("canvas"); | ||||||
| 	new_canvas.width=fft_size; | 	new_canvas.width=fft_size; | ||||||
| 	new_canvas.height=canvas_default_height; | 	new_canvas.height=canvas_default_height; | ||||||
| 	canvas_actual_line=canvas_default_height-1; | 	canvas_actual_line=canvas_default_height-1; | ||||||
| @@ -1706,9 +1763,11 @@ function add_canvas() | |||||||
| 	canvases.push(new_canvas); | 	canvases.push(new_canvas); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| function init_canvas_container() | function init_canvas_container() | ||||||
| { | { | ||||||
| 	canvas_container=e("webrx-canvas-container"); | 	canvas_container=e("webrx-canvas-container"); | ||||||
|  | 	mathbox_container=e("openwebrx-mathbox-container"); | ||||||
| 	canvas_container.addEventListener("mouseout",canvas_container_mouseout, false); | 	canvas_container.addEventListener("mouseout",canvas_container_mouseout, false); | ||||||
| 	//window.addEventListener("mouseout",window_mouseout,false); | 	//window.addEventListener("mouseout",window_mouseout,false); | ||||||
| 	//document.body.addEventListener("mouseup",body_mouseup,false); | 	//document.body.addEventListener("mouseup",body_mouseup,false); | ||||||
| @@ -1765,7 +1824,7 @@ function resize_canvases(zoom) | |||||||
| function waterfall_init() | function waterfall_init() | ||||||
| { | { | ||||||
| 	init_canvas_container(); | 	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(); | 	resize_waterfall_container(false); /* then */ resize_canvases(); | ||||||
| 	scale_setup(); | 	scale_setup(); | ||||||
| 	mkzoomlevels(); | 	mkzoomlevels(); | ||||||
| @@ -1774,6 +1833,42 @@ function waterfall_init() | |||||||
|  |  | ||||||
| var waterfall_dont_scale=0; | 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) | function waterfall_add(data) | ||||||
| { | { | ||||||
| 	if(!waterfall_setup_done) return; | 	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; | 			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 | 	//Add line to waterfall image | ||||||
| 	oneline_image = canvas_context.createImageData(w,1); | 	oneline_image = canvas_context.createImageData(w,1); | ||||||
| 	for(x=0;x<w;x++) | 	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; | 			oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	//Draw image | 	//Draw image | ||||||
| 	canvas_context.putImageData(oneline_image, 0, canvas_actual_line--); | 	canvas_context.putImageData(oneline_image, 0, canvas_actual_line--); | ||||||
| 	shift_canvases(); | 	shift_canvases(); | ||||||
| 	if(canvas_actual_line<0) add_canvas(); | 	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() | function openwebrx_resize() | ||||||
| { | { | ||||||
| 	resize_canvases(); | 	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"; | 	(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px"; | ||||||
| 	init_rx_photo(); | 	init_rx_photo(); | ||||||
| 	open_websocket(); | 	open_websocket(); | ||||||
|  |     secondary_demod_init(); | ||||||
| 	place_panels(first_show_panel); | 	place_panels(first_show_panel); | ||||||
| 	window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000); | 	window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000); | ||||||
| 	window.addEventListener("resize",openwebrx_resize); | 	window.addEventListener("resize",openwebrx_resize); | ||||||
| @@ -2002,9 +2280,14 @@ function pop_bottommost_panel(from) | |||||||
| 	return to_return; | 	return to_return; | ||||||
| } | } | ||||||
|  |  | ||||||
| function toggle_panel(what) | function toggle_panel(what, on) | ||||||
| { | { | ||||||
|     var item=e(what); |     var item=e(what); | ||||||
|  |     if(typeof on !== "undefined")  | ||||||
|  |     { | ||||||
|  |         if(item.openwebrxHidden && !on) return; | ||||||
|  |         if(!item.openwebrxHidden && on) return; | ||||||
|  |     } | ||||||
| 	if(item.openwebrxDisableClick) return; | 	if(item.openwebrxDisableClick) return; | ||||||
| 	item.style.transitionDuration="599ms"; | 	item.style.transitionDuration="599ms"; | ||||||
| 	item.style.transitionDelay="0ms"; | 	item.style.transitionDelay="0ms"; | ||||||
| @@ -2093,6 +2376,7 @@ function place_panels(function_apply) | |||||||
| 		p.style.visibility="visible"; | 		p.style.visibility="visible"; | ||||||
| 		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin; | 		y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin; | ||||||
| 		if(function_apply) function_apply(p); | 		if(function_apply) function_apply(p); | ||||||
|  |         //console.log(p.id, y, p.openwebrxPanelTransparent); | ||||||
| 	} | 	} | ||||||
| 	y=hoffset; | 	y=hoffset; | ||||||
| 	while(right_col.length>0) | 	while(right_col.length>0) | ||||||
| @@ -2125,3 +2409,331 @@ function progressbar_set(obj,val,text,over) | |||||||
| 	if(innerText==null) return; | 	if(innerText==null) return; | ||||||
| 	innerText.innerHTML=text; | 	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/>. |     along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| """ | """ | ||||||
| sw_version="v0.15" | sw_version="v0.17" | ||||||
| #0.15 (added nmux) | #0.15 (added nmux) | ||||||
|  |  | ||||||
| import os | import os | ||||||
| import code | import code | ||||||
| import importlib | import importlib | ||||||
| import plugins | import csdr | ||||||
| import plugins.dsp |  | ||||||
| import thread | import thread | ||||||
| import time | import time | ||||||
| import datetime | import datetime | ||||||
| @@ -63,6 +62,7 @@ try: import __pypy__ | |||||||
| except: pass | except: pass | ||||||
| pypy="__pypy__" in globals() | pypy="__pypy__" in globals() | ||||||
|  |  | ||||||
|  | """ | ||||||
| def import_all_plugins(directory): | def import_all_plugins(directory): | ||||||
|     for subdir in os.listdir(directory): |     for subdir in os.listdir(directory): | ||||||
|         if os.path.isdir(directory+subdir) and not subdir[0]=="_": |         if os.path.isdir(directory+subdir) and not subdir[0]=="_": | ||||||
| @@ -71,6 +71,7 @@ def import_all_plugins(directory): | |||||||
|                 importname=(directory+subdir+"/plugin").replace("/",".") |                 importname=(directory+subdir+"/plugin").replace("/",".") | ||||||
|                 print "[openwebrx-import] Found plugin:",importname |                 print "[openwebrx-import] Found plugin:",importname | ||||||
|                 importlib.import_module(importname) |                 importlib.import_module(importname) | ||||||
|  | """ | ||||||
|  |  | ||||||
| class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): | class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): | ||||||
|     pass |     pass | ||||||
| @@ -91,7 +92,8 @@ def handle_signal(sig, frame): | |||||||
|             print |             print | ||||||
|             for key in client._fields: |             for key in client._fields: | ||||||
|                 print "\t%s = %s"%(key,str(getattr(client,key))) |                 print "\t%s = %s"%(key,str(getattr(client,key))) | ||||||
|  |     elif sig == signal.SIGUSR2: | ||||||
|  |         code.interact(local=globals()) | ||||||
|     else: |     else: | ||||||
|         print "[openwebrx] Ctrl+C: aborting." |         print "[openwebrx] Ctrl+C: aborting." | ||||||
|         cleanup_clients(True) |         cleanup_clients(True) | ||||||
| @@ -127,9 +129,7 @@ def main(): | |||||||
|     #Set signal handler |     #Set signal handler | ||||||
|     signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python |     signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python | ||||||
|     signal.signal(signal.SIGUSR1, handle_signal) |     signal.signal(signal.SIGUSR1, handle_signal) | ||||||
|  |     signal.signal(signal.SIGUSR2, handle_signal) | ||||||
| 	#Load plugins |  | ||||||
| 	import_all_plugins("plugins/dsp/") |  | ||||||
|  |  | ||||||
|     #Pypy |     #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?)" |     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(): | def spectrum_thread_function(): | ||||||
|     global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick |     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.nc_port=cfg.iq_server_port | ||||||
|     dsp.set_demodulator("fft") |     dsp.set_demodulator("fft") | ||||||
|     dsp.set_samp_rate(cfg.samp_rate) |     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 |             # 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[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment! | ||||||
|             if self.path[:4]=="/ws/": |             if self.path[:4]=="/ws/": | ||||||
|  |                 print "[openwebrx-ws] Client requested WebSocket connection" | ||||||
|                 if receiver_failed: self.send_error(500,"Internal server error") |                 if receiver_failed: self.send_error(500,"Internal server error") | ||||||
|                 try: |                 try: | ||||||
|                     # ========= WebSocket handshake  ========= |                     # ========= 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)) |                     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 ========= |                     # ========= Initialize DSP ========= | ||||||
| 					dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() |                     dsp=csdr.dsp() | ||||||
|                     dsp_initialized=False |                     dsp_initialized=False | ||||||
|                     dsp.set_audio_compression(cfg.audio_compression) |                     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_format_conversion(cfg.format_conversion) | ||||||
|                     dsp.set_offset_freq(0) |                     dsp.set_offset_freq(0) | ||||||
|                     dsp.set_bpf(-4000,4000) |                     dsp.set_bpf(-4000,4000) | ||||||
|  |                     dsp.set_secondary_fft_size(cfg.digimodes_fft_size) | ||||||
|                     dsp.nc_port=cfg.iq_server_port |                     dsp.nc_port=cfg.iq_server_port | ||||||
|                     apply_csdr_cfg_to_dsp(dsp) |                     apply_csdr_cfg_to_dsp(dsp) | ||||||
|                     myclient.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))+")") |                     access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")") | ||||||
|  |  | ||||||
| 					myclient.loopstat=0 |  | ||||||
|  |  | ||||||
|                     while True: |                     while True: | ||||||
|  |                         myclient.loopstat=0 | ||||||
|                         if myclient.closed[0]: |                         if myclient.closed[0]: | ||||||
|                             print "[openwebrx-httpd:ws] client closed by other thread" |                             print "[openwebrx-httpd:ws] client closed by other thread" | ||||||
|                             break |                             break | ||||||
| @@ -514,12 +516,34 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                             rxws.send(self,myclient.bcastmsg) |                             rxws.send(self,myclient.bcastmsg) | ||||||
|                             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 ========= |                         # ========= process commands ========= | ||||||
|                         while True: |                         while True: | ||||||
|                             myclient.loopstat=50 |                             myclient.loopstat=50 | ||||||
|                             rdata=rxws.recv(self, False) |                             rdata=rxws.recv(self, False) | ||||||
| 							if not rdata: break |                             myclient.loopstat=51 | ||||||
|                             #try: |                             #try: | ||||||
|  |                             if not rdata: break | ||||||
|                             elif rdata[:3]=="SET": |                             elif rdata[:3]=="SET": | ||||||
|                                 print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata) |                                 print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata) | ||||||
|                                 pairs=rdata[4:].split(" ") |                                 pairs=rdata[4:].split(" ") | ||||||
| @@ -528,13 +552,13 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                                 filter_limit=dsp.get_output_rate()/2 |                                 filter_limit=dsp.get_output_rate()/2 | ||||||
|                                 for pair in pairs: |                                 for pair in pairs: | ||||||
|                                     param_name, param_value = pair.split("=") |                                     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 |                                         bpf_set=True | ||||||
|                                         new_bpf[0]=int(param_value) |                                         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 |                                         bpf_set=True | ||||||
|                                         new_bpf[1]=int(param_value) |                                         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 |                                         myclient.loopstat=510 | ||||||
|                                         dsp.set_offset_freq(int(param_value)) |                                         dsp.set_offset_freq(int(param_value)) | ||||||
|                                     elif param_name == "squelch_level" and float(param_value) >= 0: |                                     elif param_name == "squelch_level" and float(param_value) >= 0: | ||||||
| @@ -557,6 +581,19 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                                             myclient.loopstat=550 |                                             myclient.loopstat=550 | ||||||
|                                             dsp.start() |                                             dsp.start() | ||||||
|                                             dsp_initialized=True |                                             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: |                                     else: | ||||||
|                                         print "[openwebrx-httpd:ws] invalid parameter" |                                         print "[openwebrx-httpd:ws] invalid parameter" | ||||||
|                                 if bpf_set: |                                 if bpf_set: | ||||||
| @@ -564,16 +601,20 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                                     dsp.set_bpf(*new_bpf) |                                     dsp.set_bpf(*new_bpf) | ||||||
|                                 #code.interact(local=locals()) |                                 #code.interact(local=locals()) | ||||||
|                 except: |                 except: | ||||||
|  |                     myclient.loopstat=990 | ||||||
|                     exc_type, exc_value, exc_traceback = sys.exc_info() |                     exc_type, exc_value, exc_traceback = sys.exc_info() | ||||||
| 					if exc_value[0]==32: #"broken pipe", client disconnected |                     print "[openwebrx-httpd:ws] exception: ",exc_type,exc_value | ||||||
| 						pass |                     traceback.print_tb(exc_traceback) #TODO digimodes | ||||||
| 					elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected |                     #if exc_value[0]==32: #"broken pipe", client disconnected | ||||||
| 						pass |                     #    pass | ||||||
| 					else: |                     #elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected | ||||||
| 						print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value |                     #    pass | ||||||
| 						traceback.print_tb(exc_traceback) |                     #else: | ||||||
|  |                     #    print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value | ||||||
|  |                     #    traceback.print_tb(exc_traceback) | ||||||
|  |  | ||||||
|                 #stop dsp for the disconnected client |                 #stop dsp for the disconnected client | ||||||
|  |                 myclient.loopstat=991 | ||||||
|                 try: |                 try: | ||||||
|                     dsp.stop() |                     dsp.stop() | ||||||
|                     del dsp |                     del dsp | ||||||
| @@ -581,6 +622,7 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                     print "[openwebrx-httpd] error in dsp.stop()" |                     print "[openwebrx-httpd] error in dsp.stop()" | ||||||
|  |  | ||||||
|                 #delete disconnected client |                 #delete disconnected client | ||||||
|  |                 myclient.loopstat=992 | ||||||
|                 try: |                 try: | ||||||
|                     cma("do_GET /ws/ delete disconnected") |                     cma("do_GET /ws/ delete disconnected") | ||||||
|                     id_to_close=get_client_by_id(myclient.id,False) |                     id_to_close=get_client_by_id(myclient.id,False) | ||||||
| @@ -644,7 +686,11 @@ class WebRXHandler(BaseHTTPRequestHandler): | |||||||
|                         ("%[WATERFALL_COLORS]",cfg.waterfall_colors), |                         ("%[WATERFALL_COLORS]",cfg.waterfall_colors), | ||||||
|                         ("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)), |                         ("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)), | ||||||
|                         ("%[WATERFALL_MAX_LEVEL]",str(cfg.waterfall_max_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: |                     for rule in replace_dictionary: | ||||||
|                         while data.find(rule[0])!=-1: |                         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