Merged feature/digitalmods
This commit is contained in:
commit
c62f29ab5a
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.
|
||||||
|
|
||||||
![OpenWebRX](/screenshot.png?raw=true)
|
![OpenWebRX](http://blog.sdr.hu/images/openwebrx/screenshot.png)
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
![OpenWebRX 3D waterfall](http://blog.sdr.hu/images/openwebrx/screenshot-3d.gif)
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
![sdr.hu](/screenshot-sdrhu.png?raw=true)
|
![sdr.hu](http://blog.sdr.hu/images/openwebrx/screenshot-sdrhu.png)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
"""
|
"""
|
||||||
config_webrx: configuration options for OpenWebRX
|
config_webrx: configuration options for OpenWebRX
|
||||||
|
|
||||||
This file is part of OpenWebRX,
|
This file is part of OpenWebRX,
|
||||||
an open-source SDR receiver software with a web UI.
|
an open-source SDR receiver software with a web UI.
|
||||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@ -20,15 +20,15 @@ config_webrx: configuration options for OpenWebRX
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
In addition, as a special exception, the copyright holders
|
In addition, as a special exception, the copyright holders
|
||||||
state that config_rtl.py and config_webrx.py are not part of the
|
state that config_rtl.py and config_webrx.py are not part of the
|
||||||
Corresponding Source defined in GNU AGPL version 3 section 1.
|
Corresponding Source defined in GNU AGPL version 3 section 1.
|
||||||
|
|
||||||
(It means that you do not have to redistribute config_rtl.py and
|
(It means that you do not have to redistribute config_rtl.py and
|
||||||
config_webrx.py if you make any changes to these two configuration files,
|
config_webrx.py if you make any changes to these two configuration files,
|
||||||
and use them for running your web service with OpenWebRX.)
|
and use them for running your web service with OpenWebRX.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
|
# NOTE: you can find additional information about configuring OpenWebRX in the Wiki:
|
||||||
@ -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.
|
||||||
@ -182,15 +187,20 @@ waterfall_auto_level_margin = (5, 40)
|
|||||||
##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
|
##For the old colors, you might also want to set [fft_voverlap_factor] to 0.
|
||||||
|
|
||||||
#Note: When the auto waterfall level button is clicked, the following happens:
|
#Note: When the auto waterfall level button is clicked, the following happens:
|
||||||
# [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
|
# [waterfall_min_level] = [current_min_power_level] - [waterfall_auto_level_margin[0]]
|
||||||
# [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
|
# [waterfall_max_level] = [current_max_power_level] + [waterfall_auto_level_margin[1]]
|
||||||
#
|
#
|
||||||
# ___|____________________________________|____________________________________|____________________________________|___> 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 |
283
htdocs/index.wrx
283
htdocs/index.wrx
@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
This file is part of OpenWebRX,
|
This file is part of OpenWebRX,
|
||||||
an open-source SDR receiver software with a web UI.
|
an open-source SDR receiver software with a web UI.
|
||||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@ -20,128 +20,161 @@
|
|||||||
|
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
|
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//Global variables
|
//Global variables
|
||||||
var client_id="%[CLIENT_ID]";
|
var client_id="%[CLIENT_ID]";
|
||||||
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];
|
||||||
</script>
|
var server_enable_digimodes=%[DIGIMODES_ENABLE];
|
||||||
<script src="sdr.js"></script>
|
var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
|
||||||
<script src="openwebrx.js"></script>
|
var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
|
||||||
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
|
var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
|
||||||
<meta charset="utf-8">
|
</script>
|
||||||
</head>
|
<script src="sdr.js"></script>
|
||||||
<body onload="openwebrx_init();">
|
<script src="mathbox-bundle.min.js"></script>
|
||||||
|
<script src="openwebrx.js"></script>
|
||||||
|
<script src="jquery-3.2.1.min.js"></script>
|
||||||
|
<script src="jquery.nanoscroller.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="nanoscroller.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
|
||||||
|
<meta charset="utf-8">
|
||||||
|
</head>
|
||||||
|
<body onload="openwebrx_init();">
|
||||||
<div id="webrx-page-container">
|
<div id="webrx-page-container">
|
||||||
<div id="webrx-top-container">
|
<div id="webrx-top-container">
|
||||||
<div id="webrx-top-photo-clip">
|
<div id="webrx-top-photo-clip">
|
||||||
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
|
<img src="gfx/openwebrx-top-photo.jpg" id="webrx-top-photo"/>
|
||||||
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
|
<div id="webrx-rx-photo-title">%[RX_PHOTO_TITLE]</div>
|
||||||
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
|
<div id="webrx-rx-photo-desc">%[RX_PHOTO_DESC]</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
|
<div id="webrx-top-bar-background" class="webrx-top-bar-parts"></div>
|
||||||
<div id="webrx-top-bar" class="webrx-top-bar-parts">
|
<div id="webrx-top-bar" class="webrx-top-bar-parts">
|
||||||
<a href="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
|
<a href="http://openwebrx.org/" target="_blank"><img src="gfx/openwebrx-top-logo.png" id="webrx-top-logo" /></a>
|
||||||
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
|
<a href="http://ha5kfu.sch.bme.hu/" target="_blank"><img src="gfx/openwebrx-ha5kfu-top-logo.png" id="webrx-ha5kfu-top-logo" /></a>
|
||||||
<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
|
<img id="webrx-rx-avatar-background" src="gfx/openwebrx-avatar-background.png" onclick="toggle_rx_photo();"/>
|
||||||
<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
|
<img id="webrx-rx-avatar" src="gfx/openwebrx-avatar.png" onclick="toggle_rx_photo();"/>
|
||||||
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
|
<div id="webrx-rx-title" onclick="toggle_rx_photo();">%[RX_TITLE]</div>
|
||||||
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
|
<div id="webrx-rx-desc" onclick="toggle_rx_photo();">%[RX_LOC] | Loc: %[RX_QRA], ASL: %[RX_ASL] m, <a href="https://www.google.hu/maps/place/%[RX_GPS]" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a></div>
|
||||||
<div id="openwebrx-rx-details-arrow">
|
<div id="openwebrx-rx-details-arrow">
|
||||||
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
|
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
|
||||||
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
|
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
|
||||||
</div>
|
</div>
|
||||||
<section id="openwebrx-main-buttons">
|
<section id="openwebrx-main-buttons">
|
||||||
<ul>
|
<ul>
|
||||||
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
|
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
|
||||||
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
|
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
|
||||||
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
|
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="webrx-main-container">
|
<div id="webrx-main-container">
|
||||||
<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="webrx-canvas-container">
|
<div id="openwebrx-mathbox-container"> </div>
|
||||||
|
<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>
|
||||||
<div id="openwebrx-panels-container">
|
<div id="openwebrx-panels-container">
|
||||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115">
|
<div 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" id="openwebrx-button-nfm"
|
||||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div>
|
onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div>
|
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
|
||||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div>
|
onclick="demodulator_analog_replace('am');">AM</div>
|
||||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div>
|
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
|
||||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div>
|
onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||||
</div>
|
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
|
||||||
<div class="openwebrx-panel-line">
|
onclick="demodulator_analog_replace('usb');">USB</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>
|
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
|
||||||
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
|
onclick="demodulator_analog_replace('cw');">CW</div>
|
||||||
<div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div>
|
</div>
|
||||||
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
|
<div class="openwebrx-panel-line">
|
||||||
</div>
|
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
|
||||||
<div class="openwebrx-panel-line">
|
<select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
|
||||||
<div title="Auto-set squelch level" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
|
<option value="none"></option>
|
||||||
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
|
<option value="bpsk31">BPSK31</option>
|
||||||
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div>
|
</select>
|
||||||
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
|
</div>
|
||||||
</div>
|
<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 class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="gfx/openwebrx-zoom-in.png" /></div>
|
<input title="Volume" id="openwebrx-panel-volume" class="openwebrx-panel-slider" type="range" min="0" max="150" value="50" step="1" onchange="updateVolume()" oninput="updateVolume()">
|
||||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
|
<div title="Auto-adjust waterfall colors" id="openwebrx-waterfall-colors-auto" class="openwebrx-button" onclick="waterfall_measure_minmax_now=true;"><img src="gfx/openwebrx-waterfall-auto.png" class="openwebrx-sliderbtn-img"></div>
|
||||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
|
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);" oninput="updateVolume()">
|
||||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
|
</div>
|
||||||
<div id="openwebrx-smeter-db">0 dB</div>
|
<div class="openwebrx-panel-line">
|
||||||
</div>
|
<div title="Auto-set squelch level" id="openwebrx-squelch-default" class="openwebrx-button" onclick="setSquelchToAuto()"><img src="gfx/openwebrx-squelch-button.png" class="openwebrx-sliderbtn-img"></div>
|
||||||
<div class="openwebrx-panel-line">
|
<input title="Squelch" id="openwebrx-panel-squelch" class="openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1" onchange="updateSquelch()" oninput="updateSquelch()">
|
||||||
<div id="openwebrx-smeter-outer">
|
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button" onclick="waterfallColorsDefault()"><img src="gfx/openwebrx-waterfall-default.png" class="openwebrx-sliderbtn-img"></div>
|
||||||
<div id="openwebrx-smeter-bar"></div>
|
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);" oninput="updateVolume()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="openwebrx-panel-line">
|
||||||
</div>
|
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInOneStep();" title="Zoom in one step"> <img src="gfx/openwebrx-zoom-in.png" /></div>
|
||||||
<div class="openwebrx-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-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
|
||||||
<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
|
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
|
||||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
|
||||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
<div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
|
||||||
<div id="openwebrx-debugdiv"></div>
|
<div id="openwebrx-smeter-db">0 dB</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="openwebrx-panel-line">
|
||||||
<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 id="openwebrx-smeter-outer">
|
||||||
<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 id="openwebrx-smeter-bar"></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>
|
||||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
</div>
|
||||||
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
</div>
|
||||||
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
<div class="openwebrx-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-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
|
||||||
</div>
|
<div class="nano-content">
|
||||||
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;">
|
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
||||||
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
|
<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/>
|
||||||
<br />We're working on the code right now, so the application might fail.
|
<div id="openwebrx-debugdiv"></div>
|
||||||
</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-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network usage [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;">
|
||||||
|
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
|
||||||
|
<br />We're working on the code right now, so the application might fail.
|
||||||
|
</div>
|
||||||
|
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210">
|
||||||
|
<div id="openwebrx-digimode-canvas-container">
|
||||||
|
<div id="openwebrx-digimode-select-channel"></div>
|
||||||
|
</div>
|
||||||
|
<div id="openwebrx-digimode-content-container">
|
||||||
|
<div class="gradient"></div>
|
||||||
|
<div id="openwebrx-digimode-content">
|
||||||
|
<span id="openwebrx-cursor-blink"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
|
<div id="openwebrx-big-grey" onclick="iosPlayButtonClick();">
|
||||||
<div id="openwebrx-play-button-text">
|
<div id="openwebrx-play-button-text">
|
||||||
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
|
<img id="openwebrx-play-button" src="gfx/openwebrx-play-button.png" />
|
||||||
<br /><br />Start OpenWebRX
|
<br /><br />Start OpenWebRX
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
4
htdocs/jquery-3.2.1.min.js
vendored
Normal file
4
htdocs/jquery-3.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1000
htdocs/jquery.nanoscroller.js
Normal file
1000
htdocs/jquery.nanoscroller.js
Normal file
File diff suppressed because it is too large
Load Diff
33
htdocs/mathbox-bundle.min.js
vendored
Normal file
33
htdocs/mathbox-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
461
htdocs/mathbox.css
Normal file
461
htdocs/mathbox.css
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
.shadergraph-graph {
|
||||||
|
font: 12px sans-serif;
|
||||||
|
line-height: 25px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.shadergraph-graph:after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
font-size: 0;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.shadergraph-graph svg {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.shadergraph-clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.shadergraph-graph svg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.shadergraph-column {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.shadergraph-node .shadergraph-graph {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.shadergraph-node .shadergraph-graph .shadergraph-node {
|
||||||
|
margin: 5px 15px 15px;
|
||||||
|
}
|
||||||
|
.shadergraph-node {
|
||||||
|
margin: 5px 15px 25px;
|
||||||
|
background: rgba(0, 0, 0, .1);
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||||
|
0 1px 10px rgba(0, 0, 0, .2);
|
||||||
|
min-height: 35px;
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.shadergraph-type {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.shadergraph-header {
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
height: 25px;
|
||||||
|
background: rgba(0, 0, 0, .3);
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
|
||||||
|
color: #fff;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
.shadergraph-outlet div {
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-in .shadergraph-name {
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-out .shadergraph-name {
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-name {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
.shadergraph-point {
|
||||||
|
margin: 6px;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border-radius: 7.5px;
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-in {
|
||||||
|
float: left;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-in div {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-out {
|
||||||
|
float: right;
|
||||||
|
clear: right;
|
||||||
|
}
|
||||||
|
.shadergraph-outlet-out div {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-node-callback {
|
||||||
|
background: rgba(205, 209, 221, .5);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 10, 40, .2),
|
||||||
|
0 1px 10px rgba(0, 10, 40, .2);
|
||||||
|
}
|
||||||
|
.shadergraph-node-callback > .shadergraph-header {
|
||||||
|
background: rgba(0, 20, 80, .3);
|
||||||
|
}
|
||||||
|
.shadergraph-graph .shadergraph-graph .shadergraph-node-callback {
|
||||||
|
background: rgba(0, 20, 80, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-node-call {
|
||||||
|
background: rgba(209, 221, 205, .5);
|
||||||
|
box-shadow: 0 1px 2px rgba(10, 40, 0, .2),
|
||||||
|
0 1px 10px rgba(10, 40, 0, .2);
|
||||||
|
}
|
||||||
|
.shadergraph-node-call > .shadergraph-header {
|
||||||
|
background: rgba(20, 80, 0, .3);
|
||||||
|
}
|
||||||
|
.shadergraph-graph .shadergraph-graph .shadergraph-node-call {
|
||||||
|
background: rgba(20, 80, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-node-isolate {
|
||||||
|
background: rgba(221, 205, 209, .5);
|
||||||
|
box-shadow: 0 1px 2px rgba(40, 0, 10, .2),
|
||||||
|
0 1px 10px rgba(40, 0, 10, .2);
|
||||||
|
}
|
||||||
|
.shadergraph-node-isolate > .shadergraph-header {
|
||||||
|
background: rgba(80, 0, 20, .3);
|
||||||
|
}
|
||||||
|
.shadergraph-graph .shadergraph-graph .shadergraph-node-isolate {
|
||||||
|
background: rgba(80, 0, 20, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-node.shadergraph-has-code {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.shadergraph-node.shadergraph-has-code::before {
|
||||||
|
position: absolute;
|
||||||
|
content: ' ';
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: none;
|
||||||
|
border: 2px solid rgba(0, 0, 0, .25);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.shadergraph-node.shadergraph-has-code:hover::before {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.shadergraph-code {
|
||||||
|
z-index: 10000;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
white-space: pre;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||||
|
0 1px 10px rgba(0, 0, 0, .2);
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadergraph-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #CCC;
|
||||||
|
}
|
||||||
|
.shadergraph-overlay .shadergraph-view {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.shadergraph-overlay .shadergraph-inside {
|
||||||
|
width: 4000px;
|
||||||
|
min-height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.shadergraph-overlay .shadergraph-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(255,255,255,.3);
|
||||||
|
color: rgba(0, 0, 0, .3);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.shadergraph-overlay .shadergraph-close:hover {
|
||||||
|
background: rgba(255,255,255,1);
|
||||||
|
color: rgba(0, 0, 0, 1);
|
||||||
|
}
|
||||||
|
.shadergraph-overlay .shadergraph-graph {
|
||||||
|
padding-top: 10px;
|
||||||
|
overflow: visible;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
.shadergraph-overlay span {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 15px;
|
||||||
|
margin: 0;
|
||||||
|
background: rgba(0, 0, 0, .1);
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.mathbox-loader {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-loader.mathbox-exit {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition:
|
||||||
|
opacity .15s ease-in-out;
|
||||||
|
transition:
|
||||||
|
opacity .15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-progress {
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 80px;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
box-shadow:
|
||||||
|
1px 1px 1px rgba(255, 255, 255, .2),
|
||||||
|
1px -1px 1px rgba(255, 255, 255, .2),
|
||||||
|
-1px 1px 1px rgba(255, 255, 255, .2),
|
||||||
|
-1px -1px 1px rgba(255, 255, 255, .2);
|
||||||
|
background: #ccc;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-progress > div {
|
||||||
|
display: block;
|
||||||
|
width: 0px;
|
||||||
|
height: 10px;
|
||||||
|
background: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo {
|
||||||
|
position: relative;
|
||||||
|
width: 140px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
-webkit-perspective: 200px;
|
||||||
|
perspective: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > div {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
-webkit-transform-style: preserve-3d;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > :nth-child(1) {
|
||||||
|
-webkit-transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||||
|
transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > :nth-child(2) {
|
||||||
|
-webkit-transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||||
|
transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > div > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -100px;
|
||||||
|
margin-top: -100px;
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > div > :nth-child(1) {
|
||||||
|
-webkit-transform: scale(0.5, 0.5);
|
||||||
|
transform: rotateX(30deg) scale(0.5, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > div > :nth-child(2) {
|
||||||
|
-webkit-transform: rotateX(90deg) scale(0.42, 0.42);
|
||||||
|
transform: rotateX(90deg) scale(0.42, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > div > :nth-child(3) {
|
||||||
|
-webkit-transform: rotateY(90deg) scale(0.35, 0.35);
|
||||||
|
transform: rotateY(90deg) scale(0.35, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||||
|
border: 16px solid #808080;
|
||||||
|
}
|
||||||
|
.mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||||
|
border: 19px solid #A0A0A0;
|
||||||
|
}
|
||||||
|
.mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||||
|
border: 23px solid #C0C0C0;
|
||||||
|
}
|
||||||
|
.mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||||
|
border: 27px solid #808080;
|
||||||
|
}
|
||||||
|
.mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||||
|
border: 32px solid #A0A0A0;
|
||||||
|
}
|
||||||
|
.mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||||
|
border: 38px solid #C0C0C0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathbox-splash-blue .mathbox-progress {
|
||||||
|
background: #def;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-progress > div {
|
||||||
|
background: #1979e7;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||||
|
border-color: #1979e7;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||||
|
border-color: #33b0ff;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||||
|
border-color: #75eaff;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||||
|
border-color: #18487F;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||||
|
border-color: #33b0ff;
|
||||||
|
}
|
||||||
|
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||||
|
border-color: #75eaff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.mathbox-overlays {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.mathbox-overlays > div {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
.mathbox-overlay > div {
|
||||||
|
position: absolute;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
.mathbox-label {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.mathbox-outline-1 {
|
||||||
|
text-shadow:
|
||||||
|
-1px -1px 0px rgb(255, 255, 255),
|
||||||
|
1px 1px 0px rgb(255, 255, 255),
|
||||||
|
-1px 1px 0px rgb(255, 255, 255),
|
||||||
|
1px -1px 0px rgb(255, 255, 255),
|
||||||
|
1px 0px 1px rgb(255, 255, 255),
|
||||||
|
-1px 0px 1px rgb(255, 255, 255),
|
||||||
|
0px -1px 1px rgb(255, 255, 255),
|
||||||
|
0px 1px 1px rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
.mathbox-outline-2 {
|
||||||
|
text-shadow:
|
||||||
|
0px -2px 0px rgb(255, 255, 255),
|
||||||
|
0px 2px 0px rgb(255, 255, 255),
|
||||||
|
-2px 0px 0px rgb(255, 255, 255),
|
||||||
|
2px 0px 0px rgb(255, 255, 255),
|
||||||
|
-1px -2px 0px rgb(255, 255, 255),
|
||||||
|
-2px -1px 0px rgb(255, 255, 255),
|
||||||
|
-1px 2px 0px rgb(255, 255, 255),
|
||||||
|
-2px 1px 0px rgb(255, 255, 255),
|
||||||
|
1px 2px 0px rgb(255, 255, 255),
|
||||||
|
2px 1px 0px rgb(255, 255, 255),
|
||||||
|
1px -2px 0px rgb(255, 255, 255),
|
||||||
|
2px -1px 0px rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
.mathbox-outline-3 {
|
||||||
|
text-shadow:
|
||||||
|
3px 0px 0px rgb(255, 255, 255),
|
||||||
|
-3px 0px 0px rgb(255, 255, 255),
|
||||||
|
0px 3px 0px rgb(255, 255, 255),
|
||||||
|
0px -3px 0px rgb(255, 255, 255),
|
||||||
|
|
||||||
|
-2px -2px 0px rgb(255, 255, 255),
|
||||||
|
-2px 2px 0px rgb(255, 255, 255),
|
||||||
|
2px 2px 0px rgb(255, 255, 255),
|
||||||
|
2px -2px 0px rgb(255, 255, 255),
|
||||||
|
|
||||||
|
-1px -2px 1px rgb(255, 255, 255),
|
||||||
|
-2px -1px 1px rgb(255, 255, 255),
|
||||||
|
-1px 2px 1px rgb(255, 255, 255),
|
||||||
|
-2px 1px 1px rgb(255, 255, 255),
|
||||||
|
1px 2px 1px rgb(255, 255, 255),
|
||||||
|
2px 1px 1px rgb(255, 255, 255),
|
||||||
|
1px -2px 1px rgb(255, 255, 255),
|
||||||
|
2px -1px 1px rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
.mathbox-outline-4 {
|
||||||
|
text-shadow:
|
||||||
|
4px 0px 0px rgb(255, 255, 255),
|
||||||
|
-4px 0px 0px rgb(255, 255, 255),
|
||||||
|
0px 4px 0px rgb(255, 255, 255),
|
||||||
|
0px -4px 0px rgb(255, 255, 255),
|
||||||
|
|
||||||
|
-3px -2px 0px rgb(255, 255, 255),
|
||||||
|
-3px 2px 0px rgb(255, 255, 255),
|
||||||
|
3px 2px 0px rgb(255, 255, 255),
|
||||||
|
3px -2px 0px rgb(255, 255, 255),
|
||||||
|
|
||||||
|
-2px -3px 0px rgb(255, 255, 255),
|
||||||
|
-2px 3px 0px rgb(255, 255, 255),
|
||||||
|
2px 3px 0px rgb(255, 255, 255),
|
||||||
|
2px -3px 0px rgb(255, 255, 255),
|
||||||
|
|
||||||
|
-1px -2px 1px rgb(255, 255, 255),
|
||||||
|
-2px -1px 1px rgb(255, 255, 255),
|
||||||
|
-1px 2px 1px rgb(255, 255, 255),
|
||||||
|
-2px 1px 1px rgb(255, 255, 255),
|
||||||
|
1px 2px 1px rgb(255, 255, 255),
|
||||||
|
2px 1px 1px rgb(255, 255, 255),
|
||||||
|
1px -2px 1px rgb(255, 255, 255),
|
||||||
|
2px -1px 1px rgb(255, 255, 255);
|
||||||
|
|
||||||
|
}
|
||||||
|
.mathbox-outline-fill, .mathbox-outline-fill * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
55
htdocs/nanoscroller.css
Normal file
55
htdocs/nanoscroller.css
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/** initial setup **/
|
||||||
|
.nano {
|
||||||
|
position : relative;
|
||||||
|
width : 100%;
|
||||||
|
height : 100%;
|
||||||
|
overflow : hidden;
|
||||||
|
}
|
||||||
|
.nano > .nano-content {
|
||||||
|
position : absolute;
|
||||||
|
overflow : scroll;
|
||||||
|
overflow-x : hidden;
|
||||||
|
top : 0;
|
||||||
|
right : 0;
|
||||||
|
bottom : 0;
|
||||||
|
left : 0;
|
||||||
|
}
|
||||||
|
.nano > .nano-content:focus {
|
||||||
|
outline: thin dotted;
|
||||||
|
}
|
||||||
|
.nano > .nano-content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.has-scrollbar > .nano-content::-webkit-scrollbar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.nano > .nano-pane {
|
||||||
|
background : rgba(0,0,0,.25);
|
||||||
|
position : absolute;
|
||||||
|
width : 8px;
|
||||||
|
right : 0;
|
||||||
|
top : 0;
|
||||||
|
bottom : 0;
|
||||||
|
visibility : hidden\9; /* Target only IE7 and IE8 with this hack */
|
||||||
|
opacity : .01;
|
||||||
|
-webkit-transition : .2s;
|
||||||
|
-moz-transition : .2s;
|
||||||
|
-o-transition : .2s;
|
||||||
|
transition : .2s;
|
||||||
|
-moz-border-radius : 3px;
|
||||||
|
-webkit-border-radius : 3px;
|
||||||
|
border-radius : 3px;
|
||||||
|
}
|
||||||
|
.nano > .nano-pane > .nano-slider {
|
||||||
|
background: #444;
|
||||||
|
background: rgba(0,0,0,.5);
|
||||||
|
position : relative;
|
||||||
|
margin : 0 0px;
|
||||||
|
-moz-border-radius : 4px;
|
||||||
|
-webkit-border-radius : 4px;
|
||||||
|
border-radius : 4px;
|
||||||
|
}
|
||||||
|
.nano:hover > .nano-pane, .nano-pane.active, .nano-pane.flashed {
|
||||||
|
visibility : visible\9; /* Target only IE7 and IE8 with this hack */
|
||||||
|
opacity : 0.99;
|
||||||
|
}
|
@ -28,6 +28,11 @@ html, body
|
|||||||
overflow: hidden;
|
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
|
||||||
{*/
|
{*/
|
||||||
@ -1180,6 +1211,18 @@ function on_ws_recv(evt)
|
|||||||
case "fft_size":
|
case "fft_size":
|
||||||
fft_size=parseInt(param[1]);
|
fft_size=parseInt(param[1]);
|
||||||
break;
|
break;
|
||||||
|
case "secondary_fft_size":
|
||||||
|
secondary_fft_size=parseInt(param[1]);
|
||||||
|
break;
|
||||||
|
case "secondary_setup":
|
||||||
|
secondary_demod_init_canvases();
|
||||||
|
break;
|
||||||
|
case "if_samp_rate":
|
||||||
|
if_samp_rate=parseInt(param[1]);
|
||||||
|
break;
|
||||||
|
case "secondary_bw":
|
||||||
|
secondary_bw=parseFloat(param[1]);
|
||||||
|
break;
|
||||||
case "fft_fps":
|
case "fft_fps":
|
||||||
fft_fps=parseInt(param[1]);
|
fft_fps=parseInt(param[1]);
|
||||||
break;
|
break;
|
||||||
@ -1279,8 +1322,10 @@ function divlog(what, is_error)
|
|||||||
if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
|
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,19 +1561,27 @@ 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")
|
||||||
console.log(parseInt(harr[1]));
|
{
|
||||||
console.log(center_freq);
|
e("openwebrx-panel-squelch").value=harr[1];
|
||||||
starting_offset_frequency = parseInt(harr[1])-center_freq;
|
updateSquelch();
|
||||||
|
}
|
||||||
|
else if(harr[0]=="freq")
|
||||||
|
{
|
||||||
|
console.log(parseInt(harr[1]));
|
||||||
|
console.log(center_freq);
|
||||||
|
starting_offset_frequency = parseInt(harr[1])-center_freq;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1569,6 +1622,8 @@ function audio_preinit()
|
|||||||
|
|
||||||
function audio_init()
|
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)
|
||||||
@ -2101,7 +2385,7 @@ function place_panels(function_apply)
|
|||||||
p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
|
p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
|
||||||
p.style.bottom=y.toString()+"px";
|
p.style.bottom=y.toString()+"px";
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
1132
openwebrx.py
1132
openwebrx.py
File diff suppressed because it is too large
Load Diff
@ -1,251 +0,0 @@
|
|||||||
"""
|
|
||||||
OpenWebRX csdr plugin: do the signal processing with csdr
|
|
||||||
|
|
||||||
This file is part of OpenWebRX,
|
|
||||||
an open-source SDR receiver software with a web UI.
|
|
||||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import code
|
|
||||||
import signal
|
|
||||||
import fcntl
|
|
||||||
|
|
||||||
class dsp_plugin:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.samp_rate = 250000
|
|
||||||
self.output_rate = 11025 #this is default, and cannot be set at the moment
|
|
||||||
self.fft_size = 1024
|
|
||||||
self.fft_fps = 5
|
|
||||||
self.offset_freq = 0
|
|
||||||
self.low_cut = -4000
|
|
||||||
self.high_cut = 4000
|
|
||||||
self.bpf_transition_bw = 320 #Hz, and this is a constant
|
|
||||||
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
|
|
||||||
self.running = False
|
|
||||||
self.audio_compression = "none"
|
|
||||||
self.fft_compression = "none"
|
|
||||||
self.demodulator = "nfm"
|
|
||||||
self.name = "csdr"
|
|
||||||
self.format_conversion = "csdr convert_u8_f"
|
|
||||||
self.base_bufsize = 512
|
|
||||||
self.nc_port = 4951
|
|
||||||
self.csdr_dynamic_bufsize = False
|
|
||||||
self.csdr_print_bufsizes = False
|
|
||||||
self.csdr_through = False
|
|
||||||
self.squelch_level = 0
|
|
||||||
self.fft_averages = 50
|
|
||||||
|
|
||||||
def chain(self,which):
|
|
||||||
any_chain_base="nc -v 127.0.0.1 {nc_port} | "
|
|
||||||
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
|
|
||||||
if self.csdr_through: any_chain_base+="csdr through | "
|
|
||||||
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
|
|
||||||
if which == "fft":
|
|
||||||
fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | " + \
|
|
||||||
("csdr logpower_cf -70 | " if self.fft_averages == 0 else "csdr logaveragepower_cf -70 {fft_size} {fft_averages} | ") + \
|
|
||||||
"csdr fft_exchange_sides_ff {fft_size}"
|
|
||||||
if self.fft_compression=="adpcm":
|
|
||||||
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
|
|
||||||
else:
|
|
||||||
return fft_chain_base
|
|
||||||
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
|
|
||||||
chain_end = ""
|
|
||||||
if self.audio_compression=="adpcm":
|
|
||||||
chain_end = " | csdr encode_ima_adpcm_i16_u8"
|
|
||||||
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff 1024 | csdr convert_f_s16"+chain_end
|
|
||||||
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
|
||||||
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
|
||||||
|
|
||||||
def set_audio_compression(self,what):
|
|
||||||
self.audio_compression = what
|
|
||||||
|
|
||||||
def set_fft_compression(self,what):
|
|
||||||
self.fft_compression = what
|
|
||||||
|
|
||||||
def get_fft_bytes_to_read(self):
|
|
||||||
if self.fft_compression=="none": return self.fft_size*4
|
|
||||||
if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
|
|
||||||
|
|
||||||
def set_samp_rate(self,samp_rate):
|
|
||||||
#to change this, restart is required
|
|
||||||
self.samp_rate=samp_rate
|
|
||||||
self.decimation=1
|
|
||||||
while self.samp_rate/(self.decimation+1)>self.output_rate:
|
|
||||||
self.decimation+=1
|
|
||||||
self.last_decimation=float(self.if_samp_rate())/self.output_rate
|
|
||||||
|
|
||||||
def if_samp_rate(self):
|
|
||||||
return self.samp_rate/self.decimation
|
|
||||||
|
|
||||||
def get_name(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_output_rate(self):
|
|
||||||
return self.output_rate
|
|
||||||
|
|
||||||
def set_output_rate(self,output_rate):
|
|
||||||
self.output_rate=output_rate
|
|
||||||
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
|
|
||||||
|
|
||||||
def set_demodulator(self,demodulator):
|
|
||||||
#to change this, restart is required
|
|
||||||
self.demodulator=demodulator
|
|
||||||
|
|
||||||
def get_demodulator(self):
|
|
||||||
return self.demodulator
|
|
||||||
|
|
||||||
def set_fft_size(self,fft_size):
|
|
||||||
#to change this, restart is required
|
|
||||||
self.fft_size=fft_size
|
|
||||||
|
|
||||||
def set_fft_fps(self,fft_fps):
|
|
||||||
#to change this, restart is required
|
|
||||||
self.fft_fps=fft_fps
|
|
||||||
|
|
||||||
def set_fft_averages(self,fft_averages):
|
|
||||||
#to change this, restart is required
|
|
||||||
self.fft_averages=fft_averages
|
|
||||||
|
|
||||||
def fft_block_size(self):
|
|
||||||
if self.fft_averages == 0: return self.samp_rate/self.fft_fps
|
|
||||||
else: return self.samp_rate/self.fft_fps/self.fft_averages
|
|
||||||
|
|
||||||
def set_format_conversion(self,format_conversion):
|
|
||||||
self.format_conversion=format_conversion
|
|
||||||
|
|
||||||
def set_offset_freq(self,offset_freq):
|
|
||||||
self.offset_freq=offset_freq
|
|
||||||
if self.running:
|
|
||||||
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
|
|
||||||
self.shift_pipe_file.flush()
|
|
||||||
|
|
||||||
def set_bpf(self,low_cut,high_cut):
|
|
||||||
self.low_cut=low_cut
|
|
||||||
self.high_cut=high_cut
|
|
||||||
if self.running:
|
|
||||||
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
|
|
||||||
self.bpf_pipe_file.flush()
|
|
||||||
|
|
||||||
def get_bpf(self):
|
|
||||||
return [self.low_cut, self.high_cut]
|
|
||||||
|
|
||||||
def set_squelch_level(self, squelch_level):
|
|
||||||
self.squelch_level=squelch_level
|
|
||||||
if self.running:
|
|
||||||
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
|
|
||||||
self.squelch_pipe_file.flush()
|
|
||||||
|
|
||||||
def get_smeter_level(self):
|
|
||||||
if self.running:
|
|
||||||
line=self.smeter_pipe_file.readline()
|
|
||||||
return float(line[:-1])
|
|
||||||
|
|
||||||
def mkfifo(self,path):
|
|
||||||
try:
|
|
||||||
os.unlink(path)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
os.mkfifo(path)
|
|
||||||
|
|
||||||
def ddc_transition_bw(self):
|
|
||||||
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
command_base=self.chain(self.demodulator)
|
|
||||||
|
|
||||||
#create control pipes for csdr
|
|
||||||
pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
|
|
||||||
self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
|
|
||||||
if "{bpf_pipe}" in command_base:
|
|
||||||
self.bpf_pipe=pipe_base_path+"bpf"
|
|
||||||
self.mkfifo(self.bpf_pipe)
|
|
||||||
if "{shift_pipe}" in command_base:
|
|
||||||
self.shift_pipe=pipe_base_path+"shift"
|
|
||||||
self.mkfifo(self.shift_pipe)
|
|
||||||
if "{squelch_pipe}" in command_base:
|
|
||||||
self.squelch_pipe=pipe_base_path+"squelch"
|
|
||||||
self.mkfifo(self.squelch_pipe)
|
|
||||||
if "{smeter_pipe}" in command_base:
|
|
||||||
self.smeter_pipe=pipe_base_path+"smeter"
|
|
||||||
self.mkfifo(self.smeter_pipe)
|
|
||||||
|
|
||||||
#run the command
|
|
||||||
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
|
|
||||||
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), fft_averages=self.fft_averages, \
|
|
||||||
bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \
|
|
||||||
flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \
|
|
||||||
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe )
|
|
||||||
|
|
||||||
print "[openwebrx-dsp-plugin:csdr] Command =",command
|
|
||||||
#code.interact(local=locals())
|
|
||||||
my_env=os.environ.copy()
|
|
||||||
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
|
||||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
|
||||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
|
||||||
self.running = True
|
|
||||||
|
|
||||||
#open control pipes for csdr and send initialization data
|
|
||||||
if self.bpf_pipe != None:
|
|
||||||
self.bpf_pipe_file=open(self.bpf_pipe,"w")
|
|
||||||
self.set_bpf(self.low_cut,self.high_cut)
|
|
||||||
if self.shift_pipe != None:
|
|
||||||
self.shift_pipe_file=open(self.shift_pipe,"w")
|
|
||||||
self.set_offset_freq(self.offset_freq)
|
|
||||||
if self.squelch_pipe != None:
|
|
||||||
self.squelch_pipe_file=open(self.squelch_pipe,"w")
|
|
||||||
self.set_squelch_level(self.squelch_level)
|
|
||||||
if self.smeter_pipe != None:
|
|
||||||
self.smeter_pipe_file=open(self.smeter_pipe,"r")
|
|
||||||
fcntl.fcntl(self.smeter_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
|
|
||||||
|
|
||||||
def read(self,size):
|
|
||||||
return self.process.stdout.read(size)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
|
||||||
#if(self.process.poll()!=None):return # returns None while subprocess is running
|
|
||||||
#while(self.process.poll()==None):
|
|
||||||
# #self.process.kill()
|
|
||||||
# print "killproc",os.getpgid(self.process.pid),self.process.pid
|
|
||||||
# os.killpg(self.process.pid, signal.SIGTERM)
|
|
||||||
#
|
|
||||||
# time.sleep(0.1)
|
|
||||||
if self.bpf_pipe:
|
|
||||||
try: os.unlink(self.bpf_pipe)
|
|
||||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
|
|
||||||
if self.shift_pipe:
|
|
||||||
try: os.unlink(self.shift_pipe)
|
|
||||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
|
|
||||||
if self.squelch_pipe:
|
|
||||||
try: os.unlink(self.squelch_pipe)
|
|
||||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
|
|
||||||
if self.smeter_pipe:
|
|
||||||
try: os.unlink(self.smeter_pipe)
|
|
||||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
|
|
||||||
self.running = False
|
|
||||||
|
|
||||||
def restart(self):
|
|
||||||
self.stop()
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self.stop()
|
|
||||||
del(self.process)
|
|
230
rxws.py
230
rxws.py
@ -1,9 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
rxws: WebSocket methods implemented for OpenWebRX
|
rxws: WebSocket methods implemented for OpenWebRX
|
||||||
|
|
||||||
This file is part of OpenWebRX,
|
This file is part of OpenWebRX,
|
||||||
an open-source SDR receiver software with a web UI.
|
an open-source SDR receiver software with a web UI.
|
||||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@ -26,110 +26,110 @@ import select
|
|||||||
import code
|
import code
|
||||||
|
|
||||||
class WebSocketException(Exception):
|
class WebSocketException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handshake(myself):
|
def handshake(myself):
|
||||||
my_client_id=myself.path[4:]
|
my_client_id=myself.path[4:]
|
||||||
my_headers=myself.headers.items()
|
my_headers=myself.headers.items()
|
||||||
my_header_keys=map(lambda x:x[0],my_headers)
|
my_header_keys=map(lambda x:x[0],my_headers)
|
||||||
h_key_exists=lambda x:my_header_keys.count(x)
|
h_key_exists=lambda x:my_header_keys.count(x)
|
||||||
h_value=lambda x:my_headers[my_header_keys.index(x)][1]
|
h_value=lambda x:my_headers[my_header_keys.index(x)][1]
|
||||||
#print "The Lambdas(tm)"
|
#print "The Lambdas(tm)"
|
||||||
#print h_key_exists("upgrade")
|
#print h_key_exists("upgrade")
|
||||||
#print h_value("upgrade")
|
#print h_value("upgrade")
|
||||||
#print h_key_exists("sec-websocket-key")
|
#print h_key_exists("sec-websocket-key")
|
||||||
if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
|
if (not h_key_exists("upgrade")) or not (h_value("upgrade")=="websocket") or (not h_key_exists("sec-websocket-key")):
|
||||||
raise WebSocketException
|
raise WebSocketException
|
||||||
ws_key=h_value("sec-websocket-key")
|
ws_key=h_value("sec-websocket-key")
|
||||||
ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
|
ws_key_toreturn=base64.b64encode(sha.new(ws_key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest())
|
||||||
#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
|
#A sample list of keys we get: [('origin', 'http://localhost:8073'), ('upgrade', 'websocket'), ('sec-websocket-extensions', 'x-webkit-deflate-frame'), ('sec-websocket-version', '13'), ('host', 'localhost:8073'), ('sec-websocket-key', 't9J1rgy4fc9fg2Hshhnkmg=='), ('connection', 'Upgrade'), ('pragma', 'no-cache'), ('cache-control', 'no-cache')]
|
||||||
myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
|
myself.wfile.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+ws_key_toreturn+"\r\nCQ-CQ-de: HA5KFU\r\n\r\n")
|
||||||
|
|
||||||
def get_header(size):
|
def get_header(size):
|
||||||
#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
|
#this does something similar: https://github.com/lemmingzshadow/php-websocket/blob/master/server/lib/WebSocket/Connection.php
|
||||||
ws_first_byte=0b10000010 # FIN=1, OP=2
|
ws_first_byte=0b10000010 # FIN=1, OP=2
|
||||||
if(size>125):
|
if(size>125):
|
||||||
ws_second_byte=126 # The following two bytes will indicate frame size
|
ws_second_byte=126 # The following two bytes will indicate frame size
|
||||||
extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
|
extended_size=chr((size>>8)&0xff)+chr(size&0xff) #Okay, it uses reverse byte order (little-endian) compared to anything else sent on TCP
|
||||||
else:
|
else:
|
||||||
ws_second_byte=size
|
ws_second_byte=size
|
||||||
#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
|
#256 bytes binary message in a single unmasked frame | 0x82 0x7E 0x0100 [256 bytes of binary data]
|
||||||
extended_size=""
|
extended_size=""
|
||||||
return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
|
return chr(ws_first_byte)+chr(ws_second_byte)+extended_size
|
||||||
|
|
||||||
def code_payload(data, masking_key=""):
|
def code_payload(data, masking_key=""):
|
||||||
# both encode or decode
|
# both encode or decode
|
||||||
if masking_key=="":
|
if masking_key=="":
|
||||||
key = (61, 84, 35, 6)
|
key = (61, 84, 35, 6)
|
||||||
else:
|
else:
|
||||||
key = [ord(i) for i in masking_key]
|
key = [ord(i) for i in masking_key]
|
||||||
encoded=""
|
encoded=""
|
||||||
for i in range(0,len(data)):
|
for i in range(0,len(data)):
|
||||||
encoded+=chr(ord(data[i])^key[i%4])
|
encoded+=chr(ord(data[i])^key[i%4])
|
||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
def xxdg(data):
|
def xxdg(data):
|
||||||
output=""
|
output=""
|
||||||
for i in range(0,len(data)/8):
|
for i in range(0,len(data)/8):
|
||||||
output+=xxd(data[i:i+8])
|
output+=xxd(data[i:i+8])
|
||||||
if i%2: output+="\n"
|
if i%2: output+="\n"
|
||||||
else: output+=" "
|
else: output+=" "
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def xxd(data):
|
def xxd(data):
|
||||||
#diagnostic purposes only
|
#diagnostic purposes only
|
||||||
output=""
|
output=""
|
||||||
for d in data:
|
for d in data:
|
||||||
output+=hex(ord(d))[2:].zfill(2)+" "
|
output+=hex(ord(d))[2:].zfill(2)+" "
|
||||||
return output
|
return output
|
||||||
|
|
||||||
#for R/W the WebSocket, use recv/send
|
#for R/W the WebSocket, use recv/send
|
||||||
#for reading the TCP socket, use readsock
|
#for reading the TCP socket, use readsock
|
||||||
#for writing the TCP socket, use myself.wfile.write and flush
|
#for writing the TCP socket, use myself.wfile.write and flush
|
||||||
|
|
||||||
def readsock(myself,size,blocking):
|
def readsock(myself,size,blocking):
|
||||||
#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
|
#http://thenestofheliopolis.blogspot.hu/2011/01/how-to-implement-non-blocking-two-way.html
|
||||||
if blocking:
|
if blocking:
|
||||||
return myself.rfile.read(size)
|
return myself.rfile.read(size)
|
||||||
else:
|
else:
|
||||||
poll = select.poll()
|
poll = select.poll()
|
||||||
poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
|
poll.register(myself.rfile.fileno(), select.POLLIN or select.POLLPRI)
|
||||||
fd = poll.poll(0) #timeout is 0
|
fd = poll.poll(0) #timeout is 0
|
||||||
if len(fd):
|
if len(fd):
|
||||||
f = fd[0]
|
f = fd[0]
|
||||||
if f[1] > 0:
|
if f[1] > 0:
|
||||||
return myself.rfile.read(size)
|
return myself.rfile.read(size)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def recv(myself, blocking=False, debug=False):
|
def recv(myself, blocking=False, debug=False):
|
||||||
bufsize=70000
|
bufsize=70000
|
||||||
#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
|
#myself.connection.setblocking(blocking) #umm... we cannot do that with rfile
|
||||||
if debug: print "ws_recv begin"
|
if debug: print "ws_recv begin"
|
||||||
try:
|
try:
|
||||||
data=readsock(myself,6,blocking)
|
data=readsock(myself,6,blocking)
|
||||||
#print "rxws.recv bytes:",xxd(data)
|
#print "rxws.recv bytes:",xxd(data)
|
||||||
except:
|
except:
|
||||||
if debug: print "ws_recv error"
|
if debug: print "ws_recv error"
|
||||||
return ""
|
return ""
|
||||||
if debug: print "ws_recv recved"
|
if debug: print "ws_recv recved"
|
||||||
if(len(data)==0): return ""
|
if(len(data)==0): return ""
|
||||||
fin=ord(data[0])&128!=0
|
fin=ord(data[0])&128!=0
|
||||||
is_text_frame=ord(data[0])&15==1
|
is_text_frame=ord(data[0])&15==1
|
||||||
length=ord(data[1])&0x7f
|
length=ord(data[1])&0x7f
|
||||||
data+=readsock(myself,length,blocking)
|
data+=readsock(myself,length,blocking)
|
||||||
#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
|
#print "rxws.recv length is ",length," (multiple packets together?) len(data) =",len(data)
|
||||||
has_one_byte_length=length<125
|
has_one_byte_length=length<125
|
||||||
masked=ord(data[1])&0x80!=0
|
masked=ord(data[1])&0x80!=0
|
||||||
#print "len=", length, len(data)-2
|
#print "len=", length, len(data)-2
|
||||||
#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
|
#print "fin, is_text_frame, has_one_byte_length, masked = ", (fin, is_text_frame, has_one_byte_length, masked)
|
||||||
#print xxd(data)
|
#print xxd(data)
|
||||||
if fin and is_text_frame and has_one_byte_length:
|
if fin and is_text_frame and has_one_byte_length:
|
||||||
if masked:
|
if masked:
|
||||||
return code_payload(data[6:], data[2:6])
|
return code_payload(data[6:], data[2:6])
|
||||||
else:
|
else:
|
||||||
return data[2:]
|
return data[2:]
|
||||||
|
|
||||||
#Useful links for ideas on WebSockets:
|
#Useful links for ideas on WebSockets:
|
||||||
# http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
|
# http://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
|
||||||
@ -138,34 +138,34 @@ def recv(myself, blocking=False, debug=False):
|
|||||||
|
|
||||||
|
|
||||||
def flush(myself):
|
def flush(myself):
|
||||||
myself.wfile.flush()
|
myself.wfile.flush()
|
||||||
#or the socket, not the rfile:
|
#or the socket, not the rfile:
|
||||||
#lR,lW,lX = select.select([],[myself.connection,],[],60)
|
#lR,lW,lX = select.select([],[myself.connection,],[],60)
|
||||||
|
|
||||||
|
|
||||||
def send(myself, data, begin_id="", debug=0):
|
def send(myself, data, begin_id="", debug=0):
|
||||||
base_frame_size=35000 #could guess by MTU?
|
base_frame_size=35000 #could guess by MTU?
|
||||||
debug=0
|
debug=0
|
||||||
#try:
|
#try:
|
||||||
while True:
|
while True:
|
||||||
counter=0
|
counter=0
|
||||||
from_end=len(data)-counter
|
from_end=len(data)-counter
|
||||||
if from_end+len(begin_id)>base_frame_size:
|
if from_end+len(begin_id)>base_frame_size:
|
||||||
data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
|
data_to_send=begin_id+data[counter:counter+base_frame_size-len(begin_id)]
|
||||||
header=get_header(len(data_to_send))
|
header=get_header(len(data_to_send))
|
||||||
flush(myself)
|
flush(myself)
|
||||||
myself.wfile.write(header+data_to_send)
|
myself.wfile.write(header+data_to_send)
|
||||||
flush(myself)
|
flush(myself)
|
||||||
if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
|
if debug: print "rxws.send ==================== #1 if branch :: from={0} to={1} dlen={2} hlen={3}".format(counter,counter+base_frame_size-len(begin_id),len(data_to_send),len(header))
|
||||||
else:
|
else:
|
||||||
data_to_send=begin_id+data[counter:]
|
data_to_send=begin_id+data[counter:]
|
||||||
header=get_header(len(data_to_send))
|
header=get_header(len(data_to_send))
|
||||||
flush(myself)
|
flush(myself)
|
||||||
myself.wfile.write(header+data_to_send)
|
myself.wfile.write(header+data_to_send)
|
||||||
flush(myself)
|
flush(myself)
|
||||||
if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
|
if debug: print "rxws.send :: #2 else branch :: dlen={0} hlen={1}".format(len(data_to_send),len(header))
|
||||||
#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
|
#if debug: print "header:\n"+xxdg(header)+"\n\nws data:\n"+xxdg(data_to_send)
|
||||||
break
|
break
|
||||||
counter+=base_frame_size-len(begin_id)
|
counter+=base_frame_size-len(begin_id)
|
||||||
#except:
|
#except:
|
||||||
# pass
|
# pass
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 511 KiB |
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB |
51
sdrhu.py
51
sdrhu.py
@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/python2
|
#!/usr/bin/python2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
This file is part of OpenWebRX,
|
This file is part of OpenWebRX,
|
||||||
an open-source SDR receiver software with a web UI.
|
an open-source SDR receiver software with a web UI.
|
||||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Affero General Public License as
|
it under the terms of the GNU Affero General Public License as
|
||||||
@ -23,29 +23,28 @@
|
|||||||
import config_webrx as cfg, time, subprocess
|
import config_webrx as cfg, time, subprocess
|
||||||
|
|
||||||
def run(continuously=True):
|
def run(continuously=True):
|
||||||
if not cfg.sdrhu_key: return
|
if not cfg.sdrhu_key: return
|
||||||
firsttime="(Your receiver is soon getting listed on sdr.hu!)"
|
firsttime="(Your receiver is soon getting listed on sdr.hu!)"
|
||||||
while True:
|
while True:
|
||||||
cmd = "wget --timeout=15 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
|
cmd = "wget --timeout=15 -qO- http://sdr.hu/update --post-data \"url=http://"+cfg.server_hostname+":"+str(cfg.web_port)+"&apikey="+cfg.sdrhu_key+"\" 2>&1"
|
||||||
#print "[openwebrx-sdrhu]", cmd
|
#print "[openwebrx-sdrhu]", cmd
|
||||||
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
|
returned=subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).communicate()
|
||||||
returned=returned[0]
|
returned=returned[0]
|
||||||
#print returned
|
#print returned
|
||||||
if "UPDATE:" in returned:
|
if "UPDATE:" in returned:
|
||||||
retrytime_mins = 20
|
retrytime_mins = 20
|
||||||
value=returned.split("UPDATE:")[1].split("\n",1)[0]
|
value=returned.split("UPDATE:")[1].split("\n",1)[0]
|
||||||
if value.startswith("SUCCESS"):
|
if value.startswith("SUCCESS"):
|
||||||
print "[openwebrx-sdrhu] Update succeeded! "+firsttime
|
print "[openwebrx-sdrhu] Update succeeded! "+firsttime
|
||||||
firsttime=""
|
firsttime=""
|
||||||
else:
|
else:
|
||||||
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
|
print "[openwebrx-sdrhu] Update failed, your receiver cannot be listed on sdr.hu! Reason:", value
|
||||||
else:
|
else:
|
||||||
retrytime_mins = 2
|
retrytime_mins = 2
|
||||||
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
|
print "[openwebrx-sdrhu] wget failed while updating, your receiver cannot be listed on sdr.hu!"
|
||||||
if not continuously: break
|
if not continuously: break
|
||||||
time.sleep(60*retrytime_mins)
|
time.sleep(60*retrytime_mins)
|
||||||
|
|
||||||
if __name__=="__main__":
|
if __name__=="__main__":
|
||||||
run(False)
|
run(False)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user