Merged feature/digitalmods
This commit is contained in:
commit
c62f29ab5a
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
*.pyc
|
||||
*.swp
|
||||
tags
|
||||
|
14
README.md
14
README.md
@ -3,16 +3,19 @@ OpenWebRX
|
||||
|
||||
OpenWebRX is a multi-user SDR receiver software with a web interface.
|
||||
|
||||
![OpenWebRX](/screenshot.png?raw=true)
|
||||
![OpenWebRX](http://blog.sdr.hu/images/openwebrx/screenshot.png)
|
||||
|
||||
It has the following features:
|
||||
|
||||
- <a href="https://github.com/simonyiszk/csdr">libcsdr</a> based demodulators (AM/FM/SSB),
|
||||
- <a href="https://github.com/simonyiszk/csdr">csdr</a> based demodulators (AM/FM/SSB/CW/BPSK31),
|
||||
- filter passband can be set from GUI,
|
||||
- waterfall display can be shifted back in time,
|
||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>.
|
||||
- it extensively uses HTML5 features like WebSocket, Web Audio API, and <canvas>,
|
||||
- it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28),
|
||||
- currently supports RTL-SDR, HackRF, SDRplay, AirSpy and many other devices, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/">OpenWebRX Wiki</a>.
|
||||
- it has a 3D waterfall display:
|
||||
|
||||
![OpenWebRX 3D waterfall](http://blog.sdr.hu/images/openwebrx/screenshot-3d.gif)
|
||||
|
||||
**News (2015-08-18)**
|
||||
- My BSc. thesis written on OpenWebRX is <a href="http://openwebrx.org/bsc-thesis.pdf">available here.</a>
|
||||
@ -32,13 +35,16 @@ It has the following features:
|
||||
- *ncat* has been replaced with a custom implementation called *nmux* due to a bug that caused regular crashes on some machines. The *nmux* tool is part of the *csdr* package.
|
||||
- Most consumer SDR devices are supported via <a href="https://github.com/rxseger/rx_tools">rx_tools</a>, see the <a href="https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX">OpenWebRX Wiki</a> on that.
|
||||
|
||||
**News (2017-07-07)**
|
||||
- OpenWebRX now has a BPSK31 demodulator and a 3D waterfall display.
|
||||
|
||||
> When upgrading OpenWebRX, please make sure that you also upgrade *csdr*!
|
||||
|
||||
## OpenWebRX servers on SDR.hu
|
||||
|
||||
[SDR.hu](http://sdr.hu) is a site which lists the active, public OpenWebRX servers. Your receiver [can also be part of it](http://sdr.hu/openwebrx), if you want.
|
||||
|
||||
![sdr.hu](/screenshot-sdrhu.png?raw=true)
|
||||
![sdr.hu](http://blog.sdr.hu/images/openwebrx/screenshot-sdrhu.png)
|
||||
|
||||
## Setup
|
||||
|
||||
|
@ -67,19 +67,22 @@ sdrhu_key = ""
|
||||
sdrhu_public_listing = False
|
||||
|
||||
# ==== DSP/RX settings ====
|
||||
dsp_plugin="csdr"
|
||||
fft_fps=9
|
||||
fft_size=4096 #Should be power of 2
|
||||
fft_voverlap_factor=0.3 #If fft_voverlap_factor is above 0, multiple FFTs will be used for creating a line on the diagram.
|
||||
|
||||
samp_rate = 250000
|
||||
center_freq = 145525000
|
||||
# samp_rate = 250000
|
||||
samp_rate = 2400000
|
||||
center_freq = 144250000
|
||||
rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode.
|
||||
ppm = 0
|
||||
|
||||
audio_compression="adpcm" #valid values: "adpcm", "none"
|
||||
fft_compression="adpcm" #valid values: "adpcm", "none"
|
||||
|
||||
digimodes_enable=True #Decoding digimodes come with higher CPU usage.
|
||||
digimodes_fft_size=1024
|
||||
|
||||
start_rtl_thread=True
|
||||
|
||||
"""
|
||||
@ -104,7 +107,9 @@ Note: if you experience audio underruns while CPU usage is 100%, you can:
|
||||
start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
|
||||
format_conversion="csdr convert_u8_f"
|
||||
|
||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l16 -a0 -q -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm)
|
||||
#lna_gain=8
|
||||
#rf_amp=1
|
||||
#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain)
|
||||
#format_conversion="csdr convert_s8_f"
|
||||
"""
|
||||
To use a HackRF, compile the HackRF host tools from its "stdout" branch:
|
||||
@ -127,9 +132,9 @@ To use a HackRF, compile the HackRF host tools from its "stdout" branch:
|
||||
#format_conversion="csdr convert_s16_f | csdr gain_ff 30"
|
||||
|
||||
# >> /dev/urandom test signal source
|
||||
#samp_rate = 2400000
|
||||
#start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
|
||||
#format_conversion="csdr convert_u8_f"
|
||||
# samp_rate = 2400000
|
||||
# start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate)
|
||||
# format_conversion="csdr convert_u8_f"
|
||||
|
||||
# >> Pre-recorded raw I/Q file as signal source
|
||||
# You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file.
|
||||
@ -188,9 +193,14 @@ waterfall_auto_level_margin = (5, 40)
|
||||
# ___|____________________________________|____________________________________|____________________________________|___> signal power
|
||||
# \_waterfall_auto_level_margin[0]_/ |__ current_min_power_level | \_waterfall_auto_level_margin[1]_/
|
||||
# current_max_power_level __|
|
||||
# ==== Experimental settings ===
|
||||
|
||||
#Warning! These are very experimental.
|
||||
# 3D view settings
|
||||
mathbox_waterfall_frequency_resolution = 128 #bins
|
||||
mathbox_waterfall_history_length = 10 #seconds
|
||||
mathbox_waterfall_colors = "[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]"
|
||||
|
||||
# === Experimental settings ===
|
||||
#Warning! The settings below are very experimental.
|
||||
csdr_dynamic_bufsize = False # This allows you to change the buffering mode of csdr.
|
||||
csdr_print_bufsizes = False # This prints the buffer sizes used for csdr processes.
|
||||
csdr_through = False # Setting this True will print out how much data is going into the DSP chains.
|
||||
|
424
csdr.py
Executable file
424
csdr.py
Executable file
@ -0,0 +1,424 @@
|
||||
"""
|
||||
OpenWebRX csdr plugin: do the signal processing with csdr
|
||||
|
||||
This file is part of OpenWebRX,
|
||||
an open-source SDR receiver software with a web UI.
|
||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import code
|
||||
import signal
|
||||
import fcntl
|
||||
|
||||
class dsp:
|
||||
|
||||
def __init__(self):
|
||||
self.samp_rate = 250000
|
||||
self.output_rate = 11025 #this is default, and cannot be set at the moment
|
||||
self.fft_size = 1024
|
||||
self.fft_fps = 5
|
||||
self.offset_freq = 0
|
||||
self.low_cut = -4000
|
||||
self.high_cut = 4000
|
||||
self.bpf_transition_bw = 320 #Hz, and this is a constant
|
||||
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
|
||||
self.running = False
|
||||
self.secondary_processes_running = False
|
||||
self.audio_compression = "none"
|
||||
self.fft_compression = "none"
|
||||
self.demodulator = "nfm"
|
||||
self.name = "csdr"
|
||||
self.format_conversion = "csdr convert_u8_f"
|
||||
self.base_bufsize = 512
|
||||
self.nc_port = 4951
|
||||
self.csdr_dynamic_bufsize = False
|
||||
self.csdr_print_bufsizes = False
|
||||
self.csdr_through = False
|
||||
self.squelch_level = 0
|
||||
self.fft_averages = 50
|
||||
self.iqtee = False
|
||||
self.iqtee2 = False
|
||||
self.secondary_demodulator = None
|
||||
self.secondary_fft_size = 1024
|
||||
self.secondary_process_fft = None
|
||||
self.secondary_process_demod = None
|
||||
self.pipe_names=["bpf_pipe", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"]
|
||||
self.secondary_pipe_names=["secondary_shift_pipe"]
|
||||
self.secondary_offset_freq = 1000
|
||||
|
||||
def chain(self,which):
|
||||
any_chain_base="nc -v 127.0.0.1 {nc_port} | "
|
||||
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
|
||||
if self.csdr_through: any_chain_base+="csdr through | "
|
||||
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
|
||||
if which == "fft":
|
||||
fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | " + \
|
||||
("csdr logpower_cf -70 | " if self.fft_averages == 0 else "csdr logaveragepower_cf -70 {fft_size} {fft_averages} | ") + \
|
||||
"csdr fft_exchange_sides_ff {fft_size}"
|
||||
if self.fft_compression=="adpcm":
|
||||
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
|
||||
else:
|
||||
return fft_chain_base
|
||||
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
|
||||
if self.secondary_demodulator:
|
||||
chain_begin+="csdr tee {iqtee_pipe} | "
|
||||
chain_begin+="csdr tee {iqtee2_pipe} | "
|
||||
chain_end = ""
|
||||
if self.audio_compression=="adpcm":
|
||||
chain_end = " | csdr encode_ima_adpcm_i16_u8"
|
||||
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff 1024 | csdr convert_f_s16"+chain_end
|
||||
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
|
||||
def secondary_chain(self, which):
|
||||
secondary_chain_base="cat {input_pipe} | "
|
||||
if which == "fft":
|
||||
return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "")
|
||||
elif which == "bpsk31":
|
||||
return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \
|
||||
"csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \
|
||||
"csdr simple_agc_cc 0.001 0.5 | " + \
|
||||
"csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \
|
||||
"CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \
|
||||
"CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8"
|
||||
|
||||
def set_secondary_demodulator(self, what):
|
||||
self.secondary_demodulator = what
|
||||
|
||||
def secondary_fft_block_size(self):
|
||||
return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here
|
||||
|
||||
def secondary_decimation(self):
|
||||
return 1 #currently unused
|
||||
|
||||
def secondary_bpf_cutoff(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return (31.25/2) / self.if_samp_rate()
|
||||
return 0
|
||||
|
||||
def secondary_bpf_transition_bw(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return (31.25/2) / self.if_samp_rate()
|
||||
return 0
|
||||
|
||||
def secondary_samples_per_bits(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return int(round(self.if_samp_rate()/31.25))&~3
|
||||
return 0
|
||||
|
||||
def secondary_bw(self):
|
||||
if self.secondary_demodulator == "bpsk31":
|
||||
return 31.25
|
||||
|
||||
def start_secondary_demodulator(self):
|
||||
if(not self.secondary_demodulator): return
|
||||
print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate()
|
||||
secondary_command_fft=self.secondary_chain("fft")
|
||||
secondary_command_demod=self.secondary_chain(self.secondary_demodulator)
|
||||
self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft)
|
||||
|
||||
secondary_command_fft=secondary_command_fft.format( \
|
||||
input_pipe=self.iqtee_pipe, \
|
||||
secondary_fft_input_size=self.secondary_fft_size, \
|
||||
secondary_fft_size=self.secondary_fft_size, \
|
||||
secondary_fft_block_size=self.secondary_fft_block_size(), \
|
||||
)
|
||||
secondary_command_demod=secondary_command_demod.format( \
|
||||
input_pipe=self.iqtee2_pipe, \
|
||||
secondary_shift_pipe=self.secondary_shift_pipe, \
|
||||
secondary_decimation=self.secondary_decimation(), \
|
||||
secondary_samples_per_bits=self.secondary_samples_per_bits(), \
|
||||
secondary_bpf_cutoff=self.secondary_bpf_cutoff(), \
|
||||
secondary_bpf_transition_bw=self.secondary_bpf_transition_bw(), \
|
||||
if_samp_rate=self.if_samp_rate()
|
||||
)
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] secondary command (fft) =", secondary_command_fft
|
||||
print "[openwebrx-dsp-plugin:csdr] secondary command (demod) =", secondary_command_demod
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
#if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.secondary_process_fft = subprocess.Popen(secondary_command_fft, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (fft)"
|
||||
self.secondary_process_demod = subprocess.Popen(secondary_command_demod, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) #TODO digimodes
|
||||
print "[openwebrx-dsp-plugin:csdr] Popen on secondary command (demod)" #TODO digimodes
|
||||
self.secondary_processes_running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
# print "==========> 1"
|
||||
if self.secondary_shift_pipe != None: #TODO digimodes
|
||||
# print "==========> 2", self.secondary_shift_pipe
|
||||
self.secondary_shift_pipe_file=open(self.secondary_shift_pipe,"w") #TODO digimodes
|
||||
# print "==========> 3"
|
||||
self.set_secondary_offset_freq(self.secondary_offset_freq) #TODO digimodes
|
||||
# print "==========> 4"
|
||||
|
||||
self.set_pipe_nonblocking(self.secondary_process_demod.stdout)
|
||||
self.set_pipe_nonblocking(self.secondary_process_fft.stdout)
|
||||
|
||||
def set_secondary_offset_freq(self, value):
|
||||
self.secondary_offset_freq=value
|
||||
if self.secondary_processes_running:
|
||||
self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate()))
|
||||
self.secondary_shift_pipe_file.flush()
|
||||
|
||||
def stop_secondary_demodulator(self):
|
||||
if self.secondary_processes_running == False: return
|
||||
self.try_delete_pipes(self.secondary_pipe_names)
|
||||
if self.secondary_process_fft: os.killpg(os.getpgid(self.secondary_process_fft.pid), signal.SIGTERM)
|
||||
if self.secondary_process_demod: os.killpg(os.getpgid(self.secondary_process_demod.pid), signal.SIGTERM)
|
||||
self.secondary_processes_running = False
|
||||
|
||||
def read_secondary_demod(self, size):
|
||||
return self.secondary_process_demod.stdout.read(size)
|
||||
|
||||
def read_secondary_fft(self, size):
|
||||
return self.secondary_process_fft.stdout.read(size)
|
||||
|
||||
def get_secondary_demodulator(self):
|
||||
return self.secondary_demodulator
|
||||
|
||||
def set_secondary_fft_size(self,secondary_fft_size):
|
||||
#to change this, restart is required
|
||||
self.secondary_fft_size=secondary_fft_size
|
||||
|
||||
def set_audio_compression(self,what):
|
||||
self.audio_compression = what
|
||||
|
||||
def set_fft_compression(self,what):
|
||||
self.fft_compression = what
|
||||
|
||||
def get_fft_bytes_to_read(self):
|
||||
if self.fft_compression=="none": return self.fft_size*4
|
||||
if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
|
||||
|
||||
def get_secondary_fft_bytes_to_read(self):
|
||||
if self.fft_compression=="none": return self.secondary_fft_size*4
|
||||
if self.fft_compression=="adpcm": return (self.secondary_fft_size/2)+(10/2)
|
||||
|
||||
def set_samp_rate(self,samp_rate):
|
||||
#to change this, restart is required
|
||||
self.samp_rate=samp_rate
|
||||
self.decimation=1
|
||||
while self.samp_rate/(self.decimation+1)>self.output_rate:
|
||||
self.decimation+=1
|
||||
self.last_decimation=float(self.if_samp_rate())/self.output_rate
|
||||
|
||||
def if_samp_rate(self):
|
||||
return self.samp_rate/self.decimation
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_output_rate(self):
|
||||
return self.output_rate
|
||||
|
||||
def set_output_rate(self,output_rate):
|
||||
self.output_rate=output_rate
|
||||
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
|
||||
|
||||
def set_demodulator(self,demodulator):
|
||||
#to change this, restart is required
|
||||
self.demodulator=demodulator
|
||||
|
||||
def get_demodulator(self):
|
||||
return self.demodulator
|
||||
|
||||
def set_fft_size(self,fft_size):
|
||||
#to change this, restart is required
|
||||
self.fft_size=fft_size
|
||||
|
||||
def set_fft_fps(self,fft_fps):
|
||||
#to change this, restart is required
|
||||
self.fft_fps=fft_fps
|
||||
|
||||
def set_fft_averages(self,fft_averages):
|
||||
#to change this, restart is required
|
||||
self.fft_averages=fft_averages
|
||||
|
||||
def fft_block_size(self):
|
||||
if self.fft_averages == 0: return self.samp_rate/self.fft_fps
|
||||
else: return self.samp_rate/self.fft_fps/self.fft_averages
|
||||
|
||||
def set_format_conversion(self,format_conversion):
|
||||
self.format_conversion=format_conversion
|
||||
|
||||
def set_offset_freq(self,offset_freq):
|
||||
self.offset_freq=offset_freq
|
||||
if self.running:
|
||||
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
|
||||
self.shift_pipe_file.flush()
|
||||
|
||||
def set_bpf(self,low_cut,high_cut):
|
||||
self.low_cut=low_cut
|
||||
self.high_cut=high_cut
|
||||
if self.running:
|
||||
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
|
||||
self.bpf_pipe_file.flush()
|
||||
|
||||
def get_bpf(self):
|
||||
return [self.low_cut, self.high_cut]
|
||||
|
||||
def set_squelch_level(self, squelch_level):
|
||||
self.squelch_level=squelch_level
|
||||
if self.running:
|
||||
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
|
||||
self.squelch_pipe_file.flush()
|
||||
|
||||
def get_smeter_level(self):
|
||||
if self.running:
|
||||
line=self.smeter_pipe_file.readline()
|
||||
return float(line[:-1])
|
||||
|
||||
def mkfifo(self,path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
pass
|
||||
os.mkfifo(path)
|
||||
|
||||
def ddc_transition_bw(self):
|
||||
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
|
||||
|
||||
def try_create_pipes(self, pipe_names, command_base):
|
||||
# print "try_create_pipes"
|
||||
for pipe_name in pipe_names:
|
||||
# print "\t"+pipe_name
|
||||
if "{"+pipe_name+"}" in command_base:
|
||||
setattr(self, pipe_name, self.pipe_base_path+pipe_name)
|
||||
self.mkfifo(getattr(self, pipe_name))
|
||||
else:
|
||||
setattr(self, pipe_name, None)
|
||||
|
||||
def try_delete_pipes(self, pipe_names):
|
||||
for pipe_name in pipe_names:
|
||||
pipe_path = getattr(self,pipe_name,None)
|
||||
if pipe_path:
|
||||
try: os.unlink(pipe_path)
|
||||
except Exception as e: print "[openwebrx-dsp-plugin:csdr] try_delete_pipes() ::", e
|
||||
|
||||
def set_pipe_nonblocking(self, pipe):
|
||||
flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
|
||||
fcntl.fcntl(pipe, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
def start(self):
|
||||
command_base=self.chain(self.demodulator)
|
||||
|
||||
#create control pipes for csdr
|
||||
self.pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
|
||||
# self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
|
||||
|
||||
self.try_create_pipes(self.pipe_names, command_base)
|
||||
|
||||
# if "{bpf_pipe}" in command_base:
|
||||
# self.bpf_pipe=pipe_base_path+"bpf"
|
||||
# self.mkfifo(self.bpf_pipe)
|
||||
# if "{shift_pipe}" in command_base:
|
||||
# self.shift_pipe=pipe_base_path+"shift"
|
||||
# self.mkfifo(self.shift_pipe)
|
||||
# if "{squelch_pipe}" in command_base:
|
||||
# self.squelch_pipe=pipe_base_path+"squelch"
|
||||
# self.mkfifo(self.squelch_pipe)
|
||||
# if "{smeter_pipe}" in command_base:
|
||||
# self.smeter_pipe=pipe_base_path+"smeter"
|
||||
# self.mkfifo(self.smeter_pipe)
|
||||
# if "{iqtee_pipe}" in command_base:
|
||||
# self.iqtee_pipe=pipe_base_path+"iqtee"
|
||||
# self.mkfifo(self.iqtee_pipe)
|
||||
# if "{iqtee2_pipe}" in command_base:
|
||||
# self.iqtee2_pipe=pipe_base_path+"iqtee2"
|
||||
# self.mkfifo(self.iqtee2_pipe)
|
||||
|
||||
#run the command
|
||||
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
|
||||
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), fft_averages=self.fft_averages, \
|
||||
bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \
|
||||
flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \
|
||||
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe, iqtee_pipe=self.iqtee_pipe, iqtee2_pipe=self.iqtee2_pipe )
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] Command =",command
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
self.running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
if self.bpf_pipe != None:
|
||||
self.bpf_pipe_file=open(self.bpf_pipe,"w")
|
||||
self.set_bpf(self.low_cut,self.high_cut)
|
||||
if self.shift_pipe != None:
|
||||
self.shift_pipe_file=open(self.shift_pipe,"w")
|
||||
self.set_offset_freq(self.offset_freq)
|
||||
if self.squelch_pipe != None:
|
||||
self.squelch_pipe_file=open(self.squelch_pipe,"w")
|
||||
self.set_squelch_level(self.squelch_level)
|
||||
if self.smeter_pipe != None:
|
||||
self.smeter_pipe_file=open(self.smeter_pipe,"r")
|
||||
self.set_pipe_nonblocking(self.smeter_pipe_file)
|
||||
|
||||
self.start_secondary_demodulator()
|
||||
|
||||
def read(self,size):
|
||||
return self.process.stdout.read(size)
|
||||
|
||||
def stop(self):
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
self.stop_secondary_demodulator()
|
||||
#if(self.process.poll()!=None):return # returns None while subprocess is running
|
||||
#while(self.process.poll()==None):
|
||||
# #self.process.kill()
|
||||
# print "killproc",os.getpgid(self.process.pid),self.process.pid
|
||||
# os.killpg(self.process.pid, signal.SIGTERM)
|
||||
#
|
||||
# time.sleep(0.1)
|
||||
|
||||
self.try_delete_pipes(self.pipe_names)
|
||||
|
||||
# if self.bpf_pipe:
|
||||
# try: os.unlink(self.bpf_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
|
||||
# if self.shift_pipe:
|
||||
# try: os.unlink(self.shift_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
|
||||
# if self.squelch_pipe:
|
||||
# try: os.unlink(self.squelch_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
|
||||
# if self.smeter_pipe:
|
||||
# try: os.unlink(self.smeter_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
|
||||
# if self.iqtee_pipe:
|
||||
# try: os.unlink(self.iqtee_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee_pipe
|
||||
# if self.iqtee2_pipe:
|
||||
# try: os.unlink(self.iqtee2_pipe)
|
||||
# except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.iqtee2_pipe
|
||||
|
||||
self.running = False
|
||||
|
||||
def restart(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
del(self.process)
|
BIN
htdocs/gfx/openwebrx-3d-spectrum.png
Normal file
BIN
htdocs/gfx/openwebrx-3d-spectrum.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.9 KiB |
@ -28,15 +28,23 @@
|
||||
var ws_url="%[WS_URL]";
|
||||
var rx_photo_height=%[RX_PHOTO_HEIGHT];
|
||||
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
|
||||
var starting_mod = "%[START_MOD]";
|
||||
var starting_mod="%[START_MOD]";
|
||||
var starting_offset_frequency = %[START_OFFSET_FREQ];
|
||||
var waterfall_colors=%[WATERFALL_COLORS];
|
||||
var waterfall_min_level_default=%[WATERFALL_MIN_LEVEL];
|
||||
var waterfall_max_level_default=%[WATERFALL_MAX_LEVEL];
|
||||
var waterfall_auto_level_margin=%[WATERFALL_AUTO_LEVEL_MARGIN];
|
||||
var server_enable_digimodes=%[DIGIMODES_ENABLE];
|
||||
var mathbox_waterfall_frequency_resolution=%[MATHBOX_WATERFALL_FRES];
|
||||
var mathbox_waterfall_history_length=%[MATHBOX_WATERFALL_THIST];
|
||||
var mathbox_waterfall_colors=%[MATHBOX_WATERFALL_COLORS];
|
||||
</script>
|
||||
<script src="sdr.js"></script>
|
||||
<script src="mathbox-bundle.min.js"></script>
|
||||
<script src="openwebrx.js"></script>
|
||||
<script src="jquery-3.2.1.min.js"></script>
|
||||
<script src="jquery.nanoscroller.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="nanoscroller.css" />
|
||||
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
@ -73,8 +81,8 @@
|
||||
<div id="openwebrx-scale-container">
|
||||
<canvas id="openwebrx-scale-canvas" width="0" height="0"></canvas>
|
||||
</div>
|
||||
<div id="openwebrx-mathbox-container"> </div>
|
||||
<div id="webrx-canvas-container">
|
||||
|
||||
<div id="openwebrx-phantom-canvas"></div>
|
||||
<!-- add canvas here by javascript -->
|
||||
</div>
|
||||
@ -82,13 +90,24 @@
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="259,115">
|
||||
<div id="webrx-actual-freq">---.--- MHz</div>
|
||||
<div id="webrx-mouse-freq">---.--- MHz</div>
|
||||
<!--<div class="openwebrx-button" onclick="ws.send('SET mod=wfm');" >WFM</div>-->
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('am');">AM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-nfm"
|
||||
onclick="demodulator_analog_replace('nfm');">FM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-am"
|
||||
onclick="demodulator_analog_replace('am');">AM</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-lsb"
|
||||
onclick="demodulator_analog_replace('lsb');">LSB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-usb"
|
||||
onclick="demodulator_analog_replace('usb');">USB</div>
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-cw"
|
||||
onclick="demodulator_analog_replace('cw');">CW</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div class="openwebrx-button openwebrx-demodulator-button" id="openwebrx-button-dig" onclick="demodulator_digital_replace_last();">DIG</div>
|
||||
<select id="openwebrx-secondary-demod-listbox" onchange="secondary_demod_listbox_changed();">
|
||||
<option value="none"></option>
|
||||
<option value="bpsk31">BPSK31</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
<div title="Mute on/off" id="openwebrx-mute-off" class="openwebrx-button" onclick="toggleMute();"><img src="gfx/openwebrx-speaker.png" class="openwebrx-sliderbtn-img" id="openwebrx-mute-img"></div>
|
||||
@ -107,6 +126,7 @@
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutOneStep();" title="Zoom out one step"> <img src="gfx/openwebrx-zoom-out.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomInTotal();" title="Zoom in totally"><img src="gfx/openwebrx-zoom-in-total.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="zoomOutTotal();" title="Zoom out totally"><img src="gfx/openwebrx-zoom-out-total.png" /></div>
|
||||
<div class="openwebrx-button openwebrx-square-button" onclick="mathbox_toggle();" title="Toggle 3D view"><img src="gfx/openwebrx-3d-spectrum.png" /></div>
|
||||
<div id="openwebrx-smeter-db">0 dB</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel-line">
|
||||
@ -115,13 +135,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142">
|
||||
<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,137">
|
||||
<div class="openwebrx-panel-inner nano" id="openwebrx-log-scroll">
|
||||
<div class="nano-content">
|
||||
<div id="openwebrx-client-log-title">OpenWebRX client log</strong><span id="openwebrx-problems"></span></div>
|
||||
<span id="openwebrx-client-1">Author: </span><a href="http://blog.sdr.hu/about" target="_blank">András Retzler, HA7ILM</a><br />You can support OpenWebRX development via <a href="http://blog.sdr.hu/support" target="_blank">PayPal!</a><br/>
|
||||
<div id="openwebrx-debugdiv"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
|
||||
@ -134,6 +156,17 @@
|
||||
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
|
||||
<br />We're working on the code right now, so the application might fail.
|
||||
</div>
|
||||
<div class="openwebrx-panel" id="openwebrx-panel-digimodes" data-panel-name="digimodes" data-panel-pos="left" data-panel-order="2" data-panel-size="619,210">
|
||||
<div id="openwebrx-digimode-canvas-container">
|
||||
<div id="openwebrx-digimode-select-channel"></div>
|
||||
</div>
|
||||
<div id="openwebrx-digimode-content-container">
|
||||
<div class="gradient"></div>
|
||||
<div id="openwebrx-digimode-content">
|
||||
<span id="openwebrx-cursor-blink"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
4
htdocs/jquery-3.2.1.min.js
vendored
Normal file
4
htdocs/jquery-3.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1000
htdocs/jquery.nanoscroller.js
Normal file
1000
htdocs/jquery.nanoscroller.js
Normal file
File diff suppressed because it is too large
Load Diff
33
htdocs/mathbox-bundle.min.js
vendored
Normal file
33
htdocs/mathbox-bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
461
htdocs/mathbox.css
Normal file
461
htdocs/mathbox.css
Normal file
@ -0,0 +1,461 @@
|
||||
.shadergraph-graph {
|
||||
font: 12px sans-serif;
|
||||
line-height: 25px;
|
||||
position: relative;
|
||||
}
|
||||
.shadergraph-graph:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 0;
|
||||
font-size: 0;
|
||||
clear: both;
|
||||
}
|
||||
.shadergraph-graph svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
.shadergraph-clear {
|
||||
clear: both;
|
||||
}
|
||||
.shadergraph-graph svg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.shadergraph-column {
|
||||
float: left;
|
||||
}
|
||||
.shadergraph-node .shadergraph-graph {
|
||||
float: left;
|
||||
clear: both;
|
||||
overflow: visible;
|
||||
}
|
||||
.shadergraph-node .shadergraph-graph .shadergraph-node {
|
||||
margin: 5px 15px 15px;
|
||||
}
|
||||
.shadergraph-node {
|
||||
margin: 5px 15px 25px;
|
||||
background: rgba(0, 0, 0, .1);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||
0 1px 10px rgba(0, 0, 0, .2);
|
||||
min-height: 35px;
|
||||
float: left;
|
||||
clear: left;
|
||||
position: relative;
|
||||
}
|
||||
.shadergraph-type {
|
||||
font-weight: bold;
|
||||
}
|
||||
.shadergraph-header {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
height: 25px;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, .25);
|
||||
color: #fff;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.shadergraph-outlet div {
|
||||
}
|
||||
.shadergraph-outlet-in .shadergraph-name {
|
||||
margin-right: 7px;
|
||||
}
|
||||
.shadergraph-outlet-out .shadergraph-name {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
.shadergraph-name {
|
||||
margin: 0 4px;
|
||||
}
|
||||
.shadergraph-point {
|
||||
margin: 6px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
border-radius: 7.5px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.shadergraph-outlet-in {
|
||||
float: left;
|
||||
clear: left;
|
||||
}
|
||||
.shadergraph-outlet-in div {
|
||||
float: left;
|
||||
}
|
||||
.shadergraph-outlet-out {
|
||||
float: right;
|
||||
clear: right;
|
||||
}
|
||||
.shadergraph-outlet-out div {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.shadergraph-node-callback {
|
||||
background: rgba(205, 209, 221, .5);
|
||||
box-shadow: 0 1px 2px rgba(0, 10, 40, .2),
|
||||
0 1px 10px rgba(0, 10, 40, .2);
|
||||
}
|
||||
.shadergraph-node-callback > .shadergraph-header {
|
||||
background: rgba(0, 20, 80, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-callback {
|
||||
background: rgba(0, 20, 80, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node-call {
|
||||
background: rgba(209, 221, 205, .5);
|
||||
box-shadow: 0 1px 2px rgba(10, 40, 0, .2),
|
||||
0 1px 10px rgba(10, 40, 0, .2);
|
||||
}
|
||||
.shadergraph-node-call > .shadergraph-header {
|
||||
background: rgba(20, 80, 0, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-call {
|
||||
background: rgba(20, 80, 0, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node-isolate {
|
||||
background: rgba(221, 205, 209, .5);
|
||||
box-shadow: 0 1px 2px rgba(40, 0, 10, .2),
|
||||
0 1px 10px rgba(40, 0, 10, .2);
|
||||
}
|
||||
.shadergraph-node-isolate > .shadergraph-header {
|
||||
background: rgba(80, 0, 20, .3);
|
||||
}
|
||||
.shadergraph-graph .shadergraph-graph .shadergraph-node-isolate {
|
||||
background: rgba(80, 0, 20, .1);
|
||||
}
|
||||
|
||||
.shadergraph-node.shadergraph-has-code {
|
||||
cursor: pointer;
|
||||
}
|
||||
.shadergraph-node.shadergraph-has-code::before {
|
||||
position: absolute;
|
||||
content: ' ';
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
border: 2px solid rgba(0, 0, 0, .25);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.shadergraph-node.shadergraph-has-code:hover::before {
|
||||
display: block;
|
||||
}
|
||||
.shadergraph-code {
|
||||
z-index: 10000;
|
||||
display: none;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
white-space: pre;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, .2),
|
||||
0 1px 10px rgba(0, 0, 0, .2);
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.shadergraph-overlay {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
border-top: 1px solid #CCC;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-view {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-inside {
|
||||
width: 4000px;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-close {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
padding: 4px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255,255,255,.3);
|
||||
color: rgba(0, 0, 0, .3);
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-close:hover {
|
||||
background: rgba(255,255,255,1);
|
||||
color: rgba(0, 0, 0, 1);
|
||||
}
|
||||
.shadergraph-overlay .shadergraph-graph {
|
||||
padding-top: 10px;
|
||||
overflow: visible;
|
||||
min-height: 100%;
|
||||
}
|
||||
.shadergraph-overlay span {
|
||||
display: block;
|
||||
padding: 5px 15px;
|
||||
margin: 0;
|
||||
background: rgba(0, 0, 0, .1);
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.mathbox-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 10px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.mathbox-loader.mathbox-exit {
|
||||
opacity: 0;
|
||||
-webkit-transition:
|
||||
opacity .15s ease-in-out;
|
||||
transition:
|
||||
opacity .15s ease-in-out;
|
||||
}
|
||||
|
||||
.mathbox-progress {
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
width: 80px;
|
||||
margin: 0 auto 20px;
|
||||
box-shadow:
|
||||
1px 1px 1px rgba(255, 255, 255, .2),
|
||||
1px -1px 1px rgba(255, 255, 255, .2),
|
||||
-1px 1px 1px rgba(255, 255, 255, .2),
|
||||
-1px -1px 1px rgba(255, 255, 255, .2);
|
||||
background: #ccc;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mathbox-progress > div {
|
||||
display: block;
|
||||
width: 0px;
|
||||
height: 10px;
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.mathbox-logo {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
height: 100px;
|
||||
margin: 0 auto 10px;
|
||||
-webkit-perspective: 200px;
|
||||
perspective: 200px;
|
||||
}
|
||||
|
||||
.mathbox-logo > div {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(1) {
|
||||
-webkit-transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||
transform: rotateZ(22deg) rotateX(24deg) rotateY(30deg);
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(2) {
|
||||
-webkit-transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||
transform: rotateZ(11deg) rotateX(12deg) rotateY(15deg) scale3d(.6, .6, .6);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: -100px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(1) {
|
||||
-webkit-transform: scale(0.5, 0.5);
|
||||
transform: rotateX(30deg) scale(0.5, 0.5);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(2) {
|
||||
-webkit-transform: rotateX(90deg) scale(0.42, 0.42);
|
||||
transform: rotateX(90deg) scale(0.42, 0.42);
|
||||
}
|
||||
|
||||
.mathbox-logo > div > :nth-child(3) {
|
||||
-webkit-transform: rotateY(90deg) scale(0.35, 0.35);
|
||||
transform: rotateY(90deg) scale(0.35, 0.35);
|
||||
}
|
||||
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||
border: 16px solid #808080;
|
||||
}
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||
border: 19px solid #A0A0A0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||
border: 23px solid #C0C0C0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||
border: 27px solid #808080;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||
border: 32px solid #A0A0A0;
|
||||
}
|
||||
.mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||
border: 38px solid #C0C0C0;
|
||||
}
|
||||
|
||||
.mathbox-splash-blue .mathbox-progress {
|
||||
background: #def;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-progress > div {
|
||||
background: #1979e7;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(1) {
|
||||
border-color: #1979e7;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(2) {
|
||||
border-color: #33b0ff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(1) > :nth-child(3) {
|
||||
border-color: #75eaff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(1) {
|
||||
border-color: #18487F;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(2) {
|
||||
border-color: #33b0ff;
|
||||
}
|
||||
.mathbox-splash-blue .mathbox-logo > :nth-child(2) > :nth-child(3) {
|
||||
border-color: #75eaff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.mathbox-overlays {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
transform-style: preserve-3d;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mathbox-overlays > div {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.mathbox-overlay > div {
|
||||
position: absolute;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
.mathbox-label {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.mathbox-outline-1 {
|
||||
text-shadow:
|
||||
-1px -1px 0px rgb(255, 255, 255),
|
||||
1px 1px 0px rgb(255, 255, 255),
|
||||
-1px 1px 0px rgb(255, 255, 255),
|
||||
1px -1px 0px rgb(255, 255, 255),
|
||||
1px 0px 1px rgb(255, 255, 255),
|
||||
-1px 0px 1px rgb(255, 255, 255),
|
||||
0px -1px 1px rgb(255, 255, 255),
|
||||
0px 1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-2 {
|
||||
text-shadow:
|
||||
0px -2px 0px rgb(255, 255, 255),
|
||||
0px 2px 0px rgb(255, 255, 255),
|
||||
-2px 0px 0px rgb(255, 255, 255),
|
||||
2px 0px 0px rgb(255, 255, 255),
|
||||
-1px -2px 0px rgb(255, 255, 255),
|
||||
-2px -1px 0px rgb(255, 255, 255),
|
||||
-1px 2px 0px rgb(255, 255, 255),
|
||||
-2px 1px 0px rgb(255, 255, 255),
|
||||
1px 2px 0px rgb(255, 255, 255),
|
||||
2px 1px 0px rgb(255, 255, 255),
|
||||
1px -2px 0px rgb(255, 255, 255),
|
||||
2px -1px 0px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-3 {
|
||||
text-shadow:
|
||||
3px 0px 0px rgb(255, 255, 255),
|
||||
-3px 0px 0px rgb(255, 255, 255),
|
||||
0px 3px 0px rgb(255, 255, 255),
|
||||
0px -3px 0px rgb(255, 255, 255),
|
||||
|
||||
-2px -2px 0px rgb(255, 255, 255),
|
||||
-2px 2px 0px rgb(255, 255, 255),
|
||||
2px 2px 0px rgb(255, 255, 255),
|
||||
2px -2px 0px rgb(255, 255, 255),
|
||||
|
||||
-1px -2px 1px rgb(255, 255, 255),
|
||||
-2px -1px 1px rgb(255, 255, 255),
|
||||
-1px 2px 1px rgb(255, 255, 255),
|
||||
-2px 1px 1px rgb(255, 255, 255),
|
||||
1px 2px 1px rgb(255, 255, 255),
|
||||
2px 1px 1px rgb(255, 255, 255),
|
||||
1px -2px 1px rgb(255, 255, 255),
|
||||
2px -1px 1px rgb(255, 255, 255);
|
||||
}
|
||||
.mathbox-outline-4 {
|
||||
text-shadow:
|
||||
4px 0px 0px rgb(255, 255, 255),
|
||||
-4px 0px 0px rgb(255, 255, 255),
|
||||
0px 4px 0px rgb(255, 255, 255),
|
||||
0px -4px 0px rgb(255, 255, 255),
|
||||
|
||||
-3px -2px 0px rgb(255, 255, 255),
|
||||
-3px 2px 0px rgb(255, 255, 255),
|
||||
3px 2px 0px rgb(255, 255, 255),
|
||||
3px -2px 0px rgb(255, 255, 255),
|
||||
|
||||
-2px -3px 0px rgb(255, 255, 255),
|
||||
-2px 3px 0px rgb(255, 255, 255),
|
||||
2px 3px 0px rgb(255, 255, 255),
|
||||
2px -3px 0px rgb(255, 255, 255),
|
||||
|
||||
-1px -2px 1px rgb(255, 255, 255),
|
||||
-2px -1px 1px rgb(255, 255, 255),
|
||||
-1px 2px 1px rgb(255, 255, 255),
|
||||
-2px 1px 1px rgb(255, 255, 255),
|
||||
1px 2px 1px rgb(255, 255, 255),
|
||||
2px 1px 1px rgb(255, 255, 255),
|
||||
1px -2px 1px rgb(255, 255, 255),
|
||||
2px -1px 1px rgb(255, 255, 255);
|
||||
|
||||
}
|
||||
.mathbox-outline-fill, .mathbox-outline-fill * {
|
||||
color: #fff !important;
|
||||
}
|
55
htdocs/nanoscroller.css
Normal file
55
htdocs/nanoscroller.css
Normal file
@ -0,0 +1,55 @@
|
||||
/** initial setup **/
|
||||
.nano {
|
||||
position : relative;
|
||||
width : 100%;
|
||||
height : 100%;
|
||||
overflow : hidden;
|
||||
}
|
||||
.nano > .nano-content {
|
||||
position : absolute;
|
||||
overflow : scroll;
|
||||
overflow-x : hidden;
|
||||
top : 0;
|
||||
right : 0;
|
||||
bottom : 0;
|
||||
left : 0;
|
||||
}
|
||||
.nano > .nano-content:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
.nano > .nano-content::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.has-scrollbar > .nano-content::-webkit-scrollbar {
|
||||
display: block;
|
||||
}
|
||||
.nano > .nano-pane {
|
||||
background : rgba(0,0,0,.25);
|
||||
position : absolute;
|
||||
width : 8px;
|
||||
right : 0;
|
||||
top : 0;
|
||||
bottom : 0;
|
||||
visibility : hidden\9; /* Target only IE7 and IE8 with this hack */
|
||||
opacity : .01;
|
||||
-webkit-transition : .2s;
|
||||
-moz-transition : .2s;
|
||||
-o-transition : .2s;
|
||||
transition : .2s;
|
||||
-moz-border-radius : 3px;
|
||||
-webkit-border-radius : 3px;
|
||||
border-radius : 3px;
|
||||
}
|
||||
.nano > .nano-pane > .nano-slider {
|
||||
background: #444;
|
||||
background: rgba(0,0,0,.5);
|
||||
position : relative;
|
||||
margin : 0 0px;
|
||||
-moz-border-radius : 4px;
|
||||
-webkit-border-radius : 4px;
|
||||
border-radius : 4px;
|
||||
}
|
||||
.nano:hover > .nano-pane, .nano-pane.active, .nano-pane.flashed {
|
||||
visibility : visible\9; /* Target only IE7 and IE8 with this hack */
|
||||
opacity : 0.99;
|
||||
}
|
@ -28,6 +28,11 @@ html, body
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
|
||||
}
|
||||
|
||||
input
|
||||
{
|
||||
vertical-align:middle;
|
||||
@ -394,6 +399,13 @@ input[type=range]:focus::-ms-fill-upper
|
||||
border-style: none;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
/*transition: left 200ms, width 200ms;*/
|
||||
}
|
||||
|
||||
#openwebrx-mathbox-container
|
||||
{
|
||||
overflow: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#openwebrx-phantom-canvas
|
||||
@ -410,11 +422,15 @@ input[type=range]:focus::-ms-fill-upper
|
||||
height: 396px;
|
||||
}*/
|
||||
|
||||
/*#webrx-debugdiv
|
||||
#openwebrx-log-scroll
|
||||
{
|
||||
font-size: 10pt;
|
||||
/*overflow-y:scroll;*/
|
||||
/*}*/
|
||||
/*overflow-y:auto;*/
|
||||
height: 125px;
|
||||
width: 619px
|
||||
}
|
||||
|
||||
.nano .nano-pane { background: #444; }
|
||||
.nano .nano-slider { background: #eee !important; }
|
||||
|
||||
#webrx-main-container
|
||||
{
|
||||
@ -499,7 +515,7 @@ input[type=range]:focus::-ms-fill-upper
|
||||
|
||||
.openwebrx-panel
|
||||
{
|
||||
transform: perspective( 600px );
|
||||
transform: perspective( 600px ) rotateX( 90deg );
|
||||
visibility: hidden;
|
||||
background-color: #575757;
|
||||
padding: 10px;
|
||||
@ -526,7 +542,7 @@ input[type=range]:focus::-ms-fill-upper
|
||||
.openwebrx-button
|
||||
{
|
||||
background-color: #373737;
|
||||
padding: 5px;
|
||||
padding: 4.2px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
color: White;
|
||||
@ -544,7 +560,7 @@ input[type=range]:focus::-ms-fill-upper
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.openwebrx-button:hover
|
||||
.openwebrx-button:hover, .openwebrx-demodulator-button.highlighted
|
||||
{
|
||||
/*background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #3F3F3F), color-stop(1, #777777) );
|
||||
background:-moz-linear-gradient( center top, #373737 5%, #4F4F4F 100% );*/
|
||||
@ -560,7 +576,7 @@ input[type=range]:focus::-ms-fill-upper
|
||||
|
||||
.openwebrx-demodulator-button
|
||||
{
|
||||
width: 35px;
|
||||
width: 38px;
|
||||
height: 19px;
|
||||
font-size: 12pt;
|
||||
text-align: center;
|
||||
@ -568,7 +584,7 @@ input[type=range]:focus::-ms-fill-upper
|
||||
|
||||
.openwebrx-square-button img
|
||||
{
|
||||
height: 30px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.openwebrx-round-button
|
||||
@ -732,7 +748,7 @@ img.openwebrx-mirror-img
|
||||
{
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width:91px;
|
||||
width: 95px;
|
||||
}
|
||||
|
||||
.openwebrx-sliderbtn-img
|
||||
@ -776,7 +792,7 @@ img.openwebrx-mirror-img
|
||||
font-size: 10pt;
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
margin-top: 29px;
|
||||
margin-top: 24px;
|
||||
font-family: 'expletus-sans-medium';
|
||||
}
|
||||
|
||||
@ -806,3 +822,152 @@ img.openwebrx-mirror-img
|
||||
{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-canvas-container
|
||||
{
|
||||
/*margin: -10px -10px 10px -10px;*/
|
||||
margin: -10px -10px 0px -10px;
|
||||
border-radius: 15px;
|
||||
height: 150px;
|
||||
background-color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-canvas-container canvas
|
||||
{
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
transition: width 500ms, left 500ms;
|
||||
}
|
||||
|
||||
#openwebrx-secondary-demod-listbox
|
||||
{
|
||||
width: 201px;
|
||||
height: 27px;
|
||||
border-radius: 5px;
|
||||
background-color: #373737;
|
||||
color: White;
|
||||
font-weight: normal;
|
||||
font-size: 13pt;
|
||||
margin-right: 1px;
|
||||
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #373737), color-stop(1, #4F4F4F) );
|
||||
background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% );
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
-moz-appearance: none;
|
||||
padding-left:3px;
|
||||
}
|
||||
|
||||
#openwebrx-secondary-demod-listbox option
|
||||
{
|
||||
border-width: 0px;
|
||||
background-color: #373737;
|
||||
color: White;
|
||||
}
|
||||
|
||||
#openwebrx-cursor-blink
|
||||
{
|
||||
animation: cursor-blink 1s infinite;
|
||||
/*animation: cursor-3d 2s infinite;*/
|
||||
animation-timing-function: linear;
|
||||
animation-direction: alternate;
|
||||
height: 1em;
|
||||
width: 8px;
|
||||
background-color: White;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
/*perspective: 60px;*/
|
||||
|
||||
}
|
||||
|
||||
@keyframes cursor-blink
|
||||
{
|
||||
0%{ opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
100%{ opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes cursor-3d
|
||||
{
|
||||
0%{ transform: rotateX(0deg) rotateX(Ydeg); }
|
||||
50% { transform: rotateX(180deg) rotateY(360deg); opacity: 0.1; }
|
||||
100%{ transform: rotateX(360deg) rotateY(720deg); }
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content
|
||||
{
|
||||
word-wrap: break-word;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content-container
|
||||
{
|
||||
overflow-y: hidden;
|
||||
display: block;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content-container .gradient
|
||||
{
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: linear-gradient(to top, rgba(87,87,87,0) 0%,rgba(87,87,87,1) 100%);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
|
||||
#openwebrx-digimode-content .part
|
||||
{
|
||||
perspective: 700px;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content .part
|
||||
{
|
||||
animation: new-digimode-data-3d 100ms;
|
||||
animation-timing-function: linear;
|
||||
display: inline-block;
|
||||
perspective-origin: 50% 50%;
|
||||
transform-origin: 0% 50%;
|
||||
}
|
||||
|
||||
#openwebrx-digimode-content .part .subpart
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@keyframes new-digimode-data
|
||||
{
|
||||
0%{ opacity: 0; }
|
||||
100%{ opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes new-digimode-data-3d
|
||||
{
|
||||
0%{ transform: rotateX(0deg) rotateY(-90deg) translateX(-5px) scale(1.3); }
|
||||
100%{ transform: rotateX(0deg) rotateY(0deg) translateX(0) scale(1); }
|
||||
}
|
||||
|
||||
#openwebrx-digimode-select-channel
|
||||
{
|
||||
transition: all 500ms;
|
||||
background-color: Yellow;
|
||||
display: block;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
opacity: 0.7;
|
||||
border-style: solid;
|
||||
border-width: 0px;
|
||||
border-color: Red;
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ var audio_compression="none";
|
||||
var waterfall_setup_done=0;
|
||||
var waterfall_queue = [];
|
||||
var waterfall_timer;
|
||||
var secondary_fft_size;
|
||||
|
||||
/*function fade(something,from,to,time_ms,fps)
|
||||
{
|
||||
@ -549,6 +550,7 @@ function demodulator_default_analog(offset_frequency,subtype)
|
||||
|
||||
this.envelope.drag_end=function(x)
|
||||
{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
|
||||
demodulator_buttons_update();
|
||||
to_return=this.dragged_range!=demodulator.draggable_ranges.none; //this part is required for cliking anywhere on the scale to set offset
|
||||
this.dragged_range=demodulator.draggable_ranges.none;
|
||||
return to_return;
|
||||
@ -565,6 +567,7 @@ function mkenvelopes(visible_range) //called from mkscale
|
||||
{
|
||||
demodulators[i].envelope.draw(visible_range);
|
||||
}
|
||||
if(demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut);
|
||||
}
|
||||
|
||||
function demodulator_remove(which)
|
||||
@ -579,8 +582,17 @@ function demodulator_add(what)
|
||||
mkenvelopes(get_visible_freq_range());
|
||||
}
|
||||
|
||||
function demodulator_analog_replace(subtype)
|
||||
last_analog_demodulator_subtype = 'nfm';
|
||||
last_digital_demodulator_subtype = 'bpsk31';
|
||||
|
||||
function demodulator_analog_replace(subtype, for_digital)
|
||||
{ //this function should only exist until the multi-demodulator capability is added
|
||||
if(!(typeof for_digital !== "undefined" && for_digital && secondary_demod))
|
||||
{
|
||||
secondary_demod_close_window();
|
||||
secondary_demod_listbox_update();
|
||||
}
|
||||
last_analog_demodulator_subtype = subtype;
|
||||
var temp_offset=0;
|
||||
if(demodulators.length)
|
||||
{
|
||||
@ -588,6 +600,7 @@ function demodulator_analog_replace(subtype)
|
||||
demodulator_remove(0);
|
||||
}
|
||||
demodulator_add(new demodulator_default_analog(temp_offset,subtype));
|
||||
demodulator_buttons_update();
|
||||
}
|
||||
|
||||
function demodulator_set_offset_frequency(which,to_what)
|
||||
@ -1055,7 +1068,7 @@ function zoom_step(out, where, onscreen)
|
||||
zoom_center_rel=canvas_get_freq_offset(where);
|
||||
//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString());
|
||||
zoom_center_where=onscreen;
|
||||
console.log(zoom_center_where, zoom_center_rel, where);
|
||||
//console.log(zoom_center_where, zoom_center_rel, where);
|
||||
resize_canvases(true);
|
||||
mkscale();
|
||||
}
|
||||
@ -1087,9 +1100,17 @@ function zoom_calc()
|
||||
function resize_waterfall_container(check_init)
|
||||
{
|
||||
if(check_init&&!waterfall_setup_done) return;
|
||||
canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
|
||||
}
|
||||
var numHeight;
|
||||
mathbox_container.style.height=canvas_container.style.height=(numHeight=window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
|
||||
if(mathbox)
|
||||
{
|
||||
//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight;
|
||||
//mathbox.three.camera.updateProjectionMatrix();
|
||||
mathbox.three.renderer.setSize(document.body.offsetWidth, numHeight);
|
||||
console.log(document.body.offsetWidth, numHeight);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
audio_server_output_rate=11025;
|
||||
audio_client_resampling_factor=4;
|
||||
@ -1126,14 +1147,15 @@ function on_ws_recv(evt)
|
||||
if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
|
||||
//
|
||||
debug_ws_data_received+=evt.data.byteLength/1000;
|
||||
firstChars=getFirstChars(evt.data,3);
|
||||
if(firstChars=="CLI")
|
||||
first4Chars=getFirstChars(evt.data,4);
|
||||
first3Chars=first4Chars.slice(0,3);
|
||||
if(first3Chars=="CLI")
|
||||
{
|
||||
var stringData=arrayBufferToString(evt.data);
|
||||
if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData);
|
||||
if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Server acknowledged WebSocket connection.");
|
||||
|
||||
}
|
||||
if(firstChars=="AUD")
|
||||
if(first3Chars=="AUD")
|
||||
{
|
||||
var audio_data;
|
||||
if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4)
|
||||
@ -1143,9 +1165,10 @@ function on_ws_recv(evt)
|
||||
audio_buffer_all_size_debug+=audio_data.length;
|
||||
if(!ios && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init()
|
||||
}
|
||||
else if(firstChars=="FFT")
|
||||
else if(first3Chars=="FFT")
|
||||
{
|
||||
//alert("Yupee! Doing FFT");
|
||||
//if(first4Chars=="FFTS") console.log("FFTS");
|
||||
if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
|
||||
else if(fft_compression="adpcm")
|
||||
{
|
||||
@ -1154,9 +1177,17 @@ function on_ws_recv(evt)
|
||||
var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
|
||||
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
|
||||
for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
|
||||
waterfall_add_queue(waterfall_f32);
|
||||
if(first4Chars=="FFTS") secondary_demod_waterfall_add_queue(waterfall_f32); //TODO digimodes
|
||||
else waterfall_add_queue(waterfall_f32);
|
||||
}
|
||||
} else if(firstChars=="MSG")
|
||||
}
|
||||
else if(first3Chars=="DAT")
|
||||
{
|
||||
//secondary_demod_push_binary_data(new Uint8Array(evt.data,4));
|
||||
secondary_demod_push_data(arrayBufferToString(evt.data).substring(4));
|
||||
//console.log("DAT");
|
||||
}
|
||||
else if(first3Chars=="MSG")
|
||||
{
|
||||
/*try
|
||||
{*/
|
||||
@ -1179,6 +1210,18 @@ function on_ws_recv(evt)
|
||||
break;
|
||||
case "fft_size":
|
||||
fft_size=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_fft_size":
|
||||
secondary_fft_size=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_setup":
|
||||
secondary_demod_init_canvases();
|
||||
break;
|
||||
case "if_samp_rate":
|
||||
if_samp_rate=parseInt(param[1]);
|
||||
break;
|
||||
case "secondary_bw":
|
||||
secondary_bw=parseFloat(param[1]);
|
||||
break;
|
||||
case "fft_fps":
|
||||
fft_fps=parseInt(param[1]);
|
||||
@ -1279,8 +1322,10 @@ function divlog(what, is_error)
|
||||
if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
|
||||
}
|
||||
e("openwebrx-debugdiv").innerHTML+=what+"<br />";
|
||||
var wls=e("openwebrx-log-scroll");
|
||||
wls.scrollTop=wls.scrollHeight; //scroll to bottom
|
||||
//var wls=e("openwebrx-log-scroll");
|
||||
//wls.scrollTop=wls.scrollHeight; //scroll to bottom
|
||||
$(".nano").nanoScroller();
|
||||
$(".nano").nanoScroller({ scroll: 'bottom' });
|
||||
}
|
||||
|
||||
var audio_context;
|
||||
@ -1516,16 +1561,24 @@ function webrx_set_param(what, value)
|
||||
ws.send("SET "+what+"="+value.toString());
|
||||
}
|
||||
|
||||
var starting_mute = false;
|
||||
|
||||
function parsehash()
|
||||
{
|
||||
if(h=window.location.hash)
|
||||
{
|
||||
h.substring(1).split(",").forEach(function(x){
|
||||
harr=x.split("=");
|
||||
console.log(harr);
|
||||
if(harr[0]=="mod") starting_mod = harr[1];
|
||||
if(harr[0]=="sql") { e("openwebrx-panel-squelch").value=harr[1]; updateSquelch(); }
|
||||
if(harr[0]=="freq") {
|
||||
//console.log(harr);
|
||||
if(harr[0]=="mute") toggleMute();
|
||||
else if(harr[0]=="mod") starting_mod = harr[1];
|
||||
else if(harr[0]=="sql")
|
||||
{
|
||||
e("openwebrx-panel-squelch").value=harr[1];
|
||||
updateSquelch();
|
||||
}
|
||||
else if(harr[0]=="freq")
|
||||
{
|
||||
console.log(parseInt(harr[1]));
|
||||
console.log(center_freq);
|
||||
starting_offset_frequency = parseInt(harr[1])-center_freq;
|
||||
@ -1569,6 +1622,8 @@ function audio_preinit()
|
||||
|
||||
function audio_init()
|
||||
{
|
||||
if(starting_mute) toggleMute();
|
||||
|
||||
if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor...
|
||||
|
||||
audio_debug_time_start=(new Date()).getTime();
|
||||
@ -1613,6 +1668,7 @@ function audio_init()
|
||||
//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
|
||||
}
|
||||
},2000);
|
||||
|
||||
}
|
||||
|
||||
function on_ws_closed()
|
||||
@ -1653,17 +1709,18 @@ function open_websocket()
|
||||
ws.onerror = on_ws_error;
|
||||
}
|
||||
|
||||
function waterfall_mkcolor(db_value)
|
||||
function waterfall_mkcolor(db_value, waterfall_colors_arg)
|
||||
{
|
||||
if(typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors;
|
||||
if(db_value<waterfall_min_level) db_value=waterfall_min_level;
|
||||
if(db_value>waterfall_max_level) db_value=waterfall_max_level;
|
||||
full_scale=waterfall_max_level-waterfall_min_level;
|
||||
relative_value=db_value-waterfall_min_level;
|
||||
value_percent=relative_value/full_scale;
|
||||
percent_for_one_color=1/(waterfall_colors.length-1);
|
||||
percent_for_one_color=1/(waterfall_colors_arg.length-1);
|
||||
index=Math.floor(value_percent/percent_for_one_color);
|
||||
remain=(value_percent-percent_for_one_color*index)/percent_for_one_color;
|
||||
return color_between(waterfall_colors[index+1],waterfall_colors[index],remain);
|
||||
return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain);
|
||||
}
|
||||
|
||||
function color_between(first, second, percent)
|
||||
@ -1686,7 +1743,7 @@ var canvas_phantom;
|
||||
|
||||
function add_canvas()
|
||||
{
|
||||
new_canvas = document.createElement("canvas");
|
||||
var new_canvas = document.createElement("canvas");
|
||||
new_canvas.width=fft_size;
|
||||
new_canvas.height=canvas_default_height;
|
||||
canvas_actual_line=canvas_default_height-1;
|
||||
@ -1706,9 +1763,11 @@ function add_canvas()
|
||||
canvases.push(new_canvas);
|
||||
}
|
||||
|
||||
|
||||
function init_canvas_container()
|
||||
{
|
||||
canvas_container=e("webrx-canvas-container");
|
||||
mathbox_container=e("openwebrx-mathbox-container");
|
||||
canvas_container.addEventListener("mouseout",canvas_container_mouseout, false);
|
||||
//window.addEventListener("mouseout",window_mouseout,false);
|
||||
//document.body.addEventListener("mouseup",body_mouseup,false);
|
||||
@ -1765,7 +1824,7 @@ function resize_canvases(zoom)
|
||||
function waterfall_init()
|
||||
{
|
||||
init_canvas_container();
|
||||
waterfall_timer = window.setInterval(waterfall_dequeue,900/fft_fps);
|
||||
waterfall_timer = window.setInterval(()=>{waterfall_dequeue(); secondary_demod_waterfall_dequeue();},900/fft_fps);
|
||||
resize_waterfall_container(false); /* then */ resize_canvases();
|
||||
scale_setup();
|
||||
mkzoomlevels();
|
||||
@ -1774,6 +1833,42 @@ function waterfall_init()
|
||||
|
||||
var waterfall_dont_scale=0;
|
||||
|
||||
var mathbox_shift = function()
|
||||
{
|
||||
if(mathbox_data_current_depth < mathbox_data_max_depth) mathbox_data_current_depth++;
|
||||
if(mathbox_data_index+1>=mathbox_data_max_depth) mathbox_data_index = 0;
|
||||
else mathbox_data_index++;
|
||||
mathbox_data_global_index++;
|
||||
}
|
||||
|
||||
var mathbox_clear_data = function()
|
||||
{
|
||||
mathbox_data_index = 50;
|
||||
mathbox_data_current_depth = 0;
|
||||
}
|
||||
|
||||
//var mathbox_get_data_line = function(x) //x counts from 0 to mathbox_data_current_depth
|
||||
//{
|
||||
// return (mathbox_data_max_depth + mathbox_data_index - mathbox_data_current_depth + x - 1) % mathbox_data_max_depth;
|
||||
//}
|
||||
//
|
||||
//var mathbox_data_index_valid = function(x) //x counts from 0 to mathbox_data_current_depth
|
||||
//{
|
||||
// return x<mathbox_data_current_depth;
|
||||
//}
|
||||
|
||||
var mathbox_get_data_line = function(x)
|
||||
{
|
||||
return (mathbox_data_max_depth + mathbox_data_index + x - 1) % mathbox_data_max_depth;
|
||||
}
|
||||
|
||||
var mathbox_data_index_valid = function(x)
|
||||
{
|
||||
return x>mathbox_data_max_depth-mathbox_data_current_depth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function waterfall_add(data)
|
||||
{
|
||||
if(!waterfall_setup_done) return;
|
||||
@ -1850,6 +1945,14 @@ function waterfall_add(data)
|
||||
waterfall_image.data[base+x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}*/
|
||||
|
||||
if(mathbox_mode==MATHBOX_MODES.WATERFALL)
|
||||
{
|
||||
//Handle mathbox
|
||||
for(var i=0;i<fft_size;i++) mathbox_data[i+mathbox_data_index*fft_size]=data[i];
|
||||
mathbox_shift();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Add line to waterfall image
|
||||
oneline_image = canvas_context.createImageData(w,1);
|
||||
for(x=0;x<w;x++)
|
||||
@ -1859,13 +1962,13 @@ function waterfall_add(data)
|
||||
oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}
|
||||
|
||||
|
||||
//Draw image
|
||||
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
|
||||
shift_canvases();
|
||||
if(canvas_actual_line<0) add_canvas();
|
||||
}
|
||||
|
||||
|
||||
//divlog("Drawn FFT");
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1895,6 +1998,180 @@ function check_top_bar_congestion()
|
||||
|
||||
}
|
||||
|
||||
var MATHBOX_MODES =
|
||||
{
|
||||
UNINITIALIZED: 0,
|
||||
NONE: 1,
|
||||
WATERFALL: 2,
|
||||
CONSTELLATION: 3
|
||||
};
|
||||
var mathbox_mode = MATHBOX_MODES.UNINITIALIZED;
|
||||
var mathbox;
|
||||
var mathbox_element;
|
||||
|
||||
function mathbox_init()
|
||||
{
|
||||
//mathbox_waterfall_history_length is defined in the config
|
||||
mathbox_data_max_depth = fft_fps * mathbox_waterfall_history_length; //how many lines can the buffer store
|
||||
mathbox_data_current_depth = 0; //how many lines are in the buffer currently
|
||||
mathbox_data_index = 0; //the index of the last empty line / the line to be overwritten
|
||||
mathbox_data = new Float32Array(fft_size * mathbox_data_max_depth);
|
||||
mathbox_data_global_index = 0;
|
||||
mathbox_correction_for_z = 0;
|
||||
|
||||
mathbox = mathBox({
|
||||
plugins: ['core', 'controls', 'cursor', 'stats'],
|
||||
controls: { klass: THREE.OrbitControls },
|
||||
});
|
||||
three = mathbox.three;
|
||||
if(typeof three == "undefined") divlog("3D waterfall cannot be initialized because WebGL is not supported in your browser.", true);
|
||||
|
||||
three.renderer.setClearColor(new THREE.Color(0x808080), 1.0);
|
||||
mathbox_container.appendChild((mathbox_element=three.renderer.domElement));
|
||||
view = mathbox
|
||||
.set({
|
||||
scale: 1080,
|
||||
focus: 3,
|
||||
})
|
||||
.camera({
|
||||
proxy: true,
|
||||
position: [-2, 1, 3],
|
||||
})
|
||||
.cartesian({
|
||||
range: [[-1, 1], [0, 1], [0, 1]],
|
||||
scale: [2, 2/3, 1],
|
||||
});
|
||||
|
||||
view.axis({
|
||||
axis: 1,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
});
|
||||
view.axis({
|
||||
axis: 2,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
//offset: [0, 0, 0],
|
||||
});
|
||||
view.axis({
|
||||
axis: 3,
|
||||
width: 3,
|
||||
color: "#fff",
|
||||
});
|
||||
|
||||
view.grid({
|
||||
width: 2,
|
||||
opacity: 0.5,
|
||||
axes: [1, 3],
|
||||
zOrder: 1,
|
||||
color: "#fff",
|
||||
});
|
||||
|
||||
//var remap = function (v) { return Math.sqrt(.5 + .5 * v); };
|
||||
|
||||
|
||||
var remap = function(x,z,t)
|
||||
{
|
||||
var currentTimePos = mathbox_data_global_index/(fft_fps*1.0);
|
||||
var realZAdd = (-(t-currentTimePos)/mathbox_waterfall_history_length);
|
||||
var zAdd = realZAdd - mathbox_correction_for_z;
|
||||
if(zAdd<-0.2 || zAdd>0.2) { mathbox_correction_for_z = realZAdd; }
|
||||
|
||||
var xIndex = Math.trunc(((x+1)/2.0)*fft_size); //x: frequency
|
||||
var zIndex = Math.trunc(z*(mathbox_data_max_depth-1)); //z: time
|
||||
var realZIndex = mathbox_get_data_line(zIndex);
|
||||
if(!mathbox_data_index_valid(zIndex)) return {y: undefined, dBValue: undefined, zAdd: 0 };
|
||||
//if(realZIndex>=(mathbox_data_max_depth-1)) console.log("realZIndexundef", realZIndex, zIndex);
|
||||
var index = Math.trunc(xIndex + realZIndex * fft_size);
|
||||
/*if(mathbox_data[index]==undefined) console.log("Undef", index, mathbox_data.length, zIndex,
|
||||
realZIndex, mathbox_data_max_depth,
|
||||
mathbox_data_current_depth, mathbox_data_index);*/
|
||||
var dBValue = mathbox_data[index];
|
||||
//y=1;
|
||||
if(dBValue>waterfall_max_level) y = 1;
|
||||
else if(dBValue<waterfall_min_level) y = 0;
|
||||
else y = (dBValue-waterfall_min_level)/(waterfall_max_level-waterfall_min_level);
|
||||
mathbox_dbg = { dbv: dBValue, indexval: index, mbd: mathbox_data.length, yval: y };
|
||||
if(!y) y=0;
|
||||
return {y: y, dBValue: dBValue, zAdd: zAdd};
|
||||
}
|
||||
|
||||
var points = view.area({
|
||||
expr: function (emit, x, z, i, j, t) {
|
||||
var y;
|
||||
remapResult=remap(x,z,t);
|
||||
if((y=remapResult.y)==undefined) return;
|
||||
emit(x, y, z+remapResult.zAdd);
|
||||
},
|
||||
width: mathbox_waterfall_frequency_resolution,
|
||||
height: mathbox_data_max_depth - 1,
|
||||
channels: 3,
|
||||
axes: [1, 3],
|
||||
});
|
||||
|
||||
var colors = view.area({
|
||||
expr: function (emit, x, z, i, j, t) {
|
||||
var dBValue;
|
||||
if((dBValue=remap(x,z,t).dBValue)==undefined) return;
|
||||
var color=waterfall_mkcolor(dBValue, mathbox_waterfall_colors);
|
||||
var b = (color&0xff)/255.0;
|
||||
var g = ((color&0xff00)>>8)/255.0;
|
||||
var r = ((color&0xff0000)>>16)/255.0;
|
||||
emit(r, g, b, 1.0);
|
||||
},
|
||||
width: mathbox_waterfall_frequency_resolution,
|
||||
height: mathbox_data_max_depth - 1,
|
||||
channels: 4,
|
||||
axes: [1, 3],
|
||||
});
|
||||
|
||||
view.surface({
|
||||
shaded: true,
|
||||
points: '<<',
|
||||
colors: '<',
|
||||
color: 0xFFFFFF,
|
||||
});
|
||||
|
||||
view.surface({
|
||||
fill: false,
|
||||
lineX: false,
|
||||
lineY: false,
|
||||
points: '<<',
|
||||
colors: '<',
|
||||
color: 0xFFFFFF,
|
||||
width: 2,
|
||||
blending: 'add',
|
||||
opacity: .25,
|
||||
zBias: 5,
|
||||
});
|
||||
mathbox_mode = MATHBOX_MODES.NONE;
|
||||
|
||||
//mathbox_element.style.width="100%";
|
||||
//mathbox_element.style.height="100%";
|
||||
|
||||
}
|
||||
|
||||
function mathbox_toggle()
|
||||
{
|
||||
|
||||
if(mathbox_mode == MATHBOX_MODES.UNINITIALIZED) mathbox_init();
|
||||
mathbox_mode = (mathbox_mode == MATHBOX_MODES.NONE) ? MATHBOX_MODES.WATERFALL : MATHBOX_MODES.NONE;
|
||||
mathbox_container.style.display = (mathbox_mode == MATHBOX_MODES.WATERFALL) ? "block" : "none";
|
||||
mathbox_clear_data();
|
||||
waterfall_clear();
|
||||
}
|
||||
|
||||
function waterfall_clear()
|
||||
{
|
||||
while(canvases.length) //delete all canvases
|
||||
{
|
||||
var x=canvases.shift();
|
||||
x.parentNode.removeChild(x);
|
||||
delete x;
|
||||
}
|
||||
add_canvas();
|
||||
}
|
||||
|
||||
function openwebrx_resize()
|
||||
{
|
||||
resize_canvases();
|
||||
@ -1909,6 +2186,7 @@ function openwebrx_init()
|
||||
(opb=e("openwebrx-play-button-text")).style.marginTop=(window.innerHeight/2-opb.clientHeight/2).toString()+"px";
|
||||
init_rx_photo();
|
||||
open_websocket();
|
||||
secondary_demod_init();
|
||||
place_panels(first_show_panel);
|
||||
window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
|
||||
window.addEventListener("resize",openwebrx_resize);
|
||||
@ -2002,9 +2280,14 @@ function pop_bottommost_panel(from)
|
||||
return to_return;
|
||||
}
|
||||
|
||||
function toggle_panel(what)
|
||||
function toggle_panel(what, on)
|
||||
{
|
||||
var item=e(what);
|
||||
if(typeof on !== "undefined")
|
||||
{
|
||||
if(item.openwebrxHidden && !on) return;
|
||||
if(!item.openwebrxHidden && on) return;
|
||||
}
|
||||
if(item.openwebrxDisableClick) return;
|
||||
item.style.transitionDuration="599ms";
|
||||
item.style.transitionDelay="0ms";
|
||||
@ -2093,6 +2376,7 @@ function place_panels(function_apply)
|
||||
p.style.visibility="visible";
|
||||
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
|
||||
if(function_apply) function_apply(p);
|
||||
//console.log(p.id, y, p.openwebrxPanelTransparent);
|
||||
}
|
||||
y=hoffset;
|
||||
while(right_col.length>0)
|
||||
@ -2125,3 +2409,331 @@ function progressbar_set(obj,val,text,over)
|
||||
if(innerText==null) return;
|
||||
innerText.innerHTML=text;
|
||||
}
|
||||
|
||||
function demodulator_buttons_update()
|
||||
{
|
||||
$(".openwebrx-demodulator-button").removeClass("highlighted");
|
||||
if(secondary_demod) $("#openwebrx-button-dig").addClass("highlighted");
|
||||
else switch(demodulators[0].subtype)
|
||||
{
|
||||
case "nfm":
|
||||
$("#openwebrx-button-nfm").addClass("highlighted");
|
||||
break;
|
||||
case "am":
|
||||
$("#openwebrx-button-am").addClass("highlighted");
|
||||
break;
|
||||
case "lsb":
|
||||
case "usb":
|
||||
case "cw":
|
||||
if(demodulators[0].high_cut-demodulators[0].low_cut<300)
|
||||
$("#openwebrx-button-cw").addClass("highlighted");
|
||||
else
|
||||
{
|
||||
if(demodulators[0].high_cut<0)
|
||||
$("#openwebrx-button-lsb").addClass("highlighted");
|
||||
else if(demodulators[0].low_cut>0)
|
||||
$("#openwebrx-button-usb").addClass("highlighted");
|
||||
else $("#openwebrx-button-lsb, #openwebrx-button-usb").addClass("highlighted");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
function demodulator_analog_replace_last() { demodulator_analog_replace(last_analog_demodulator_subtype); }
|
||||
|
||||
/*
|
||||
_____ _ _ _
|
||||
| __ \(_) (_) | |
|
||||
| | | |_ __ _ _ _ __ ___ ___ __| | ___ ___
|
||||
| | | | |/ _` | | '_ ` _ \ / _ \ / _` |/ _ \/ __|
|
||||
| |__| | | (_| | | | | | | | (_) | (_| | __/\__ \
|
||||
|_____/|_|\__, |_|_| |_| |_|\___/ \__,_|\___||___/
|
||||
__/ |
|
||||
|___/
|
||||
*/
|
||||
|
||||
secondary_demod = false;
|
||||
secondary_demod_offset_freq = 0;
|
||||
secondary_demod_waterfall_queue = [];
|
||||
|
||||
function demodulator_digital_replace_last()
|
||||
{
|
||||
demodulator_digital_replace(last_digital_demodulator_subtype);
|
||||
secondary_demod_listbox_update();
|
||||
}
|
||||
function demodulator_digital_replace(subtype)
|
||||
{
|
||||
switch(subtype)
|
||||
{
|
||||
case "bpsk31":
|
||||
case "rtty":
|
||||
secondary_demod_start(subtype);
|
||||
demodulator_analog_replace('usb', true);
|
||||
demodulator_buttons_update();
|
||||
break;
|
||||
}
|
||||
toggle_panel("openwebrx-panel-digimodes", true);
|
||||
}
|
||||
|
||||
function secondary_demod_create_canvas()
|
||||
{
|
||||
var new_canvas = document.createElement("canvas");
|
||||
new_canvas.width=secondary_fft_size;
|
||||
new_canvas.height=$(secondary_demod_canvas_container).height();
|
||||
new_canvas.style.width=$(secondary_demod_canvas_container).width()+"px";
|
||||
new_canvas.style.height=$(secondary_demod_canvas_container).height()+"px";
|
||||
console.log(new_canvas.width, new_canvas.height, new_canvas.style.width, new_canvas.style.height);
|
||||
secondary_demod_current_canvas_actual_line=new_canvas.height-1;
|
||||
$(secondary_demod_canvas_container).children().last().before(new_canvas);
|
||||
return new_canvas;
|
||||
}
|
||||
|
||||
function secondary_demod_remove_canvases()
|
||||
{
|
||||
$(secondary_demod_canvas_container).children("canvas").remove();
|
||||
}
|
||||
|
||||
function secondary_demod_init_canvases()
|
||||
{
|
||||
secondary_demod_remove_canvases();
|
||||
secondary_demod_canvases=[];
|
||||
secondary_demod_canvases.push(secondary_demod_create_canvas());
|
||||
secondary_demod_canvases.push(secondary_demod_create_canvas());
|
||||
secondary_demod_canvases[0].openwebrx_top=-$(secondary_demod_canvas_container).height();
|
||||
secondary_demod_canvases[1].openwebrx_top=0;
|
||||
secondary_demod_canvases_update_top();
|
||||
secondary_demod_current_canvas_context = secondary_demod_canvases[0].getContext("2d");
|
||||
secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
|
||||
secondary_demod_current_canvas_index=0;
|
||||
secondary_demod_canvases_initialized=true;
|
||||
//secondary_demod_update_channel_freq_from_event();
|
||||
mkscale(); //so that the secondary waterfall zoom level will be initialized
|
||||
}
|
||||
|
||||
function secondary_demod_canvases_update_top()
|
||||
{
|
||||
for(var i=0;i<2;i++) secondary_demod_canvases[i].style.top=secondary_demod_canvases[i].openwebrx_top+"px";
|
||||
}
|
||||
|
||||
function secondary_demod_swap_canvases()
|
||||
{
|
||||
console.log("swap");
|
||||
secondary_demod_canvases[0+!secondary_demod_current_canvas_index].openwebrx_top-=$(secondary_demod_canvas_container).height()*2;
|
||||
secondary_demod_current_canvas_index=0+!secondary_demod_current_canvas_index;
|
||||
secondary_demod_current_canvas_context = secondary_demod_canvases[secondary_demod_current_canvas_index].getContext("2d");
|
||||
secondary_demod_current_canvas_actual_line=$(secondary_demod_canvas_container).height()-1;
|
||||
}
|
||||
|
||||
function secondary_demod_init()
|
||||
{
|
||||
$("#openwebrx-panel-digimodes")[0].openwebrxHidden = true;
|
||||
secondary_demod_canvas_container = $("#openwebrx-digimode-canvas-container")[0];
|
||||
$(secondary_demod_canvas_container)
|
||||
.mousemove(secondary_demod_canvas_container_mousemove)
|
||||
.mouseup(secondary_demod_canvas_container_mouseup)
|
||||
.mousedown(secondary_demod_canvas_container_mousedown)
|
||||
.mouseenter(secondary_demod_canvas_container_mousein)
|
||||
.mouseleave(secondary_demod_canvas_container_mouseout);
|
||||
}
|
||||
|
||||
function secondary_demod_start(subtype)
|
||||
{
|
||||
secondary_demod_canvases_initialized = false;
|
||||
ws.send("SET secondary_mod="+subtype);
|
||||
secondary_demod = subtype;
|
||||
}
|
||||
|
||||
function secondary_demod_set()
|
||||
{
|
||||
ws.send("SET secondary_offset_freq="+secondary_demod_offset_freq.toString());
|
||||
}
|
||||
|
||||
function secondary_demod_stop()
|
||||
{
|
||||
ws.send("SET secondary_mod=off");
|
||||
secondary_demod = false;
|
||||
secondary_demod_waterfall_queue = [];
|
||||
}
|
||||
|
||||
function secondary_demod_waterfall_add_queue(x)
|
||||
{
|
||||
secondary_demod_waterfall_queue.push(x);
|
||||
}
|
||||
|
||||
function secondary_demod_push_binary_data(x)
|
||||
{
|
||||
secondary_demod_push_data(Array.from(x).map( y => (y)?"1":"0" ).join(""));
|
||||
}
|
||||
|
||||
function secondary_demod_push_data(x)
|
||||
{
|
||||
x=Array.from(x).map((y)=>{
|
||||
var c=y.charCodeAt(0);
|
||||
if(y=="\r") return " ";
|
||||
if(y=="\n") return " ";
|
||||
//if(y=="\n") return "<br />";
|
||||
if(c<32||c>126) return "";
|
||||
if(y=="&") return "&";
|
||||
if(y=="<") return "<";
|
||||
if(y==">") return ">";
|
||||
if(y==" ") return " ";
|
||||
return y;
|
||||
}).join("");
|
||||
$("#openwebrx-cursor-blink").before("<span class=\"part\"><span class=\"subpart\">"+x+"</span></span>");
|
||||
}
|
||||
|
||||
function secondary_demod_data_clear()
|
||||
{
|
||||
$("#openwebrx-cursor-blink").prevAll().remove();
|
||||
}
|
||||
|
||||
function secondary_demod_close_window()
|
||||
{
|
||||
secondary_demod_stop();
|
||||
toggle_panel("openwebrx-panel-digimodes", false);
|
||||
}
|
||||
|
||||
secondary_demod_fft_offset_db=30; //need to calculate that later
|
||||
|
||||
function secondary_demod_waterfall_add(data)
|
||||
{
|
||||
if(!secondary_demod) return;
|
||||
var w=secondary_fft_size;
|
||||
|
||||
//Add line to waterfall image
|
||||
var oneline_image = secondary_demod_current_canvas_context.createImageData(w,1);
|
||||
for(x=0;x<w;x++)
|
||||
{
|
||||
var color=waterfall_mkcolor(data[x]+secondary_demod_fft_offset_db);
|
||||
for(i=0;i<4;i++) oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
|
||||
}
|
||||
|
||||
//Draw image
|
||||
secondary_demod_current_canvas_context.putImageData(oneline_image, 0, secondary_demod_current_canvas_actual_line--);
|
||||
secondary_demod_canvases.map((x)=>{x.openwebrx_top += 1;});
|
||||
secondary_demod_canvases_update_top();
|
||||
if(secondary_demod_current_canvas_actual_line<0) secondary_demod_swap_canvases();
|
||||
}
|
||||
|
||||
var secondary_demod_canvases_initialized = false;
|
||||
|
||||
function secondary_demod_waterfall_dequeue()
|
||||
{
|
||||
if(!secondary_demod || !secondary_demod_canvases_initialized) return;
|
||||
if(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
|
||||
if(secondary_demod_waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of fft overflow
|
||||
{
|
||||
console.log("secondary waterfall overflow, queue length:", secondary_demod_waterfall_queue.length);
|
||||
while(secondary_demod_waterfall_queue.length) secondary_demod_waterfall_add(secondary_demod_waterfall_queue.shift());
|
||||
}
|
||||
}
|
||||
|
||||
secondary_demod_listbox_updating = false;
|
||||
function secondary_demod_listbox_changed()
|
||||
{
|
||||
if(secondary_demod_listbox_updating) return;
|
||||
switch ($("#openwebrx-secondary-demod-listbox")[0].value)
|
||||
{
|
||||
case "none":
|
||||
demodulator_analog_replace_last();
|
||||
break;
|
||||
case "bpsk31":
|
||||
demodulator_digital_replace('bpsk31');
|
||||
break;
|
||||
case "rtty":
|
||||
demodulator_digital_replace('rtty');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function secondary_demod_listbox_update()
|
||||
{
|
||||
secondary_demod_listbox_updating = true;
|
||||
$("#openwebrx-secondary-demod-listbox").val((secondary_demod)?secondary_demod:"none");
|
||||
console.log("update");
|
||||
secondary_demod_listbox_updating = false;
|
||||
}
|
||||
|
||||
secondary_demod_channel_freq=1000;
|
||||
function secondary_demod_update_marker()
|
||||
{
|
||||
var width = Math.max( (secondary_bw / (if_samp_rate/2)) * secondary_demod_canvas_width, 5);
|
||||
var center_at = (secondary_demod_channel_freq / (if_samp_rate/2)) * secondary_demod_canvas_width + secondary_demod_canvas_left;
|
||||
var left = center_at-width/2;
|
||||
//console.log("sdum", width, left);
|
||||
$("#openwebrx-digimode-select-channel").width(width).css("left",left+"px")
|
||||
}
|
||||
|
||||
secondary_demod_waiting_for_set = false;
|
||||
function secondary_demod_update_channel_freq_from_event(evt)
|
||||
{
|
||||
if(typeof evt !== "undefined")
|
||||
{
|
||||
var relativeX=(evt.offsetX)?evt.offsetX:evt.layerX;
|
||||
secondary_demod_channel_freq=secondary_demod_low_cut +
|
||||
(relativeX/$(secondary_demod_canvas_container).width()) * (secondary_demod_high_cut-secondary_demod_low_cut);
|
||||
}
|
||||
//console.log("toset:", secondary_demod_channel_freq);
|
||||
if(!secondary_demod_waiting_for_set)
|
||||
{
|
||||
secondary_demod_waiting_for_set = true;
|
||||
window.setTimeout(()=>{
|
||||
ws.send("SET secondary_offset_freq="+Math.floor(secondary_demod_channel_freq));
|
||||
//console.log("doneset:", secondary_demod_channel_freq);
|
||||
secondary_demod_waiting_for_set = false;
|
||||
}, 50);
|
||||
}
|
||||
secondary_demod_update_marker();
|
||||
}
|
||||
|
||||
secondary_demod_mousedown=false;
|
||||
function secondary_demod_canvas_container_mousein()
|
||||
{
|
||||
$("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px");
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mouseout()
|
||||
{
|
||||
$("#openwebrx-digimode-select-channel").css("opacity","0");
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mousemove(evt)
|
||||
{
|
||||
if(secondary_demod_mousedown) secondary_demod_update_channel_freq_from_event(evt);
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mousedown(evt)
|
||||
{
|
||||
if(evt.which==1) secondary_demod_mousedown=true;
|
||||
}
|
||||
|
||||
function secondary_demod_canvas_container_mouseup(evt)
|
||||
{
|
||||
if(evt.which==1) secondary_demod_mousedown=false;
|
||||
secondary_demod_update_channel_freq_from_event(evt);
|
||||
}
|
||||
|
||||
|
||||
function secondary_demod_waterfall_set_zoom(low_cut, high_cut)
|
||||
{
|
||||
if(!secondary_demod || !secondary_demod_canvases_initialized) return;
|
||||
if(low_cut<0 && high_cut<0)
|
||||
{
|
||||
var hctmp = high_cut;
|
||||
var lctmp = low_cut;
|
||||
low_cut = -hctmp;
|
||||
low_cut = -lctmp;
|
||||
}
|
||||
else if(low_cut<0 && high_cut>0)
|
||||
{
|
||||
high_cut=Math.max(Math.abs(high_cut), Math.abs(low_cut));
|
||||
low_cut=0;
|
||||
}
|
||||
secondary_demod_low_cut = low_cut;
|
||||
secondary_demod_high_cut = high_cut;
|
||||
var shown_bw = high_cut-low_cut;
|
||||
secondary_demod_canvas_width = $(secondary_demod_canvas_container).width() * (if_samp_rate/2)/shown_bw;
|
||||
secondary_demod_canvas_left = -secondary_demod_canvas_width*(low_cut/(if_samp_rate/2));
|
||||
//console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
|
||||
secondary_demod_canvases.map((x)=>{$(x).css("left",secondary_demod_canvas_left+"px").css("width",secondary_demod_canvas_width+"px");});
|
||||
secondary_demod_update_channel_freq_from_event();
|
||||
}
|
||||
|
94
openwebrx.py
94
openwebrx.py
@ -20,14 +20,13 @@ print "" # python2.7 is required to run OpenWebRX instead of python3. Please run
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
sw_version="v0.15"
|
||||
sw_version="v0.17"
|
||||
#0.15 (added nmux)
|
||||
|
||||
import os
|
||||
import code
|
||||
import importlib
|
||||
import plugins
|
||||
import plugins.dsp
|
||||
import csdr
|
||||
import thread
|
||||
import time
|
||||
import datetime
|
||||
@ -63,6 +62,7 @@ try: import __pypy__
|
||||
except: pass
|
||||
pypy="__pypy__" in globals()
|
||||
|
||||
"""
|
||||
def import_all_plugins(directory):
|
||||
for subdir in os.listdir(directory):
|
||||
if os.path.isdir(directory+subdir) and not subdir[0]=="_":
|
||||
@ -71,6 +71,7 @@ def import_all_plugins(directory):
|
||||
importname=(directory+subdir+"/plugin").replace("/",".")
|
||||
print "[openwebrx-import] Found plugin:",importname
|
||||
importlib.import_module(importname)
|
||||
"""
|
||||
|
||||
class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
pass
|
||||
@ -91,7 +92,8 @@ def handle_signal(sig, frame):
|
||||
print
|
||||
for key in client._fields:
|
||||
print "\t%s = %s"%(key,str(getattr(client,key)))
|
||||
|
||||
elif sig == signal.SIGUSR2:
|
||||
code.interact(local=globals())
|
||||
else:
|
||||
print "[openwebrx] Ctrl+C: aborting."
|
||||
cleanup_clients(True)
|
||||
@ -127,9 +129,7 @@ def main():
|
||||
#Set signal handler
|
||||
signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python
|
||||
signal.signal(signal.SIGUSR1, handle_signal)
|
||||
|
||||
#Load plugins
|
||||
import_all_plugins("plugins/dsp/")
|
||||
signal.signal(signal.SIGUSR2, handle_signal)
|
||||
|
||||
#Pypy
|
||||
if pypy: print "pypy detected (and now something completely different: c code is expected to run at a speed of 3*10^8 m/s?)"
|
||||
@ -292,7 +292,7 @@ def apply_csdr_cfg_to_dsp(dsp):
|
||||
|
||||
def spectrum_thread_function():
|
||||
global clients, spectrum_dsp, spectrum_thread_watchdog_last_tick
|
||||
spectrum_dsp=dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
|
||||
spectrum_dsp=dsp=csdr.dsp()
|
||||
dsp.nc_port=cfg.iq_server_port
|
||||
dsp.set_demodulator("fft")
|
||||
dsp.set_samp_rate(cfg.samp_rate)
|
||||
@ -427,6 +427,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
# there's even another cool tip at http://stackoverflow.com/questions/4419650/how-to-implement-timeout-in-basehttpserver-basehttprequesthandler-python
|
||||
#if self.path[:5]=="/lock": cma("do_GET /lock/") # to test mutex_watchdog_thread. Do not uncomment in production environment!
|
||||
if self.path[:4]=="/ws/":
|
||||
print "[openwebrx-ws] Client requested WebSocket connection"
|
||||
if receiver_failed: self.send_error(500,"Internal server error")
|
||||
try:
|
||||
# ========= WebSocket handshake =========
|
||||
@ -459,21 +460,22 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients))
|
||||
|
||||
# ========= Initialize DSP =========
|
||||
dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin()
|
||||
dsp=csdr.dsp()
|
||||
dsp_initialized=False
|
||||
dsp.set_audio_compression(cfg.audio_compression)
|
||||
dsp.set_fft_compression(cfg.fft_compression) #used by secondary chains
|
||||
dsp.set_format_conversion(cfg.format_conversion)
|
||||
dsp.set_offset_freq(0)
|
||||
dsp.set_bpf(-4000,4000)
|
||||
dsp.set_secondary_fft_size(cfg.digimodes_fft_size)
|
||||
dsp.nc_port=cfg.iq_server_port
|
||||
apply_csdr_cfg_to_dsp(dsp)
|
||||
myclient.dsp=dsp
|
||||
|
||||
do_secondary_demod=False
|
||||
access_log("Started streaming to client: "+self.client_address[0]+"#"+myclient.id+" (users now: "+str(len(clients))+")")
|
||||
|
||||
myclient.loopstat=0
|
||||
|
||||
while True:
|
||||
myclient.loopstat=0
|
||||
if myclient.closed[0]:
|
||||
print "[openwebrx-httpd:ws] client closed by other thread"
|
||||
break
|
||||
@ -514,12 +516,34 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
rxws.send(self,myclient.bcastmsg)
|
||||
myclient.bcastmsg=""
|
||||
|
||||
# ========= send secondary =========
|
||||
if do_secondary_demod:
|
||||
myclient.loopstat=41
|
||||
while True:
|
||||
try:
|
||||
secondary_spectrum_data=dsp.read_secondary_fft(dsp.get_secondary_fft_bytes_to_read())
|
||||
if len(secondary_spectrum_data) == 0: break
|
||||
# print "len(secondary_spectrum_data)", len(secondary_spectrum_data) #TODO digimodes
|
||||
rxws.send(self, secondary_spectrum_data, "FFTS")
|
||||
except: break
|
||||
myclient.loopstat=42
|
||||
while True:
|
||||
try:
|
||||
myclient.loopstat=422
|
||||
secondary_demod_data=dsp.read_secondary_demod(1)
|
||||
myclient.loopstat=423
|
||||
if len(secondary_demod_data) == 0: break
|
||||
# print "len(secondary_demod_data)", len(secondary_demod_data), secondary_demod_data #TODO digimodes
|
||||
rxws.send(self, secondary_demod_data, "DAT ")
|
||||
except: break
|
||||
|
||||
# ========= process commands =========
|
||||
while True:
|
||||
myclient.loopstat=50
|
||||
rdata=rxws.recv(self, False)
|
||||
if not rdata: break
|
||||
myclient.loopstat=51
|
||||
#try:
|
||||
if not rdata: break
|
||||
elif rdata[:3]=="SET":
|
||||
print "[openwebrx-httpd:ws,%d] command: %s"%(client_i,rdata)
|
||||
pairs=rdata[4:].split(" ")
|
||||
@ -528,13 +552,13 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
filter_limit=dsp.get_output_rate()/2
|
||||
for pair in pairs:
|
||||
param_name, param_value = pair.split("=")
|
||||
if param_name == "low_cut" and -filter_limit <= float(param_value) <= filter_limit:
|
||||
if param_name == "low_cut" and -filter_limit <= int(param_value) <= filter_limit:
|
||||
bpf_set=True
|
||||
new_bpf[0]=int(param_value)
|
||||
elif param_name == "high_cut" and -filter_limit <= float(param_value) <= filter_limit:
|
||||
elif param_name == "high_cut" and -filter_limit <= int(param_value) <= filter_limit:
|
||||
bpf_set=True
|
||||
new_bpf[1]=int(param_value)
|
||||
elif param_name == "offset_freq" and -cfg.samp_rate/2 <= float(param_value) <= cfg.samp_rate/2:
|
||||
elif param_name == "offset_freq" and -cfg.samp_rate/2 <= int(param_value) <= cfg.samp_rate/2:
|
||||
myclient.loopstat=510
|
||||
dsp.set_offset_freq(int(param_value))
|
||||
elif param_name == "squelch_level" and float(param_value) >= 0:
|
||||
@ -557,6 +581,19 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
myclient.loopstat=550
|
||||
dsp.start()
|
||||
dsp_initialized=True
|
||||
elif param_name=="secondary_mod" and cfg.digimodes_enable:
|
||||
if (dsp.get_secondary_demodulator() != param_value):
|
||||
if dsp_initialized: dsp.stop()
|
||||
if param_value == "off":
|
||||
dsp.set_secondary_demodulator(None)
|
||||
do_secondary_demod = False
|
||||
else:
|
||||
dsp.set_secondary_demodulator(param_value)
|
||||
do_secondary_demod = True
|
||||
rxws.send(self, "MSG secondary_fft_size={0} if_samp_rate={1} secondary_bw={2} secondary_setup".format(cfg.digimodes_fft_size, dsp.if_samp_rate(), dsp.secondary_bw()))
|
||||
if dsp_initialized: dsp.start()
|
||||
elif param_name=="secondary_offset_freq" and 0 <= int(param_value) <= dsp.if_samp_rate()/2 and cfg.digimodes_enable:
|
||||
dsp.set_secondary_offset_freq(int(param_value))
|
||||
else:
|
||||
print "[openwebrx-httpd:ws] invalid parameter"
|
||||
if bpf_set:
|
||||
@ -564,16 +601,20 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
dsp.set_bpf(*new_bpf)
|
||||
#code.interact(local=locals())
|
||||
except:
|
||||
myclient.loopstat=990
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
if exc_value[0]==32: #"broken pipe", client disconnected
|
||||
pass
|
||||
elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected
|
||||
pass
|
||||
else:
|
||||
print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value
|
||||
traceback.print_tb(exc_traceback)
|
||||
print "[openwebrx-httpd:ws] exception: ",exc_type,exc_value
|
||||
traceback.print_tb(exc_traceback) #TODO digimodes
|
||||
#if exc_value[0]==32: #"broken pipe", client disconnected
|
||||
# pass
|
||||
#elif exc_value[0]==11: #"resource unavailable" on recv, client disconnected
|
||||
# pass
|
||||
#else:
|
||||
# print "[openwebrx-httpd] error in /ws/ handler: ",exc_type,exc_value
|
||||
# traceback.print_tb(exc_traceback)
|
||||
|
||||
#stop dsp for the disconnected client
|
||||
myclient.loopstat=991
|
||||
try:
|
||||
dsp.stop()
|
||||
del dsp
|
||||
@ -581,6 +622,7 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
print "[openwebrx-httpd] error in dsp.stop()"
|
||||
|
||||
#delete disconnected client
|
||||
myclient.loopstat=992
|
||||
try:
|
||||
cma("do_GET /ws/ delete disconnected")
|
||||
id_to_close=get_client_by_id(myclient.id,False)
|
||||
@ -644,7 +686,11 @@ class WebRXHandler(BaseHTTPRequestHandler):
|
||||
("%[WATERFALL_COLORS]",cfg.waterfall_colors),
|
||||
("%[WATERFALL_MIN_LEVEL]",str(cfg.waterfall_min_level)),
|
||||
("%[WATERFALL_MAX_LEVEL]",str(cfg.waterfall_max_level)),
|
||||
("%[WATERFALL_AUTO_LEVEL_MARGIN]","[%d,%d]"%cfg.waterfall_auto_level_margin)
|
||||
("%[WATERFALL_AUTO_LEVEL_MARGIN]","[%d,%d]"%cfg.waterfall_auto_level_margin),
|
||||
("%[DIGIMODES_ENABLE]",("true" if cfg.digimodes_enable else "false")),
|
||||
("%[MATHBOX_WATERFALL_FRES]",str(cfg.mathbox_waterfall_frequency_resolution)),
|
||||
("%[MATHBOX_WATERFALL_THIST]",str(cfg.mathbox_waterfall_history_length)),
|
||||
("%[MATHBOX_WATERFALL_COLORS]",cfg.mathbox_waterfall_colors)
|
||||
)
|
||||
for rule in replace_dictionary:
|
||||
while data.find(rule[0])!=-1:
|
||||
|
@ -1,251 +0,0 @@
|
||||
"""
|
||||
OpenWebRX csdr plugin: do the signal processing with csdr
|
||||
|
||||
This file is part of OpenWebRX,
|
||||
an open-source SDR receiver software with a web UI.
|
||||
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import code
|
||||
import signal
|
||||
import fcntl
|
||||
|
||||
class dsp_plugin:
|
||||
|
||||
def __init__(self):
|
||||
self.samp_rate = 250000
|
||||
self.output_rate = 11025 #this is default, and cannot be set at the moment
|
||||
self.fft_size = 1024
|
||||
self.fft_fps = 5
|
||||
self.offset_freq = 0
|
||||
self.low_cut = -4000
|
||||
self.high_cut = 4000
|
||||
self.bpf_transition_bw = 320 #Hz, and this is a constant
|
||||
self.ddc_transition_bw_rate = 0.15 # of the IF sample rate
|
||||
self.running = False
|
||||
self.audio_compression = "none"
|
||||
self.fft_compression = "none"
|
||||
self.demodulator = "nfm"
|
||||
self.name = "csdr"
|
||||
self.format_conversion = "csdr convert_u8_f"
|
||||
self.base_bufsize = 512
|
||||
self.nc_port = 4951
|
||||
self.csdr_dynamic_bufsize = False
|
||||
self.csdr_print_bufsizes = False
|
||||
self.csdr_through = False
|
||||
self.squelch_level = 0
|
||||
self.fft_averages = 50
|
||||
|
||||
def chain(self,which):
|
||||
any_chain_base="nc -v 127.0.0.1 {nc_port} | "
|
||||
if self.csdr_dynamic_bufsize: any_chain_base+="csdr setbuf {start_bufsize} | "
|
||||
if self.csdr_through: any_chain_base+="csdr through | "
|
||||
any_chain_base+=self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | "
|
||||
if which == "fft":
|
||||
fft_chain_base = any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | " + \
|
||||
("csdr logpower_cf -70 | " if self.fft_averages == 0 else "csdr logaveragepower_cf -70 {fft_size} {fft_averages} | ") + \
|
||||
"csdr fft_exchange_sides_ff {fft_size}"
|
||||
if self.fft_compression=="adpcm":
|
||||
return fft_chain_base+" | csdr compress_fft_adpcm_f_u8 {fft_size}"
|
||||
else:
|
||||
return fft_chain_base
|
||||
chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | "
|
||||
chain_end = ""
|
||||
if self.audio_compression=="adpcm":
|
||||
chain_end = " | csdr encode_ima_adpcm_i16_u8"
|
||||
if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff 1024 | csdr convert_f_s16"+chain_end
|
||||
elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end
|
||||
|
||||
def set_audio_compression(self,what):
|
||||
self.audio_compression = what
|
||||
|
||||
def set_fft_compression(self,what):
|
||||
self.fft_compression = what
|
||||
|
||||
def get_fft_bytes_to_read(self):
|
||||
if self.fft_compression=="none": return self.fft_size*4
|
||||
if self.fft_compression=="adpcm": return (self.fft_size/2)+(10/2)
|
||||
|
||||
def set_samp_rate(self,samp_rate):
|
||||
#to change this, restart is required
|
||||
self.samp_rate=samp_rate
|
||||
self.decimation=1
|
||||
while self.samp_rate/(self.decimation+1)>self.output_rate:
|
||||
self.decimation+=1
|
||||
self.last_decimation=float(self.if_samp_rate())/self.output_rate
|
||||
|
||||
def if_samp_rate(self):
|
||||
return self.samp_rate/self.decimation
|
||||
|
||||
def get_name(self):
|
||||
return self.name
|
||||
|
||||
def get_output_rate(self):
|
||||
return self.output_rate
|
||||
|
||||
def set_output_rate(self,output_rate):
|
||||
self.output_rate=output_rate
|
||||
self.set_samp_rate(self.samp_rate) #as it depends on output_rate
|
||||
|
||||
def set_demodulator(self,demodulator):
|
||||
#to change this, restart is required
|
||||
self.demodulator=demodulator
|
||||
|
||||
def get_demodulator(self):
|
||||
return self.demodulator
|
||||
|
||||
def set_fft_size(self,fft_size):
|
||||
#to change this, restart is required
|
||||
self.fft_size=fft_size
|
||||
|
||||
def set_fft_fps(self,fft_fps):
|
||||
#to change this, restart is required
|
||||
self.fft_fps=fft_fps
|
||||
|
||||
def set_fft_averages(self,fft_averages):
|
||||
#to change this, restart is required
|
||||
self.fft_averages=fft_averages
|
||||
|
||||
def fft_block_size(self):
|
||||
if self.fft_averages == 0: return self.samp_rate/self.fft_fps
|
||||
else: return self.samp_rate/self.fft_fps/self.fft_averages
|
||||
|
||||
def set_format_conversion(self,format_conversion):
|
||||
self.format_conversion=format_conversion
|
||||
|
||||
def set_offset_freq(self,offset_freq):
|
||||
self.offset_freq=offset_freq
|
||||
if self.running:
|
||||
self.shift_pipe_file.write("%g\n"%(-float(self.offset_freq)/self.samp_rate))
|
||||
self.shift_pipe_file.flush()
|
||||
|
||||
def set_bpf(self,low_cut,high_cut):
|
||||
self.low_cut=low_cut
|
||||
self.high_cut=high_cut
|
||||
if self.running:
|
||||
self.bpf_pipe_file.write( "%g %g\n"%(float(self.low_cut)/self.if_samp_rate(), float(self.high_cut)/self.if_samp_rate()) )
|
||||
self.bpf_pipe_file.flush()
|
||||
|
||||
def get_bpf(self):
|
||||
return [self.low_cut, self.high_cut]
|
||||
|
||||
def set_squelch_level(self, squelch_level):
|
||||
self.squelch_level=squelch_level
|
||||
if self.running:
|
||||
self.squelch_pipe_file.write( "%g\n"%(float(self.squelch_level)) )
|
||||
self.squelch_pipe_file.flush()
|
||||
|
||||
def get_smeter_level(self):
|
||||
if self.running:
|
||||
line=self.smeter_pipe_file.readline()
|
||||
return float(line[:-1])
|
||||
|
||||
def mkfifo(self,path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
pass
|
||||
os.mkfifo(path)
|
||||
|
||||
def ddc_transition_bw(self):
|
||||
return self.ddc_transition_bw_rate*(self.if_samp_rate()/float(self.samp_rate))
|
||||
|
||||
def start(self):
|
||||
command_base=self.chain(self.demodulator)
|
||||
|
||||
#create control pipes for csdr
|
||||
pipe_base_path="/tmp/openwebrx_pipe_{myid}_".format(myid=id(self))
|
||||
self.bpf_pipe = self.shift_pipe = self.squelch_pipe = self.smeter_pipe = None
|
||||
if "{bpf_pipe}" in command_base:
|
||||
self.bpf_pipe=pipe_base_path+"bpf"
|
||||
self.mkfifo(self.bpf_pipe)
|
||||
if "{shift_pipe}" in command_base:
|
||||
self.shift_pipe=pipe_base_path+"shift"
|
||||
self.mkfifo(self.shift_pipe)
|
||||
if "{squelch_pipe}" in command_base:
|
||||
self.squelch_pipe=pipe_base_path+"squelch"
|
||||
self.mkfifo(self.squelch_pipe)
|
||||
if "{smeter_pipe}" in command_base:
|
||||
self.smeter_pipe=pipe_base_path+"smeter"
|
||||
self.mkfifo(self.smeter_pipe)
|
||||
|
||||
#run the command
|
||||
command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \
|
||||
last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), fft_averages=self.fft_averages, \
|
||||
bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \
|
||||
flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port, \
|
||||
squelch_pipe=self.squelch_pipe, smeter_pipe=self.smeter_pipe )
|
||||
|
||||
print "[openwebrx-dsp-plugin:csdr] Command =",command
|
||||
#code.interact(local=locals())
|
||||
my_env=os.environ.copy()
|
||||
if self.csdr_dynamic_bufsize: my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1";
|
||||
if self.csdr_print_bufsizes: my_env["CSDR_PRINT_BUFSIZES"]="1";
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env)
|
||||
self.running = True
|
||||
|
||||
#open control pipes for csdr and send initialization data
|
||||
if self.bpf_pipe != None:
|
||||
self.bpf_pipe_file=open(self.bpf_pipe,"w")
|
||||
self.set_bpf(self.low_cut,self.high_cut)
|
||||
if self.shift_pipe != None:
|
||||
self.shift_pipe_file=open(self.shift_pipe,"w")
|
||||
self.set_offset_freq(self.offset_freq)
|
||||
if self.squelch_pipe != None:
|
||||
self.squelch_pipe_file=open(self.squelch_pipe,"w")
|
||||
self.set_squelch_level(self.squelch_level)
|
||||
if self.smeter_pipe != None:
|
||||
self.smeter_pipe_file=open(self.smeter_pipe,"r")
|
||||
fcntl.fcntl(self.smeter_pipe_file, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
|
||||
def read(self,size):
|
||||
return self.process.stdout.read(size)
|
||||
|
||||
def stop(self):
|
||||
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)
|
||||
#if(self.process.poll()!=None):return # returns None while subprocess is running
|
||||
#while(self.process.poll()==None):
|
||||
# #self.process.kill()
|
||||
# print "killproc",os.getpgid(self.process.pid),self.process.pid
|
||||
# os.killpg(self.process.pid, signal.SIGTERM)
|
||||
#
|
||||
# time.sleep(0.1)
|
||||
if self.bpf_pipe:
|
||||
try: os.unlink(self.bpf_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe
|
||||
if self.shift_pipe:
|
||||
try: os.unlink(self.shift_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.shift_pipe
|
||||
if self.squelch_pipe:
|
||||
try: os.unlink(self.squelch_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.squelch_pipe
|
||||
if self.smeter_pipe:
|
||||
try: os.unlink(self.smeter_pipe)
|
||||
except: print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.smeter_pipe
|
||||
self.running = False
|
||||
|
||||
def restart(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
del(self.process)
|
Binary file not shown.
Before Width: | Height: | Size: 511 KiB |
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 1.4 MiB |
Loading…
Reference in New Issue
Block a user