2014-11-29 00:07:10 +00:00
/ *
2016-03-20 10:32:37 +00:00
This file is part of OpenWebRX ,
2015-08-17 18:32:58 +00:00
an open - source SDR receiver software with a web UI .
Copyright ( c ) 2013 - 2015 by Andras Retzler < randras @ sdr . hu >
2019-11-12 12:43:39 +00:00
Copyright ( c ) 2019 by Jakob Ketterl < dd5jfk @ darc . de >
2014-11-29 00:07:10 +00:00
2015-08-17 18:32:58 +00:00
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 .
2014-11-29 00:07:10 +00:00
2015-08-17 18:32:58 +00:00
This program is distributed in the hope that it will be useful ,
2014-11-29 00:07:10 +00:00
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2015-08-17 18:32:58 +00:00
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/>.
2014-11-29 00:07:10 +00:00
2015-08-17 18:32:58 +00:00
"" "
2014-11-29 00:07:10 +00:00
* /
2019-10-16 11:17:47 +00:00
is _firefox = navigator . userAgent . indexOf ( "Firefox" ) >= 0 ;
2014-11-29 00:07:10 +00:00
var bandwidth ;
var center _freq ;
var fft _size ;
var fft _fps ;
2019-10-16 11:17:47 +00:00
var fft _compression = "none" ;
var fft _codec = new sdrjs . ImaAdpcm ( ) ;
var waterfall _setup _done = 0 ;
2018-09-25 12:56:47 +00:00
var secondary _fft _size ;
2019-10-16 11:17:47 +00:00
var rx _photo _state = 1 ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function e ( what ) {
return document . getElementById ( what ) ;
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
var rx _photo _height ;
function init _rx _photo ( ) {
2019-05-04 14:56:23 +00:00
var clip = e ( "webrx-top-photo-clip" ) ;
2019-10-16 11:17:47 +00:00
rx _photo _height = clip . clientHeight ;
clip . style . maxHeight = rx _photo _height + "px" ;
2019-10-25 17:21:49 +00:00
$ . extend ( $ . easing , {
easeOutCubic : function ( x ) {
return 1 - Math . pow ( 1 - x , 3 ) ;
}
} ) ;
2019-10-16 11:17:47 +00:00
window . setTimeout ( function ( ) {
2019-10-24 17:35:23 +00:00
$ ( '#webrx-rx-photo-title' ) . animate ( { opacity : 0 } , 500 ) ;
2019-10-16 11:17:47 +00:00
} , 1000 ) ;
window . setTimeout ( function ( ) {
2019-10-24 17:35:23 +00:00
$ ( '#webrx-rx-photo-desc' ) . animate ( { opacity : 0 } , 500 ) ;
2019-10-16 11:17:47 +00:00
} , 1500 ) ;
window . setTimeout ( function ( ) {
close _rx _photo ( )
} , 2500 ) ;
$ ( '#webrx-top-container' ) . find ( '.openwebrx-photo-trigger' ) . click ( toggle _rx _photo ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var dont _toggle _rx _photo _flag = 0 ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function dont _toggle _rx _photo ( ) {
dont _toggle _rx _photo _flag = 1 ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function toggle _rx _photo ( ) {
if ( dont _toggle _rx _photo _flag ) {
dont _toggle _rx _photo _flag = 0 ;
return ;
}
if ( rx _photo _state ) close _rx _photo ( ) ;
else open _rx _photo ( )
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function close _rx _photo ( ) {
rx _photo _state = 0 ;
2019-10-26 19:32:00 +00:00
$ ( '#webrx-top-photo-clip' ) . animate ( { maxHeight : 67 } , { duration : 1000 , easing : 'easeOutCubic' } ) ;
2019-10-16 11:17:47 +00:00
e ( "openwebrx-rx-details-arrow-down" ) . style . display = "block" ;
e ( "openwebrx-rx-details-arrow-up" ) . style . display = "none" ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function open _rx _photo ( ) {
rx _photo _state = 1 ;
e ( "webrx-rx-photo-desc" ) . style . opacity = 1 ;
e ( "webrx-rx-photo-title" ) . style . opacity = 1 ;
2019-10-26 19:32:00 +00:00
$ ( '#webrx-top-photo-clip' ) . animate ( { maxHeight : rx _photo _height } , { duration : 1000 , easing : 'easeOutCubic' } ) ;
2019-10-16 11:17:47 +00:00
e ( "openwebrx-rx-details-arrow-down" ) . style . display = "none" ;
e ( "openwebrx-rx-details-arrow-up" ) . style . display = "block" ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function updateVolume ( ) {
2019-10-20 16:53:23 +00:00
audioEngine . setVolume ( parseFloat ( e ( "openwebrx-panel-volume" ) . value ) / 100 ) ;
2016-02-06 13:49:10 +00:00
}
2019-10-16 11:17:47 +00:00
function toggleMute ( ) {
if ( mute ) {
mute = false ;
e ( "openwebrx-mute-on" ) . id = "openwebrx-mute-off" ;
e ( "openwebrx-mute-img" ) . src = "static/gfx/openwebrx-speaker.png" ;
e ( "openwebrx-panel-volume" ) . disabled = false ;
e ( "openwebrx-panel-volume" ) . style . opacity = 1.0 ;
e ( "openwebrx-panel-volume" ) . value = volumeBeforeMute ;
} else {
mute = true ;
e ( "openwebrx-mute-off" ) . id = "openwebrx-mute-on" ;
e ( "openwebrx-mute-img" ) . src = "static/gfx/openwebrx-speaker-muted.png" ;
e ( "openwebrx-panel-volume" ) . disabled = true ;
e ( "openwebrx-panel-volume" ) . style . opacity = 0.5 ;
volumeBeforeMute = e ( "openwebrx-panel-volume" ) . value ;
e ( "openwebrx-panel-volume" ) . value = 0 ;
}
updateVolume ( ) ;
2016-03-21 08:10:41 +00:00
}
2016-02-06 13:49:10 +00:00
2019-10-16 11:17:47 +00:00
function zoomInOneStep ( ) {
zoom _set ( zoom _level + 1 ) ;
2016-03-20 10:32:37 +00:00
}
2019-10-16 11:17:47 +00:00
function zoomOutOneStep ( ) {
zoom _set ( zoom _level - 1 ) ;
2016-03-20 10:32:37 +00:00
}
2016-02-06 13:49:10 +00:00
2019-10-16 11:17:47 +00:00
function zoomInTotal ( ) {
zoom _set ( zoom _levels . length - 1 ) ;
2016-03-20 15:06:10 +00:00
}
2019-10-16 11:17:47 +00:00
function zoomOutTotal ( ) {
zoom _set ( 0 ) ;
2016-03-21 08:10:41 +00:00
}
2019-10-16 11:17:47 +00:00
function setSquelchToAuto ( ) {
e ( "openwebrx-panel-squelch" ) . value = ( getLogSmeterValue ( smeter _level ) + 10 ) . toString ( ) ;
updateSquelch ( ) ;
2016-03-20 15:06:10 +00:00
}
2019-10-16 11:17:47 +00:00
function updateSquelch ( ) {
2019-11-23 15:56:29 +00:00
var sliderValue = parseInt ( $ ( "#openwebrx-panel-squelch" ) . val ( ) ) ;
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "squelch_level" : sliderValue } } ) ) ;
2019-10-16 11:17:47 +00:00
}
2019-10-16 15:11:09 +00:00
var waterfall _min _level ;
var waterfall _max _level ;
2019-10-20 21:38:58 +00:00
var waterfall _min _level _default ;
var waterfall _max _level _default ;
var waterfall _colors ;
var waterfall _auto _level _margin ;
2019-10-16 15:11:09 +00:00
2019-10-16 11:17:47 +00:00
function updateWaterfallColors ( which ) {
2019-10-16 15:11:09 +00:00
var wfmax = e ( "openwebrx-waterfall-color-max" ) ;
var wfmin = e ( "openwebrx-waterfall-color-min" ) ;
2019-10-16 11:17:47 +00:00
if ( parseInt ( wfmin . value ) >= parseInt ( wfmax . value ) ) {
if ( ! which ) wfmin . value = ( parseInt ( wfmax . value ) - 1 ) . toString ( ) ;
else wfmax . value = ( parseInt ( wfmin . value ) + 1 ) . toString ( ) ;
}
waterfall _min _level = parseInt ( wfmin . value ) ;
waterfall _max _level = parseInt ( wfmax . value ) ;
}
function waterfallColorsDefault ( ) {
waterfall _min _level = waterfall _min _level _default ;
waterfall _max _level = waterfall _max _level _default ;
e ( "openwebrx-waterfall-color-min" ) . value = waterfall _min _level . toString ( ) ;
e ( "openwebrx-waterfall-color-max" ) . value = waterfall _max _level . toString ( ) ;
}
function waterfallColorsAuto ( ) {
e ( "openwebrx-waterfall-color-min" ) . value = ( waterfall _measure _minmax _min - waterfall _auto _level _margin [ 0 ] ) . toString ( ) ;
e ( "openwebrx-waterfall-color-max" ) . value = ( waterfall _measure _minmax _max + waterfall _auto _level _margin [ 1 ] ) . toString ( ) ;
updateWaterfallColors ( 0 ) ;
}
function setSmeterRelativeValue ( value ) {
if ( value < 0 ) value = 0 ;
if ( value > 1.0 ) value = 1.0 ;
var bar = e ( "openwebrx-smeter-bar" ) ;
var outer = e ( "openwebrx-smeter-outer" ) ;
bar . style . width = ( outer . offsetWidth * value ) . toString ( ) + "px" ;
2019-10-16 15:11:09 +00:00
var bgRed = "linear-gradient(to top, #ff5939 , #961700)" ;
var bgGreen = "linear-gradient(to top, #22ff2f , #008908)" ;
var bgYellow = "linear-gradient(to top, #fff720 , #a49f00)" ;
2019-10-16 11:17:47 +00:00
bar . style . background = ( value > 0.9 ) ? bgRed : ( ( value > 0.7 ) ? bgYellow : bgGreen ) ;
}
2019-10-27 15:04:00 +00:00
function setSquelchSliderBackground ( val ) {
var $slider = $ ( '#openwebrx-panel-squelch' ) ;
var min = Number ( $slider . attr ( 'min' ) ) ;
var max = Number ( $slider . attr ( 'max' ) ) ;
var sliderPosition = $slider . val ( ) ;
var relative = ( val - min ) / ( max - min ) ;
// use a brighter color when squelch is open
var color = val >= sliderPosition ? '#22ff2f' : '#008908' ;
2019-10-28 19:54:31 +00:00
// we don't use the gradient, but separate the colors discretely using css tricks
var style = 'linear-gradient(90deg, ' + color + ', ' + color + ' ' + relative * 100 + '%, #B6B6B6 ' + relative * 100 + '%)' ;
2019-10-27 15:04:00 +00:00
$slider . css ( '--track-background' , style ) ;
}
2019-10-16 11:17:47 +00:00
function getLogSmeterValue ( value ) {
return 10 * Math . log10 ( value ) ;
}
function getLinearSmeterValue ( db _value ) {
return Math . pow ( 10 , db _value / 10 ) ;
}
function setSmeterAbsoluteValue ( value ) //the value that comes from `csdr squelch_and_smeter_cc`
2016-03-21 09:09:06 +00:00
{
2019-10-16 11:17:47 +00:00
var logValue = getLogSmeterValue ( value ) ;
2019-10-27 15:04:00 +00:00
setSquelchSliderBackground ( logValue ) ;
2019-10-16 11:17:47 +00:00
var lowLevel = waterfall _min _level - 20 ;
var highLevel = waterfall _max _level + 20 ;
var percent = ( logValue - lowLevel ) / ( highLevel - lowLevel ) ;
setSmeterRelativeValue ( percent ) ;
e ( "openwebrx-smeter-db" ) . innerHTML = logValue . toFixed ( 1 ) + " dB" ;
2016-03-21 09:09:06 +00:00
}
2019-10-16 11:17:47 +00:00
function typeInAnimation ( element , timeout , what , onFinish ) {
if ( ! what ) {
onFinish ( ) ;
return ;
}
element . innerHTML += what [ 0 ] ;
window . setTimeout ( function ( ) {
typeInAnimation ( element , timeout , what . substring ( 1 ) , onFinish ) ;
} , timeout ) ;
}
2016-03-21 09:09:06 +00:00
2014-11-29 00:07:10 +00:00
// ========================================================
// ================ DEMODULATOR ROUTINES ================
// ========================================================
2019-10-16 11:17:47 +00:00
demodulators = [ ] ;
2019-10-16 15:11:09 +00:00
var demodulator _color _index = 0 ;
var demodulator _colors = [ "#ffff00" , "#00ff00" , "#00ffff" , "#058cff" , "#ff9600" , "#a1ff39" , "#ff4e39" , "#ff5dbd" ] ;
2019-10-16 11:17:47 +00:00
function demodulators _get _next _color ( ) {
if ( demodulator _color _index >= demodulator _colors . length ) demodulator _color _index = 0 ;
return ( demodulator _colors [ demodulator _color _index ++ ] ) ;
}
function demod _envelope _draw ( range , from , to , color , line ) { // ____
// Draws a standard filter envelope like this: _/ \_
// Parameters are given in offset frequency (Hz).
// Envelope is drawn on the scale canvas.
// A "drag range" object is returned, containing information about the draggable areas of the envelope
// (beginning, ending and the line showing the offset frequency).
if ( typeof color === "undefined" ) color = "#ffff00" ; //yellow
2019-10-16 15:11:09 +00:00
var env _bounding _line _w = 5 ; //
var env _att _w = 5 ; // _______ ___env_h2 in px ___|_____
var env _h1 = 17 ; // _/| \_ ___env_h1 in px _/ |_ \_
var env _h2 = 5 ; // |||env_att_line_w |_env_lineplus
var env _lineplus = 1 ; // ||env_bounding_line_w
var env _line _click _area = 6 ;
2019-10-16 11:17:47 +00:00
//range=get_visible_freq_range();
2019-10-16 15:11:09 +00:00
var from _px = scale _px _from _freq ( from , range ) ;
var to _px = scale _px _from _freq ( to , range ) ;
2019-10-16 11:17:47 +00:00
if ( to _px < from _px ) /* swap'em */ {
2019-10-16 15:11:09 +00:00
var temp _px = to _px ;
2019-10-16 11:17:47 +00:00
to _px = from _px ;
from _px = temp _px ;
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
/ * f r o m _ p x - = e n v _ b o u n d i n g _ l i n e _ w / 2 ;
2014-11-29 00:07:10 +00:00
to _px += env _bounding _line _w / 2 ; * /
2019-10-16 11:17:47 +00:00
from _px -= ( env _att _w + env _bounding _line _w ) ;
to _px += ( env _att _w + env _bounding _line _w ) ;
// do drawing:
scale _ctx . lineWidth = 3 ;
scale _ctx . strokeStyle = color ;
scale _ctx . fillStyle = color ;
var drag _ranges = { envelope _on _screen : false , line _on _screen : false } ;
if ( ! ( to _px < 0 || from _px > window . innerWidth ) ) // out of screen?
{
drag _ranges . beginning = { x1 : from _px , x2 : from _px + env _bounding _line _w + env _att _w } ;
drag _ranges . ending = { x1 : to _px - env _bounding _line _w - env _att _w , x2 : to _px } ;
drag _ranges . whole _envelope = { x1 : from _px , x2 : to _px } ;
drag _ranges . envelope _on _screen = true ;
scale _ctx . beginPath ( ) ;
scale _ctx . moveTo ( from _px , env _h1 ) ;
scale _ctx . lineTo ( from _px + env _bounding _line _w , env _h1 ) ;
scale _ctx . lineTo ( from _px + env _bounding _line _w + env _att _w , env _h2 ) ;
scale _ctx . lineTo ( to _px - env _bounding _line _w - env _att _w , env _h2 ) ;
scale _ctx . lineTo ( to _px - env _bounding _line _w , env _h1 ) ;
scale _ctx . lineTo ( to _px , env _h1 ) ;
scale _ctx . globalAlpha = 0.3 ;
scale _ctx . fill ( ) ;
scale _ctx . globalAlpha = 1 ;
scale _ctx . stroke ( ) ;
}
if ( typeof line !== "undefined" ) // out of screen?
{
2019-10-16 15:11:09 +00:00
var line _px = scale _px _from _freq ( line , range ) ;
2019-10-16 11:17:47 +00:00
if ( ! ( line _px < 0 || line _px > window . innerWidth ) ) {
drag _ranges . line = { x1 : line _px - env _line _click _area / 2 , x2 : line _px + env _line _click _area / 2 } ;
drag _ranges . line _on _screen = true ;
scale _ctx . moveTo ( line _px , env _h1 + env _lineplus ) ;
scale _ctx . lineTo ( line _px , env _h2 - env _lineplus ) ;
scale _ctx . stroke ( ) ;
}
}
return drag _ranges ;
}
function demod _envelope _where _clicked ( x , drag _ranges , key _modifiers ) { // Check exactly what the user has clicked based on ranges returned by demod_envelope_draw().
2019-10-16 15:11:09 +00:00
var in _range = function ( x , range ) {
2019-10-16 11:17:47 +00:00
return range . x1 <= x && range . x2 >= x ;
} ;
2019-10-16 15:11:09 +00:00
var dr = Demodulator . draggable _ranges ;
2019-10-16 11:17:47 +00:00
if ( key _modifiers . shiftKey ) {
//Check first: shift + center drag emulates BFO knob
if ( drag _ranges . line _on _screen && in _range ( x , drag _ranges . line ) ) return dr . bfo ;
//Check second: shift + envelope drag emulates PBF knob
if ( drag _ranges . envelope _on _screen && in _range ( x , drag _ranges . whole _envelope ) ) return dr . pbs ;
}
if ( drag _ranges . envelope _on _screen ) {
// For low and high cut:
if ( in _range ( x , drag _ranges . beginning ) ) return dr . beginning ;
if ( in _range ( x , drag _ranges . ending ) ) return dr . ending ;
// Last priority: having clicked anything else on the envelope, without holding the shift key
if ( in _range ( x , drag _ranges . whole _envelope ) ) return dr . anything _else ;
}
return dr . none ; //User doesn't drag the envelope for this demodulator
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
//******* class Demodulator *******
2014-11-29 00:07:10 +00:00
// this can be used as a base class for ANY demodulator
2019-10-16 15:11:09 +00:00
Demodulator = function ( offset _frequency ) {
2019-10-16 11:17:47 +00:00
//console.log("this too");
this . offset _frequency = offset _frequency ;
this . envelope = { } ;
this . color = demodulators _get _next _color ( ) ;
this . stop = function ( ) {
} ;
2019-10-16 15:11:09 +00:00
} ;
2014-11-29 00:07:10 +00:00
//ranges on filter envelope that can be dragged:
2019-10-16 15:11:09 +00:00
Demodulator . draggable _ranges = {
2019-10-16 11:17:47 +00:00
none : 0 ,
beginning : 1 /*from*/ ,
ending : 2 /*to*/ ,
anything _else : 3 ,
bfo : 4 /*line (while holding shift)*/ ,
pbs : 5
2019-10-16 15:11:09 +00:00
} ; //to which parameter these correspond in demod_envelope_draw()
2014-11-29 00:07:10 +00:00
2019-10-16 15:11:09 +00:00
//******* class Demodulator_default_analog *******
2014-11-29 00:07:10 +00:00
// This can be used as a base for basic audio demodulators.
// It already supports most basic modulations used for ham radio and commercial services: AM/FM/LSB/USB
2019-10-16 11:17:47 +00:00
demodulator _response _time = 50 ;
2014-11-29 00:07:10 +00:00
//in ms; if we don't limit the number of SETs sent to the server, audio will underrun (possibly output buffer is cleared on SETs in GNU Radio
2019-10-16 15:11:09 +00:00
function Demodulator _default _analog ( offset _frequency , subtype ) {
2019-10-16 11:17:47 +00:00
//console.log("hopefully this happens");
//http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
2019-10-16 15:11:09 +00:00
Demodulator . call ( this , offset _frequency ) ;
2019-10-16 11:17:47 +00:00
this . subtype = subtype ;
this . filter = {
min _passband : 100 ,
2019-10-20 16:53:23 +00:00
high _cut _limit : ( audioEngine . getOutputRate ( ) / 2 ) - 1 ,
low _cut _limit : ( - audioEngine . getOutputRate ( ) / 2 ) + 1
2019-10-16 11:17:47 +00:00
} ;
//Subtypes only define some filter parameters and the mod string sent to server,
//so you may set these parameters in your custom child class.
//Why? As of demodulation is done on the server, difference is mainly on the server side.
this . server _mod = subtype ;
if ( subtype === "lsb" ) {
this . low _cut = - 3000 ;
this . high _cut = - 300 ;
this . server _mod = "ssb" ;
}
else if ( subtype === "usb" ) {
this . low _cut = 300 ;
this . high _cut = 3000 ;
this . server _mod = "ssb" ;
}
else if ( subtype === "cw" ) {
this . low _cut = 700 ;
this . high _cut = 900 ;
this . server _mod = "ssb" ;
}
else if ( subtype === "nfm" ) {
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
}
else if ( subtype === "dmr" || subtype === "ysf" ) {
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
}
else if ( subtype === "dstar" || subtype === "nxdn" ) {
this . low _cut = - 3250 ;
this . high _cut = 3250 ;
}
else if ( subtype === "am" ) {
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
}
this . wait _for _timer = false ;
this . set _after = false ;
this . set = function ( ) { //set() is a wrapper to call doset(), but it ensures that doset won't execute more frequently than demodulator_response_time.
if ( ! this . wait _for _timer ) {
this . doset ( false ) ;
this . set _after = false ;
this . wait _for _timer = true ;
2019-10-16 15:11:09 +00:00
var timeout _this = this ; //http://stackoverflow.com/a/2130411
2019-10-16 11:17:47 +00:00
window . setTimeout ( function ( ) {
timeout _this . wait _for _timer = false ;
if ( timeout _this . set _after ) timeout _this . set ( ) ;
} , demodulator _response _time ) ;
}
else {
this . set _after = true ;
}
} ;
this . doset = function ( first _time ) { //this function sends demodulator parameters to the server
2019-10-16 15:11:09 +00:00
var params = {
2019-10-16 11:17:47 +00:00
"low_cut" : this . low _cut ,
"high_cut" : this . high _cut ,
"offset_freq" : this . offset _frequency
} ;
if ( first _time ) params . mod = this . server _mod ;
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : params } ) ) ;
} ;
this . doset ( true ) ; //we set parameters on object creation
//******* envelope object *******
// for drawing the filter envelope above scale
this . envelope . parent = this ;
this . envelope . draw = function ( visible _range ) {
this . visible _range = visible _range ;
this . drag _ranges = demod _envelope _draw ( range ,
center _freq + this . parent . offset _frequency + this . parent . low _cut ,
center _freq + this . parent . offset _frequency + this . parent . high _cut ,
this . color , center _freq + this . parent . offset _frequency ) ;
} ;
2019-10-16 15:11:09 +00:00
this . envelope . dragged _range = Demodulator . draggable _ranges . none ;
2019-10-16 11:17:47 +00:00
// event handlers
this . envelope . drag _start = function ( x , key _modifiers ) {
this . key _modifiers = key _modifiers ;
this . dragged _range = demod _envelope _where _clicked ( x , this . drag _ranges , key _modifiers ) ;
this . drag _origin = {
x : x ,
low _cut : this . parent . low _cut ,
high _cut : this . parent . high _cut ,
offset _frequency : this . parent . offset _frequency
} ;
2019-10-16 15:11:09 +00:00
return this . dragged _range !== Demodulator . draggable _ranges . none ;
2019-10-16 11:17:47 +00:00
} ;
this . envelope . drag _move = function ( x ) {
2019-10-16 15:11:09 +00:00
var dr = Demodulator . draggable _ranges ;
var new _value ;
2019-10-16 11:17:47 +00:00
if ( this . dragged _range === dr . none ) return false ; // we return if user is not dragging (us) at all
2019-10-16 15:11:09 +00:00
var freq _change = Math . round ( this . visible _range . hps * ( x - this . drag _origin . x ) ) ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
//dragging the line in the middle of the filter envelope while holding Shift does emulate
//the BFO knob on radio equipment: moving offset frequency, while passband remains unchanged
//Filter passband moves in the opposite direction than dragged, hence the minus below.
2019-10-16 15:11:09 +00:00
var minus = ( this . dragged _range === dr . bfo ) ? - 1 : 1 ;
2019-10-16 11:17:47 +00:00
//dragging any other parts of the filter envelope while holding Shift does emulate the PBS knob
//(PassBand Shift) on radio equipment: PBS does move the whole passband without moving the offset
//frequency.
if ( this . dragged _range === dr . beginning || this . dragged _range === dr . bfo || this . dragged _range === dr . pbs ) {
//we don't let low_cut go beyond its limits
if ( ( new _value = this . drag _origin . low _cut + minus * freq _change ) < this . parent . filter . low _cut _limit ) return true ;
//nor the filter passband be too small
if ( this . parent . high _cut - new _value < this . parent . filter . min _passband ) return true ;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if ( new _value >= this . parent . high _cut ) return true ;
this . parent . low _cut = new _value ;
}
if ( this . dragged _range === dr . ending || this . dragged _range === dr . bfo || this . dragged _range === dr . pbs ) {
//we don't let high_cut go beyond its limits
if ( ( new _value = this . drag _origin . high _cut + minus * freq _change ) > this . parent . filter . high _cut _limit ) return true ;
//nor the filter passband be too small
if ( new _value - this . parent . low _cut < this . parent . filter . min _passband ) return true ;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if ( new _value <= this . parent . low _cut ) return true ;
this . parent . high _cut = new _value ;
}
if ( this . dragged _range === dr . anything _else || this . dragged _range === dr . bfo ) {
//when any other part of the envelope is dragged, the offset frequency is changed (whole passband also moves with it)
new _value = this . drag _origin . offset _frequency + freq _change ;
if ( new _value > bandwidth / 2 || new _value < - bandwidth / 2 ) return true ; //we don't allow tuning above Nyquist frequency :-)
this . parent . offset _frequency = new _value ;
}
//now do the actual modifications:
mkenvelopes ( this . visible _range ) ;
this . parent . set ( ) ;
//will have to change this when changing to multi-demodulator mode:
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , center _freq + this . parent . offset _frequency , 1e6 , 4 ) ;
return true ;
} ;
2016-03-20 10:32:37 +00:00
2019-10-16 15:11:09 +00:00
this . envelope . drag _end = function ( ) { //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
2019-10-16 11:17:47 +00:00
demodulator _buttons _update ( ) ;
2019-10-16 15:11:09 +00:00
var 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 ;
2019-10-16 11:17:47 +00:00
return to _return ;
} ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
Demodulator _default _analog . prototype = new Demodulator ( ) ;
2014-11-29 00:07:10 +00:00
function mkenvelopes ( visible _range ) //called from mkscale
{
2019-10-16 11:17:47 +00:00
scale _ctx . clearRect ( 0 , 0 , scale _ctx . canvas . width , 22 ) ; //clear the upper part of the canvas (where filter envelopes reside)
for ( var i = 0 ; i < demodulators . length ; i ++ ) {
demodulators [ i ] . envelope . draw ( visible _range ) ;
}
if ( demodulators . length ) secondary _demod _waterfall _set _zoom ( demodulators [ 0 ] . low _cut , demodulators [ 0 ] . high _cut ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function demodulator _remove ( which ) {
demodulators [ which ] . stop ( ) ;
demodulators . splice ( which , 1 ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function demodulator _add ( what ) {
demodulators . push ( what ) ;
mkenvelopes ( get _visible _freq _range ( ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
var last _analog _demodulator _subtype = 'nfm' ;
var last _digital _demodulator _subtype = 'bpsk31' ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
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 ( ) ;
2018-09-25 12:56:47 +00:00
}
last _analog _demodulator _subtype = subtype ;
2019-10-16 11:17:47 +00:00
var temp _offset = 0 ;
if ( demodulators . length ) {
temp _offset = demodulators [ 0 ] . offset _frequency ;
demodulator _remove ( 0 ) ;
}
2019-10-16 15:11:09 +00:00
demodulator _add ( new Demodulator _default _analog ( temp _offset , subtype ) ) ;
2019-10-16 11:17:47 +00:00
demodulator _buttons _update ( ) ;
update _digitalvoice _panels ( "openwebrx-panel-metadata-" + subtype ) ;
}
function demodulator _set _offset _frequency ( which , to _what ) {
if ( to _what > bandwidth / 2 || to _what < - bandwidth / 2 ) return ;
demodulators [ 0 ] . offset _frequency = Math . round ( to _what ) ;
demodulators [ 0 ] . set ( ) ;
mkenvelopes ( get _visible _freq _range ( ) ) ;
2019-09-28 00:21:29 +00:00
$ ( "#webrx-actual-freq" ) . html ( format _frequency ( "{x} MHz" , center _freq + to _what , 1e6 , 4 ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-11-01 18:48:08 +00:00
function waterfallWidth ( ) {
return $ ( 'body' ) . width ( ) ;
}
2014-11-29 00:07:10 +00:00
// ========================================================
// =================== SCALE ROUTINES ===================
// ========================================================
var scale _ctx ;
var scale _canvas ;
2019-10-16 11:17:47 +00:00
function scale _setup ( ) {
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , canvas _get _frequency ( window . innerWidth / 2 ) , 1e6 , 4 ) ;
scale _canvas = e ( "openwebrx-scale-canvas" ) ;
scale _ctx = scale _canvas . getContext ( "2d" ) ;
scale _canvas . addEventListener ( "mousedown" , scale _canvas _mousedown , false ) ;
scale _canvas . addEventListener ( "mousemove" , scale _canvas _mousemove , false ) ;
scale _canvas . addEventListener ( "mouseup" , scale _canvas _mouseup , false ) ;
resize _scale ( ) ;
var frequency _container = e ( "openwebrx-frequency-container" ) ;
frequency _container . addEventListener ( "mousemove" , frequency _container _mousemove , false ) ;
}
var scale _canvas _drag _params = {
mouse _down : false ,
drag : false ,
start _x : 0 ,
key _modifiers : { shiftKey : false , altKey : false , ctrlKey : false }
2014-11-29 00:07:10 +00:00
} ;
2019-10-16 11:17:47 +00:00
function scale _canvas _mousedown ( evt ) {
2019-10-16 15:11:09 +00:00
scale _canvas _drag _params . mouse _down = true ;
scale _canvas _drag _params . drag = false ;
scale _canvas _drag _params . start _x = evt . pageX ;
scale _canvas _drag _params . key _modifiers . shiftKey = evt . shiftKey ;
scale _canvas _drag _params . key _modifiers . altKey = evt . altKey ;
scale _canvas _drag _params . key _modifiers . ctrlKey = evt . ctrlKey ;
2019-10-16 11:17:47 +00:00
evt . preventDefault ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale _offset _freq _from _px ( x , visible _range ) {
if ( typeof visible _range === "undefined" ) visible _range = get _visible _freq _range ( ) ;
2019-11-01 18:48:08 +00:00
return ( visible _range . start + visible _range . bw * ( x / waterfallWidth ( ) ) ) - center _freq ;
2019-10-16 11:17:47 +00:00
}
function scale _canvas _mousemove ( evt ) {
2019-10-16 15:11:09 +00:00
var event _handled = false ;
var i ;
2019-10-16 11:17:47 +00:00
if ( scale _canvas _drag _params . mouse _down && ! scale _canvas _drag _params . drag && Math . abs ( evt . pageX - scale _canvas _drag _params . start _x ) > canvas _drag _min _delta )
//we can use the main drag_min_delta thing of the main canvas
{
scale _canvas _drag _params . drag = true ;
//call the drag_start for all demodulators (and they will decide if they're dragged, based on X coordinate)
2019-10-16 15:11:09 +00:00
for ( i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _start ( evt . pageX , scale _canvas _drag _params . key _modifiers ) ;
2019-10-16 11:17:47 +00:00
scale _canvas . style . cursor = "move" ;
}
else if ( scale _canvas _drag _params . drag ) {
//call the drag_move for all demodulators (and they will decide if they're dragged)
2019-10-16 15:11:09 +00:00
for ( i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _move ( evt . pageX ) ;
2019-10-16 11:17:47 +00:00
if ( ! event _handled ) demodulator _set _offset _frequency ( 0 , scale _offset _freq _from _px ( evt . pageX ) ) ;
}
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
2019-10-03 15:24:28 +00:00
function frequency _container _mousemove ( evt ) {
2019-10-16 11:17:47 +00:00
var frequency = center _freq + scale _offset _freq _from _px ( evt . pageX ) ;
2019-10-03 15:24:28 +00:00
e ( "webrx-mouse-freq" ) . innerHTML = format _frequency ( "{x} MHz" , frequency , 1e6 , 4 ) ;
}
2019-10-16 11:17:47 +00:00
function scale _canvas _end _drag ( x ) {
scale _canvas . style . cursor = "default" ;
scale _canvas _drag _params . drag = false ;
scale _canvas _drag _params . mouse _down = false ;
var event _handled = false ;
2019-10-16 15:11:09 +00:00
for ( var i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _end ( ) ;
2019-10-16 11:17:47 +00:00
if ( ! event _handled ) demodulator _set _offset _frequency ( 0 , scale _offset _freq _from _px ( x ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale _canvas _mouseup ( evt ) {
scale _canvas _end _drag ( evt . pageX ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale _px _from _freq ( f , range ) {
2019-11-01 18:48:08 +00:00
return Math . round ( ( ( f - range . start ) / range . bw ) * waterfallWidth ( ) ) ;
2019-10-16 11:17:47 +00:00
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function get _visible _freq _range ( ) {
2019-10-16 15:11:09 +00:00
var out = { } ;
var fcalc = function ( x ) {
2019-11-01 18:48:08 +00:00
var canvasWidth = waterfallWidth ( ) * zoom _levels [ zoom _level ] ;
2019-10-20 16:53:23 +00:00
return Math . round ( ( ( - zoom _offset _px + x ) / canvasWidth ) * bandwidth ) + ( center _freq - bandwidth / 2 ) ;
2019-10-16 11:17:47 +00:00
} ;
out . start = fcalc ( 0 ) ;
2019-11-01 18:48:08 +00:00
out . center = fcalc ( waterfallWidth ( ) / 2 ) ;
out . end = fcalc ( waterfallWidth ( ) ) ;
2019-10-16 11:17:47 +00:00
out . bw = out . end - out . start ;
2019-11-01 18:48:08 +00:00
out . hps = out . bw / waterfallWidth ( ) ;
2019-10-16 11:17:47 +00:00
return out ;
}
var scale _markers _levels = [
{
"large_marker_per_hz" : 10000000 , //large
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 0
} ,
{
"large_marker_per_hz" : 5000000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 0
} ,
{
"large_marker_per_hz" : 1000000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 0
} ,
{
"large_marker_per_hz" : 500000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 1
} ,
{
"large_marker_per_hz" : 100000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 1
} ,
{
"large_marker_per_hz" : 50000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 2
} ,
{
"large_marker_per_hz" : 10000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 2
} ,
{
"large_marker_per_hz" : 5000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 3
} ,
{
"large_marker_per_hz" : 1000 ,
"estimated_text_width" : 70 ,
"format" : "{x} MHz" ,
"pre_divide" : 1000000 ,
"decimals" : 1
}
2014-11-29 00:07:10 +00:00
] ;
2019-10-16 11:17:47 +00:00
var scale _min _space _bw _texts = 50 ;
var scale _min _space _bw _small _markers = 7 ;
function get _scale _mark _spacing ( range ) {
2019-10-16 15:11:09 +00:00
var out = { } ;
var fcalc = function ( freq ) {
2019-10-16 11:17:47 +00:00
out . numlarge = ( range . bw / freq ) ;
2019-11-01 18:48:08 +00:00
out . large = waterfallWidth ( ) / out . numlarge ; //distance between large markers (these have text)
2019-10-16 11:17:47 +00:00
out . ratio = 5 ; //(ratio-1) small markers exist per large marker
out . small = out . large / out . ratio ; //distance between small markers
if ( out . small < scale _min _space _bw _small _markers ) return false ;
if ( out . small / 2 >= scale _min _space _bw _small _markers && freq . toString ( ) [ 0 ] !== "5" ) {
out . small /= 2 ;
out . ratio *= 2 ;
}
out . smallbw = freq / out . ratio ;
return true ;
} ;
2019-10-16 15:11:09 +00:00
for ( var i = scale _markers _levels . length - 1 ; i >= 0 ; i -- ) {
var mp = scale _markers _levels [ i ] ;
2019-10-16 11:17:47 +00:00
if ( ! fcalc ( mp . large _marker _per _hz ) ) continue ;
//console.log(mp.large_marker_per_hz);
//console.log(out);
if ( out . large - mp . estimated _text _width > scale _min _space _bw _texts ) break ;
}
out . params = mp ;
return out ;
}
2019-10-16 15:11:09 +00:00
var range ;
2019-10-16 11:17:47 +00:00
function mkscale ( ) {
//clear the lower part of the canvas (where frequency scale resides; the upper part is used by filter envelopes):
range = get _visible _freq _range ( ) ;
mkenvelopes ( range ) ; //when scale changes we will always have to redraw filter envelopes, too
scale _ctx . clearRect ( 0 , 22 , scale _ctx . canvas . width , scale _ctx . canvas . height - 22 ) ;
scale _ctx . strokeStyle = "#fff" ;
scale _ctx . font = "bold 11px sans-serif" ;
scale _ctx . textBaseline = "top" ;
scale _ctx . fillStyle = "#fff" ;
2019-10-16 15:11:09 +00:00
var spacing = get _scale _mark _spacing ( range ) ;
2019-10-16 11:17:47 +00:00
//console.log(spacing);
2019-10-16 15:11:09 +00:00
var marker _hz = Math . ceil ( range . start / spacing . smallbw ) * spacing . smallbw ;
var text _h _pos = 22 + 10 + ( ( is _firefox ) ? 3 : 0 ) ;
var text _to _draw = '' ;
2019-10-16 11:17:47 +00:00
var ftext = function ( f ) {
text _to _draw = format _frequency ( spacing . params . format , f , spacing . params . pre _divide , spacing . params . decimals ) ;
} ;
var last _large ;
2019-10-16 15:11:09 +00:00
var x ;
2019-10-16 11:17:47 +00:00
for ( ; ; ) {
2019-10-16 15:11:09 +00:00
x = scale _px _from _freq ( marker _hz , range ) ;
2019-10-16 11:17:47 +00:00
if ( x > window . innerWidth ) break ;
scale _ctx . beginPath ( ) ;
scale _ctx . moveTo ( x , 22 ) ;
if ( marker _hz % spacing . params . large _marker _per _hz === 0 ) { //large marker
if ( typeof first _large === "undefined" ) var first _large = marker _hz ;
last _large = marker _hz ;
scale _ctx . lineWidth = 3.5 ;
scale _ctx . lineTo ( x , 22 + 11 ) ;
ftext ( marker _hz ) ;
var text _measured = scale _ctx . measureText ( text _to _draw ) ;
scale _ctx . textAlign = "center" ;
//advanced text drawing begins
if ( zoom _level === 0 && ( range . start + spacing . smallbw * spacing . ratio > marker _hz ) && ( x < text _measured . width / 2 ) ) { //if this is the first overall marker when zoomed out... and if it would be clipped off the screen...
if ( scale _px _from _freq ( marker _hz + spacing . smallbw * spacing . ratio , range ) - text _measured . width >= scale _min _space _bw _texts ) { //and if we have enough space to draw it correctly without clipping
scale _ctx . textAlign = "left" ;
scale _ctx . fillText ( text _to _draw , 0 , text _h _pos ) ;
}
}
else if ( zoom _level === 0 && ( range . end - spacing . smallbw * spacing . ratio < marker _hz ) && ( x > window . innerWidth - text _measured . width / 2 ) ) { // if this is the last overall marker when zoomed out... and if it would be clipped off the screen...
if ( window . innerWidth - text _measured . width - scale _px _from _freq ( marker _hz - spacing . smallbw * spacing . ratio , range ) >= scale _min _space _bw _texts ) { //and if we have enough space to draw it correctly without clipping
scale _ctx . textAlign = "right" ;
scale _ctx . fillText ( text _to _draw , window . innerWidth , text _h _pos ) ;
}
}
else scale _ctx . fillText ( text _to _draw , x , text _h _pos ) ; //draw text normally
}
else { //small marker
scale _ctx . lineWidth = 2 ;
scale _ctx . lineTo ( x , 22 + 8 ) ;
}
marker _hz += spacing . smallbw ;
scale _ctx . stroke ( ) ;
}
if ( zoom _level !== 0 ) { // if zoomed, we don't want the texts to disappear because their markers can't be seen
// on the left side
scale _ctx . textAlign = "center" ;
var f = first _large - spacing . smallbw * spacing . ratio ;
2019-10-16 15:11:09 +00:00
x = scale _px _from _freq ( f , range ) ;
2019-10-16 11:17:47 +00:00
ftext ( f ) ;
var w = scale _ctx . measureText ( text _to _draw ) . width ;
if ( x + w / 2 > 0 ) scale _ctx . fillText ( text _to _draw , x , 22 + 10 ) ;
// on the right side
f = last _large + spacing . smallbw * spacing . ratio ;
x = scale _px _from _freq ( f , range ) ;
ftext ( f ) ;
w = scale _ctx . measureText ( text _to _draw ) . width ;
if ( x - w / 2 < window . innerWidth ) scale _ctx . fillText ( text _to _draw , x , 22 + 10 ) ;
}
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function resize _scale ( ) {
2019-10-16 15:11:09 +00:00
var ratio = window . devicePixelRatio || 1 ;
2019-09-28 05:36:28 +00:00
var w = window . innerWidth ;
var h = 47 ;
scale _canvas . style . width = w + "px" ;
scale _canvas . style . height = h + "px" ;
w *= ratio ;
h *= ratio ;
2019-10-16 11:17:47 +00:00
scale _canvas . width = w ;
2019-09-28 05:36:28 +00:00
scale _canvas . height = h ;
scale _ctx . scale ( ratio , ratio ) ;
2019-10-16 11:17:47 +00:00
mkscale ( ) ;
bookmarks . position ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas _get _freq _offset ( relativeX ) {
2019-10-16 15:11:09 +00:00
var rel = ( relativeX / canvases [ 0 ] . clientWidth ) ;
2019-10-16 11:17:47 +00:00
return Math . round ( ( bandwidth * rel ) - ( bandwidth / 2 ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas _get _frequency ( relativeX ) {
return center _freq + canvas _get _freq _offset ( relativeX ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function format _frequency ( format , freq _hz , pre _divide , decimals ) {
2019-10-16 15:11:09 +00:00
var out = format . replace ( "{x}" , ( freq _hz / pre _divide ) . toFixed ( decimals ) ) ;
var at = out . indexOf ( "." ) + 4 ;
2019-10-16 11:17:47 +00:00
while ( decimals > 3 ) {
out = out . substr ( 0 , at ) + "," + out . substr ( at ) ;
at += 4 ;
decimals -= 3 ;
}
return out ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
var canvas _drag = false ;
var canvas _drag _min _delta = 1 ;
var canvas _mouse _down = false ;
var canvas _drag _last _x ;
var canvas _drag _last _y ;
var canvas _drag _start _x ;
var canvas _drag _start _y ;
2019-10-16 11:17:47 +00:00
function canvas _mousedown ( evt ) {
canvas _mouse _down = true ;
canvas _drag = false ;
canvas _drag _last _x = canvas _drag _start _x = evt . pageX ;
canvas _drag _last _y = canvas _drag _start _y = evt . pageY ;
evt . preventDefault ( ) ; //don't show text selection mouse pointer
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas _mousemove ( evt ) {
if ( ! waterfall _setup _done ) return ;
2019-10-16 15:11:09 +00:00
var relativeX = get _relative _x ( evt ) ;
2019-10-16 11:17:47 +00:00
if ( canvas _mouse _down ) {
if ( ! canvas _drag && Math . abs ( evt . pageX - canvas _drag _start _x ) > canvas _drag _min _delta ) {
canvas _drag = true ;
canvas _container . style . cursor = "move" ;
}
if ( canvas _drag ) {
var deltaX = canvas _drag _last _x - evt . pageX ;
var dpx = range . hps * deltaX ;
if (
2019-11-01 18:48:08 +00:00
! ( zoom _center _rel + dpx > ( bandwidth / 2 - waterfallWidth ( ) * ( 1 - zoom _center _where ) * range . hps ) ) &&
! ( zoom _center _rel + dpx < - bandwidth / 2 + waterfallWidth ( ) * zoom _center _where * range . hps )
2019-10-16 11:17:47 +00:00
) {
zoom _center _rel += dpx ;
}
resize _canvases ( false ) ;
canvas _drag _last _x = evt . pageX ;
canvas _drag _last _y = evt . pageY ;
mkscale ( ) ;
bookmarks . position ( ) ;
}
}
else e ( "webrx-mouse-freq" ) . innerHTML = format _frequency ( "{x} MHz" , canvas _get _frequency ( relativeX ) , 1e6 , 4 ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
function canvas _container _mouseleave ( ) {
2019-10-16 11:17:47 +00:00
canvas _end _drag ( ) ;
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function canvas _mouseup ( evt ) {
if ( ! waterfall _setup _done ) return ;
2019-10-16 15:11:09 +00:00
var relativeX = get _relative _x ( evt ) ;
2019-10-16 11:17:47 +00:00
if ( ! canvas _drag ) {
demodulator _set _offset _frequency ( 0 , canvas _get _freq _offset ( relativeX ) ) ;
}
else {
canvas _end _drag ( ) ;
}
canvas _mouse _down = false ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas _end _drag ( ) {
canvas _container . style . cursor = "crosshair" ;
canvas _mouse _down = false ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function zoom _center _where _calc ( screenposX ) {
2019-11-01 18:48:08 +00:00
return screenposX / waterfallWidth ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-03 15:24:28 +00:00
function get _relative _x ( evt ) {
2019-11-01 18:48:08 +00:00
var relativeX = evt . offsetX || evt . layerX ;
2019-10-03 15:24:28 +00:00
if ( $ ( evt . target ) . closest ( canvas _container ) . length ) return relativeX ;
2019-10-16 11:17:47 +00:00
// compensate for the frequency scale, since that is not resized by the browser.
2019-11-01 18:48:08 +00:00
var relatives = $ ( evt . target ) . closest ( '#openwebrx-frequency-container' ) . map ( function ( ) {
return evt . pageX - this . offsetLeft ;
} ) ;
if ( relatives . length ) relativeX = relatives [ 0 ] ;
2019-10-03 15:24:28 +00:00
return relativeX - zoom _offset _px ;
}
2019-10-16 11:17:47 +00:00
function canvas _mousewheel ( evt ) {
if ( ! waterfall _setup _done ) return ;
var relativeX = get _relative _x ( evt ) ;
var dir = ( evt . deltaY / Math . abs ( evt . deltaY ) ) > 0 ;
zoom _step ( dir , relativeX , zoom _center _where _calc ( evt . pageX ) ) ;
evt . preventDefault ( ) ;
}
2019-10-16 15:11:09 +00:00
var zoom _max _level _hps = 33 ; //Hz/pixel
var zoom _levels _count = 14 ;
2019-10-16 11:17:47 +00:00
function get _zoom _coeff _from _hps ( hps ) {
var shown _bw = ( window . innerWidth * hps ) ;
return bandwidth / shown _bw ;
}
2019-10-16 15:11:09 +00:00
var zoom _levels = [ 1 ] ;
var zoom _level = 0 ;
var zoom _offset _px = 0 ;
var zoom _center _rel = 0 ;
var zoom _center _where = 0 ;
2019-10-16 11:17:47 +00:00
2019-10-16 15:11:09 +00:00
var smeter _level = 0 ;
2019-10-16 11:17:47 +00:00
function mkzoomlevels ( ) {
zoom _levels = [ 1 ] ;
2019-10-16 15:11:09 +00:00
var maxc = get _zoom _coeff _from _hps ( zoom _max _level _hps ) ;
2019-10-16 11:17:47 +00:00
if ( maxc < 1 ) return ;
// logarithmic interpolation
2019-10-16 15:11:09 +00:00
var zoom _ratio = Math . pow ( maxc , 1 / zoom _levels _count ) ;
for ( var i = 1 ; i < zoom _levels _count ; i ++ )
2019-10-16 11:17:47 +00:00
zoom _levels . push ( Math . pow ( zoom _ratio , i ) ) ;
}
function zoom _step ( out , where , onscreen ) {
if ( ( out && zoom _level === 0 ) || ( ! out && zoom _level >= zoom _levels _count - 1 ) ) return ;
if ( out ) -- zoom _level ;
else ++ zoom _level ;
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);
resize _canvases ( true ) ;
mkscale ( ) ;
bookmarks . position ( ) ;
}
function zoom _set ( level ) {
if ( ! ( level >= 0 && level <= zoom _levels . length - 1 ) ) return ;
level = parseInt ( level ) ;
zoom _level = level ;
2019-11-01 18:48:08 +00:00
//zoom_center_rel=canvas_get_freq_offset(-canvases[0].offsetLeft+waterfallWidth()/2); //zoom to screen center instead of demod envelope
2019-10-16 11:17:47 +00:00
zoom _center _rel = demodulators [ 0 ] . offset _frequency ;
zoom _center _where = 0.5 + ( zoom _center _rel / bandwidth ) ; //this is a kind of hack
resize _canvases ( true ) ;
mkscale ( ) ;
bookmarks . position ( ) ;
}
function zoom _calc ( ) {
2019-11-01 18:48:08 +00:00
var winsize = waterfallWidth ( ) ;
2019-10-16 11:17:47 +00:00
var canvases _new _width = winsize * zoom _levels [ zoom _level ] ;
zoom _offset _px = - ( ( canvases _new _width * ( 0.5 + zoom _center _rel / bandwidth ) ) - ( winsize * zoom _center _where ) ) ;
if ( zoom _offset _px > 0 ) zoom _offset _px = 0 ;
if ( zoom _offset _px < winsize - canvases _new _width )
zoom _offset _px = winsize - canvases _new _width ;
}
2019-10-26 20:44:54 +00:00
var networkSpeedMeasurement ;
2019-10-04 20:01:07 +00:00
var currentprofile ;
2015-08-17 18:32:58 +00:00
2019-10-16 11:17:47 +00:00
var COMPRESS _FFT _PAD _N = 10 ; //should be the same as in csdr.c
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function on _ws _recv ( evt ) {
if ( typeof evt . data === 'string' ) {
2019-05-04 14:56:23 +00:00
// text messages
2019-10-26 20:44:54 +00:00
networkSpeedMeasurement . add ( evt . data . length ) ;
2019-05-07 18:20:12 +00:00
2019-10-16 11:17:47 +00:00
if ( evt . data . substr ( 0 , 16 ) === "CLIENT DE SERVER" ) {
divlog ( "Server acknowledged WebSocket connection." ) ;
} else {
try {
2019-10-16 15:11:09 +00:00
var json = JSON . parse ( evt . data ) ;
2019-10-16 11:17:47 +00:00
switch ( json . type ) {
2019-05-04 14:56:23 +00:00
case "config" :
2019-10-16 15:11:09 +00:00
var config = json [ 'value' ] ;
2019-10-20 16:53:23 +00:00
waterfall _colors = config [ 'waterfall_colors' ] ;
waterfall _min _level _default = config [ 'waterfall_min_level' ] ;
waterfall _max _level _default = config [ 'waterfall_max_level' ] ;
waterfall _auto _level _margin = config [ 'waterfall_auto_level_margin' ] ;
2019-05-04 14:56:23 +00:00
waterfallColorsDefault ( ) ;
2019-10-20 16:53:23 +00:00
starting _mod = config [ 'start_mod' ] ;
starting _offset _frequency = config [ 'start_offset_freq' ] ;
2019-10-16 15:11:09 +00:00
bandwidth = config [ 'samp_rate' ] ;
2019-11-21 14:31:37 +00:00
center _freq = config [ 'center_freq' ] ;
2019-10-16 15:11:09 +00:00
fft _size = config [ 'fft_size' ] ;
fft _fps = config [ 'fft_fps' ] ;
2019-10-20 16:53:23 +00:00
var audio _compression = config [ 'audio_compression' ] ;
audioEngine . setCompression ( audio _compression ) ;
2019-10-16 11:17:47 +00:00
divlog ( "Audio stream is " + ( ( audio _compression === "adpcm" ) ? "compressed" : "uncompressed" ) + "." ) ;
2019-10-16 15:11:09 +00:00
fft _compression = config [ 'fft_compression' ] ;
2019-10-16 11:17:47 +00:00
divlog ( "FFT stream is " + ( ( fft _compression === "adpcm" ) ? "compressed" : "uncompressed" ) + "." ) ;
2019-10-22 20:35:54 +00:00
clientProgressBar . setMaxClients ( config [ 'max_clients' ] ) ;
2019-10-16 15:11:09 +00:00
mathbox _waterfall _colors = config [ 'mathbox_waterfall_colors' ] ;
mathbox _waterfall _frequency _resolution = config [ 'mathbox_waterfall_frequency_resolution' ] ;
mathbox _waterfall _history _length = config [ 'mathbox_waterfall_history_length' ] ;
2019-11-23 15:56:29 +00:00
var sql = Number . isInteger ( config [ 'initial_squelch_level' ] ) ? config [ 'initial_squelch_level' ] : - 150 ;
$ ( "#openwebrx-panel-squelch" ) . val ( sql ) ;
updateSquelch ( ) ;
2019-10-16 11:17:47 +00:00
waterfall _init ( ) ;
2019-10-20 16:53:23 +00:00
initialize _demodulator ( ) ;
2019-10-16 11:17:47 +00:00
bookmarks . loadLocalBookmarks ( ) ;
waterfall _clear ( ) ;
2019-10-04 20:01:07 +00:00
2019-11-23 16:22:20 +00:00
currentprofile = config [ 'sdr_id' ] + '|' + config [ 'profile_id' ] ;
2019-10-16 11:17:47 +00:00
$ ( '#openwebrx-sdr-profiles-listbox' ) . val ( currentprofile ) ;
2019-10-20 16:53:23 +00:00
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 20:09:48 +00:00
case "secondary_config" :
2019-10-16 15:11:09 +00:00
var s = json [ 'value' ] ;
window . secondary _fft _size = s [ 'secondary_fft_size' ] ;
window . secondary _bw = s [ 'secondary_bw' ] ;
window . if _samp _rate = s [ 'if_samp_rate' ] ;
2018-09-25 12:56:47 +00:00
secondary _demod _init _canvases ( ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 15:52:26 +00:00
case "receiver_details" :
2019-10-16 15:11:09 +00:00
var r = json [ 'value' ] ;
e ( 'webrx-rx-title' ) . innerHTML = r [ 'receiver_name' ] ;
e ( 'webrx-rx-desc' ) . innerHTML = r [ 'receiver_location' ] + ' | Loc: ' + r [ 'locator' ] + ', ASL: ' + r [ 'receiver_asl' ] + ' m, <a href="https://www.google.hu/maps/place/' + r [ 'receiver_gps' ] [ 0 ] + ',' + r [ 'receiver_gps' ] [ 1 ] + '" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a>' ;
e ( 'webrx-rx-photo-title' ) . innerHTML = r [ 'photo_title' ] ;
e ( 'webrx-rx-photo-desc' ) . innerHTML = r [ 'photo_desc' ] ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 14:17:55 +00:00
case "smeter" :
2019-10-16 15:11:09 +00:00
smeter _level = json [ 'value' ] ;
2019-05-12 14:02:49 +00:00
setSmeterAbsoluteValue ( smeter _level ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 15:34:40 +00:00
case "cpuusage" :
2019-10-22 20:35:54 +00:00
cpuProgressBar . setUsage ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-10 20:47:40 +00:00
case "clients" :
2019-10-22 20:35:54 +00:00
clientProgressBar . setClients ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-10 14:14:16 +00:00
case "profiles" :
var listbox = e ( "openwebrx-sdr-profiles-listbox" ) ;
2019-10-16 15:11:09 +00:00
listbox . innerHTML = json [ 'value' ] . map ( function ( profile ) {
return '<option value="' + profile [ 'id' ] + '">' + profile [ 'name' ] + "</option>" ;
2019-05-10 14:14:16 +00:00
} ) . join ( "" ) ;
2019-10-04 20:01:07 +00:00
if ( currentprofile ) {
2019-10-16 11:17:47 +00:00
$ ( '#openwebrx-sdr-profiles-listbox' ) . val ( currentprofile ) ;
}
break ;
case "features" :
2019-10-16 15:11:09 +00:00
var features = json [ 'value' ] ;
for ( var feature in features ) {
if ( features . hasOwnProperty ( feature ) ) {
$ ( '[data-feature="' + feature + '"' ) [ features [ feature ] ? "show" : "hide" ] ( ) ;
}
2019-10-16 11:17:47 +00:00
}
break ;
case "metadata" :
2019-10-16 15:11:09 +00:00
update _metadata ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
case "wsjt_message" :
2019-10-16 15:11:09 +00:00
update _wsjt _panel ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
case "dial_frequencies" :
2019-10-16 15:11:09 +00:00
var as _bookmarks = json [ 'value' ] . map ( function ( d ) {
2019-10-16 11:17:47 +00:00
return {
2019-10-16 15:11:09 +00:00
name : d [ 'mode' ] . toUpperCase ( ) ,
digital _modulation : d [ 'mode' ] ,
frequency : d [ 'frequency' ]
2019-10-16 11:17:47 +00:00
} ;
} ) ;
bookmarks . replace _bookmarks ( as _bookmarks , 'dial_frequencies' ) ;
break ;
case "aprs_data" :
2019-10-16 15:11:09 +00:00
update _packet _panel ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
case "bookmarks" :
2019-10-16 15:11:09 +00:00
bookmarks . replace _bookmarks ( json [ 'value' ] , "server" ) ;
2019-10-16 11:17:47 +00:00
break ;
case "sdr_error" :
2019-10-16 15:11:09 +00:00
divlog ( json [ 'value' ] , true ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-10-25 19:08:56 +00:00
case 'secondary_demod' :
secondary _demod _push _data ( json [ 'value' ] ) ;
break ;
2019-05-04 14:56:23 +00:00
default :
2019-10-16 15:11:09 +00:00
console . warn ( 'received message of unknown type: ' + json [ 'type' ] ) ;
2019-10-16 11:17:47 +00:00
}
} catch ( e ) {
// don't lose exception
console . error ( e )
}
}
2019-05-04 14:56:23 +00:00
} else if ( evt . data instanceof ArrayBuffer ) {
// binary messages
2019-10-26 20:44:54 +00:00
networkSpeedMeasurement . add ( evt . data . byteLength ) ;
2019-05-07 18:20:12 +00:00
2019-10-16 15:11:09 +00:00
var type = new Uint8Array ( evt . data , 0 , 1 ) [ 0 ] ;
var data = evt . data . slice ( 1 ) ;
var waterfall _i16 ;
var waterfall _f32 ;
var i ;
2019-05-04 18:26:11 +00:00
switch ( type ) {
case 1 :
2019-05-04 21:11:13 +00:00
// FFT data
2019-10-16 11:17:47 +00:00
if ( fft _compression === "none" ) {
2019-10-05 18:38:58 +00:00
waterfall _add ( new Float32Array ( data ) ) ;
2019-10-16 11:17:47 +00:00
} else if ( fft _compression === "adpcm" ) {
2019-05-04 18:26:11 +00:00
fft _codec . reset ( ) ;
2019-10-16 15:11:09 +00:00
waterfall _i16 = fft _codec . decode ( new Uint8Array ( data ) ) ;
waterfall _f32 = new Float32Array ( waterfall _i16 . length - COMPRESS _FFT _PAD _N ) ;
for ( i = 0 ; i < waterfall _i16 . length ; i ++ ) waterfall _f32 [ i ] = waterfall _i16 [ i + COMPRESS _FFT _PAD _N ] / 100 ;
2019-10-05 18:38:58 +00:00
waterfall _add ( waterfall _f32 ) ;
2016-11-11 21:42:45 +00:00
}
2019-10-16 11:17:47 +00:00
break ;
2019-05-04 21:11:13 +00:00
case 2 :
// audio data
2019-10-20 16:53:23 +00:00
audioEngine . pushAudio ( data ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 20:09:48 +00:00
case 3 :
// secondary FFT
2019-10-16 11:17:47 +00:00
if ( fft _compression === "none" ) {
2019-10-05 18:38:58 +00:00
secondary _demod _waterfall _add ( new Float32Array ( data ) ) ;
2019-10-16 11:17:47 +00:00
} else if ( fft _compression === "adpcm" ) {
2019-05-05 20:09:48 +00:00
fft _codec . reset ( ) ;
2019-10-16 15:11:09 +00:00
waterfall _i16 = fft _codec . decode ( new Uint8Array ( data ) ) ;
waterfall _f32 = new Float32Array ( waterfall _i16 . length - COMPRESS _FFT _PAD _N ) ;
for ( i = 0 ; i < waterfall _i16 . length ; i ++ ) waterfall _f32 [ i ] = waterfall _i16 [ i + COMPRESS _FFT _PAD _N ] / 100 ;
secondary _demod _waterfall _add ( waterfall _f32 ) ;
2019-05-05 20:09:48 +00:00
}
2019-10-16 11:17:47 +00:00
break ;
2019-05-04 18:26:11 +00:00
default :
console . warn ( 'unknown type of binary message: ' + type )
2016-11-11 21:42:45 +00:00
}
2016-11-11 20:56:17 +00:00
}
2014-11-29 00:07:10 +00:00
}
2019-05-30 14:12:13 +00:00
function update _metadata ( meta ) {
2019-10-16 15:11:09 +00:00
var el ;
if ( meta [ 'protocol' ] ) switch ( meta [ 'protocol' ] ) {
2019-05-14 21:30:03 +00:00
case 'DMR' :
2019-10-16 15:11:09 +00:00
if ( meta [ 'slot' ] ) {
el = $ ( "#openwebrx-panel-metadata-dmr" ) . find ( ".openwebrx-dmr-timeslot-panel" ) . get ( meta [ 'slot' ] ) ;
2019-06-09 13:15:27 +00:00
var id = "" ;
var name = "" ;
2019-06-09 15:39:15 +00:00
var target = "" ;
2019-06-09 20:27:35 +00:00
var group = false ;
2019-10-16 15:11:09 +00:00
$ ( el ) [ meta [ 'sync' ] ? "addClass" : "removeClass" ] ( "sync" ) ;
if ( meta [ 'sync' ] && meta [ 'sync' ] === "voice" ) {
id = ( meta [ 'additional' ] && meta [ 'additional' ] [ 'callsign' ] ) || meta [ 'source' ] || "" ;
name = ( meta [ 'additional' ] && meta [ 'additional' ] [ 'fname' ] ) || "" ;
if ( meta [ 'type' ] === "group" ) {
2019-06-09 20:27:35 +00:00
target = "Talkgroup: " ;
group = true ;
2019-05-30 15:19:46 +00:00
}
2019-10-16 15:11:09 +00:00
if ( meta [ 'type' ] === "direct" ) target = "Direct: " ;
target += meta [ 'target' ] || "" ;
2019-06-09 13:15:27 +00:00
$ ( el ) . addClass ( "active" ) ;
} else {
$ ( el ) . removeClass ( "active" ) ;
2019-05-30 15:19:46 +00:00
}
2019-06-09 13:15:27 +00:00
$ ( el ) . find ( ".openwebrx-dmr-id" ) . text ( id ) ;
$ ( el ) . find ( ".openwebrx-dmr-name" ) . text ( name ) ;
2019-06-09 15:39:15 +00:00
$ ( el ) . find ( ".openwebrx-dmr-target" ) . text ( target ) ;
2019-06-09 20:27:35 +00:00
$ ( el ) . find ( ".openwebrx-meta-user-image" ) [ group ? "addClass" : "removeClass" ] ( "group" ) ;
2019-06-10 19:30:46 +00:00
} else {
2019-06-15 17:10:33 +00:00
clear _metadata ( ) ;
2019-05-14 21:30:03 +00:00
}
break ;
case 'YSF' :
2019-10-16 15:11:09 +00:00
el = $ ( "#openwebrx-panel-metadata-ysf" ) ;
2019-06-09 15:39:15 +00:00
2019-10-16 15:11:09 +00:00
var mode = " " ;
2019-06-09 15:39:15 +00:00
var source = "" ;
var up = "" ;
var down = "" ;
2019-10-16 15:11:09 +00:00
if ( meta [ 'mode' ] && meta [ 'mode' ] !== "" ) {
mode = "Mode: " + meta [ 'mode' ] ;
source = meta [ 'source' ] || "" ;
if ( meta [ 'lat' ] && meta [ 'lon' ] && meta [ 'source' ] ) {
source = "<a class=\"openwebrx-maps-pin\" href=\"/map?callsign=" + meta [ 'source' ] + "\" target=\"_blank\"></a>" + source ;
2019-06-19 21:16:57 +00:00
}
2019-10-16 15:11:09 +00:00
up = meta [ 'up' ] ? "Up: " + meta [ 'up' ] : "" ;
down = meta [ 'down' ] ? "Down: " + meta [ 'down' ] : "" ;
2019-06-09 15:39:15 +00:00
$ ( el ) . find ( ".openwebrx-meta-slot" ) . addClass ( "active" ) ;
} else {
$ ( el ) . find ( ".openwebrx-meta-slot" ) . removeClass ( "active" ) ;
2019-05-14 21:30:03 +00:00
}
2019-06-09 15:39:15 +00:00
$ ( el ) . find ( ".openwebrx-ysf-mode" ) . text ( mode ) ;
2019-06-19 21:16:57 +00:00
$ ( el ) . find ( ".openwebrx-ysf-source" ) . html ( source ) ;
2019-06-09 15:39:15 +00:00
$ ( el ) . find ( ".openwebrx-ysf-up" ) . text ( up ) ;
$ ( el ) . find ( ".openwebrx-ysf-down" ) . text ( down ) ;
2019-05-14 21:30:03 +00:00
break ;
2019-06-09 17:12:37 +00:00
} else {
2019-06-15 17:10:33 +00:00
clear _metadata ( ) ;
2019-05-14 21:30:03 +00:00
}
2019-05-16 21:09:57 +00:00
}
2019-07-14 12:33:30 +00:00
function html _escape ( input ) {
return $ ( '<div/>' ) . text ( input ) . html ( )
}
2019-07-07 12:31:12 +00:00
function update _wsjt _panel ( msg ) {
2019-10-16 11:17:47 +00:00
var $b = $ ( '#openwebrx-panel-wsjt-message' ) . find ( 'tbody' ) ;
2019-07-07 12:31:12 +00:00
var t = new Date ( msg [ 'timestamp' ] ) ;
2019-10-16 11:17:47 +00:00
var pad = function ( i ) {
return ( '' + i ) . padStart ( 2 , "0" ) ;
} ;
2019-07-07 20:36:34 +00:00
var linkedmsg = msg [ 'msg' ] ;
2019-10-16 15:11:09 +00:00
var matches ;
2019-07-20 11:38:25 +00:00
if ( [ 'FT8' , 'JT65' , 'JT9' , 'FT4' ] . indexOf ( msg [ 'mode' ] ) >= 0 ) {
2019-10-16 15:11:09 +00:00
matches = linkedmsg . match ( /(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/ ) ;
2019-10-16 11:17:47 +00:00
if ( matches && matches [ 2 ] !== 'RR73' ) {
2019-07-14 12:33:30 +00:00
linkedmsg = html _escape ( matches [ 1 ] ) + '<a href="/map?locator=' + matches [ 2 ] + '" target="_blank">' + matches [ 2 ] + '</a>' ;
} else {
linkedmsg = html _escape ( linkedmsg ) ;
}
2019-10-16 11:17:47 +00:00
} else if ( msg [ 'mode' ] === 'WSPR' ) {
2019-10-16 15:11:09 +00:00
matches = linkedmsg . match ( /([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/ ) ;
2019-07-14 12:33:30 +00:00
if ( matches ) {
linkedmsg = html _escape ( matches [ 1 ] ) + '<a href="/map?locator=' + matches [ 2 ] + '" target="_blank">' + matches [ 2 ] + '</a>' + html _escape ( matches [ 3 ] ) ;
} else {
linkedmsg = html _escape ( linkedmsg ) ;
}
2019-07-07 20:36:34 +00:00
}
2019-07-07 12:31:12 +00:00
$b . append ( $ (
'<tr data-timestamp="' + msg [ 'timestamp' ] + '">' +
2019-10-16 11:17:47 +00:00
'<td>' + pad ( t . getUTCHours ( ) ) + pad ( t . getUTCMinutes ( ) ) + pad ( t . getUTCSeconds ( ) ) + '</td>' +
'<td class="decimal">' + msg [ 'db' ] + '</td>' +
'<td class="decimal">' + msg [ 'dt' ] + '</td>' +
'<td class="decimal freq">' + msg [ 'freq' ] + '</td>' +
'<td class="message">' + linkedmsg + '</td>' +
2019-07-07 12:31:12 +00:00
'</tr>'
) ) ;
$b . scrollTop ( $b [ 0 ] . scrollHeight ) ;
}
2019-09-13 20:29:04 +00:00
var digital _removal _interval ;
2019-07-07 12:31:12 +00:00
// remove old wsjt messages in fixed intervals
2019-09-13 20:29:04 +00:00
function init _digital _removal _timer ( ) {
if ( digital _removal _interval ) clearInterval ( digital _removal _interval ) ;
2019-10-16 11:17:47 +00:00
digital _removal _interval = setInterval ( function ( ) {
[ '#openwebrx-panel-wsjt-message' , '#openwebrx-panel-packet-message' ] . forEach ( function ( root ) {
2019-09-13 20:29:04 +00:00
var $elements = $ ( root + ' tbody tr' ) ;
// limit to 1000 entries in the list since browsers get laggy at some point
var toRemove = $elements . length - 1000 ;
if ( toRemove <= 0 ) return ;
$elements . slice ( 0 , toRemove ) . remove ( ) ;
} ) ;
2019-07-07 12:31:12 +00:00
} , 15000 ) ;
}
2019-09-13 20:29:04 +00:00
function update _packet _panel ( msg ) {
2019-10-16 11:17:47 +00:00
var $b = $ ( '#openwebrx-panel-packet-message' ) . find ( 'tbody' ) ;
var pad = function ( i ) {
return ( '' + i ) . padStart ( 2 , "0" ) ;
} ;
2019-09-13 20:29:04 +00:00
2019-10-16 11:17:47 +00:00
if ( msg . type && msg . type === 'thirdparty' && msg . data ) {
2019-09-13 20:29:04 +00:00
msg = msg . data ;
}
var source = msg . source ;
if ( msg . type ) {
2019-10-16 11:17:47 +00:00
if ( msg . type === 'item' ) {
2019-09-13 20:29:04 +00:00
source = msg . item ;
}
2019-10-16 11:17:47 +00:00
if ( msg . type === 'object' ) {
2019-09-13 20:29:04 +00:00
source = msg . object ;
}
}
var timestamp = '' ;
if ( msg . timestamp ) {
var t = new Date ( msg . timestamp ) ;
timestamp = pad ( t . getUTCHours ( ) ) + pad ( t . getUTCMinutes ( ) ) + pad ( t . getUTCSeconds ( ) )
}
var link = '' ;
2019-09-18 22:18:51 +00:00
var classes = [ ] ;
var styles = { } ;
var overlay = '' ;
2019-10-16 11:17:47 +00:00
var stylesToString = function ( s ) {
return $ . map ( s , function ( value , key ) {
return key + ':' + value + ';'
} ) . join ( '' )
} ;
2019-09-18 22:18:51 +00:00
if ( msg . symbol ) {
classes . push ( 'aprs-symbol' ) ;
2019-10-16 11:17:47 +00:00
classes . push ( 'aprs-symboltable-' + ( msg . symbol . table === '/' ? 'normal' : 'alternate' ) ) ;
2019-09-18 22:18:51 +00:00
styles [ 'background-position-x' ] = - ( msg . symbol . index % 16 ) * 15 + 'px' ;
styles [ 'background-position-y' ] = - Math . floor ( msg . symbol . index / 16 ) * 15 + 'px' ;
2019-10-16 11:17:47 +00:00
if ( msg . symbol . table !== '/' && msg . symbol . table !== '\\' ) {
2019-10-16 15:11:09 +00:00
var s = { } ;
2019-09-18 22:18:51 +00:00
s [ 'background-position-x' ] = - ( msg . symbol . tableindex % 16 ) * 15 + 'px' ;
s [ 'background-position-y' ] = - Math . floor ( msg . symbol . tableindex / 16 ) * 15 + 'px' ;
2019-10-16 11:17:47 +00:00
overlay = '<div class="aprs-symbol aprs-symboltable-overlay" style="' + stylesToString ( s ) + '"></div>' ;
2019-09-18 22:18:51 +00:00
}
} else if ( msg . lat && msg . lon ) {
classes . push ( 'openwebrx-maps-pin' ) ;
}
var attrs = [
'class="' + classes . join ( ' ' ) + '"' ,
2019-10-16 11:17:47 +00:00
'style="' + stylesToString ( styles ) + '"'
2019-09-18 22:18:51 +00:00
] . join ( ' ' ) ;
2019-09-13 20:29:04 +00:00
if ( msg . lat && msg . lon ) {
2019-09-18 22:18:51 +00:00
link = '<a ' + attrs + ' href="/map?callsign=' + source + '" target="_blank">' + overlay + '</a>' ;
} else {
link = '<div ' + attrs + '>' + overlay + '</div>'
2019-09-13 20:29:04 +00:00
}
$b . append ( $ (
'<tr>' +
2019-10-16 11:17:47 +00:00
'<td>' + timestamp + '</td>' +
'<td class="callsign">' + source + '</td>' +
'<td class="coord">' + link + '</td>' +
'<td class="message">' + ( msg . comment || msg . message || '' ) + '</td>' +
2019-09-13 20:29:04 +00:00
'</tr>'
) ) ;
$b . scrollTop ( $b [ 0 ] . scrollHeight ) ;
}
2019-09-29 15:16:08 +00:00
function update _digitalvoice _panels ( showing ) {
2019-10-16 11:17:47 +00:00
$ ( ".openwebrx-meta-panel" ) . each ( function ( _ , p ) {
toggle _panel ( p . id , p . id === showing ) ;
2019-06-09 15:39:15 +00:00
} ) ;
2019-06-15 17:10:33 +00:00
clear _metadata ( ) ;
2019-05-16 21:09:57 +00:00
}
function clear _metadata ( ) {
2019-06-15 17:10:33 +00:00
$ ( ".openwebrx-meta-panel .openwebrx-meta-autoclear" ) . text ( "" ) ;
$ ( ".openwebrx-meta-slot" ) . removeClass ( "active" ) . removeClass ( "sync" ) ;
$ ( ".openwebrx-dmr-timeslot-panel" ) . removeClass ( "muted" ) ;
2019-05-14 21:30:03 +00:00
}
2019-10-16 15:11:09 +00:00
var waterfall _measure _minmax = false ;
var waterfall _measure _minmax _now = false ;
var waterfall _measure _minmax _min = 1e100 ;
var waterfall _measure _minmax _max = - 1e100 ;
2015-08-17 18:32:58 +00:00
2019-10-16 11:17:47 +00:00
function waterfall _measure _minmax _do ( what ) {
waterfall _measure _minmax _min = Math . min ( waterfall _measure _minmax _min , Math . min . apply ( Math , what ) ) ;
waterfall _measure _minmax _max = Math . max ( waterfall _measure _minmax _max , Math . max . apply ( Math , what ) ) ;
2015-08-17 18:32:58 +00:00
}
2019-10-16 11:17:47 +00:00
function on _ws _opened ( ) {
ws . send ( "SERVER DE CLIENT client=openwebrx.js type=receiver" ) ;
2019-10-16 15:11:09 +00:00
divlog ( "WebSocket opened to " + ws . url ) ;
2019-10-26 20:44:54 +00:00
if ( ! networkSpeedMeasurement ) {
networkSpeedMeasurement = new Measurement ( ) ;
networkSpeedMeasurement . report ( 60000 , 1000 , function ( rate ) {
networkSpeedProgressBar . setSpeed ( rate ) ;
} ) ;
} else {
networkSpeedMeasurement . reset ( ) ;
}
2019-10-16 11:17:47 +00:00
reconnect _timeout = false ;
2019-10-20 16:53:23 +00:00
ws . send ( JSON . stringify ( {
"type" : "dspcontrol" ,
"action" : "start" ,
"params" : { "output_rate" : audioEngine . getOutputRate ( ) }
} ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var was _error = 0 ;
2015-08-17 18:32:58 +00:00
2019-10-16 11:17:47 +00:00
function divlog ( what , is _error ) {
is _error = ! ! is _error ;
was _error |= is _error ;
if ( is _error ) {
what = "<span class=\"webrx-error\">" + what + "</span>" ;
2019-11-01 15:58:36 +00:00
toggle _panel ( "openwebrx-panel-log" , true ) ; //show panel if any error is present
2019-10-16 11:17:47 +00:00
}
e ( "openwebrx-debugdiv" ) . innerHTML += what + "<br />" ;
2019-10-16 15:11:09 +00:00
var nano = $ ( '.nano' ) ;
nano . nanoScroller ( ) ;
nano . nanoScroller ( { scroll : 'bottom' } ) ;
2014-11-29 00:07:10 +00:00
}
2016-02-13 23:31:28 +00:00
var volumeBeforeMute = 100.0 ;
2016-02-06 13:49:10 +00:00
var mute = false ;
2014-11-29 00:07:10 +00:00
// Optimalise these if audio lags or is choppy:
2019-10-18 19:34:00 +00:00
var audio _buffer _maximal _length _sec = 1 ; //actual number of samples are calculated from sample rate
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function webrx _set _param ( what , value ) {
2019-10-16 15:11:09 +00:00
var params = { } ;
2019-05-04 21:11:13 +00:00
params [ what ] = value ;
2019-10-16 11:17:47 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : params } ) ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
var starting _offset _frequency ;
var starting _mod ;
2018-09-25 12:56:47 +00:00
2019-10-20 16:53:23 +00:00
function parseHash ( ) {
2019-10-16 15:11:09 +00:00
var h ;
2019-10-16 11:17:47 +00:00
if ( h = window . location . hash ) {
h . substring ( 1 ) . split ( "," ) . forEach ( function ( x ) {
2019-10-16 15:11:09 +00:00
var harr = x . split ( "=" ) ;
2019-10-16 11:17:47 +00:00
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 ;
}
} ) ;
2016-03-20 10:32:37 +00:00
2019-10-16 11:17:47 +00:00
}
2015-09-30 14:06:30 +00:00
}
2014-12-12 12:55:10 +00:00
2019-10-20 16:53:23 +00:00
function onAudioStart ( success , apiType ) {
divlog ( 'Web Audio API succesfully initialized, using ' + apiType + ' API, sample rate: ' + audioEngine . getSampleRate ( ) + " Hz" ) ;
2019-10-16 11:17:47 +00:00
2019-10-20 16:53:23 +00:00
// canvas_container is set after waterfall_init() has been called. we cannot initialize before.
if ( canvas _container ) initialize _demodulator ( ) ;
2019-10-16 11:17:47 +00:00
2019-10-20 16:53:23 +00:00
//hide log panel in a second (if user has not hidden it yet)
window . setTimeout ( function ( ) {
2019-11-01 15:58:36 +00:00
toggle _panel ( "openwebrx-panel-log" , ! ! was _error ) ;
2019-10-20 16:53:23 +00:00
} , 2000 ) ;
2019-10-19 11:09:41 +00:00
//Synchronise volume with slider
updateVolume ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-02 22:36:26 +00:00
function initialize _demodulator ( ) {
2019-10-16 11:17:47 +00:00
demodulator _analog _replace ( starting _mod ) ;
if ( starting _offset _frequency ) {
demodulators [ 0 ] . offset _frequency = starting _offset _frequency ;
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , center _freq + starting _offset _frequency , 1e6 , 4 ) ;
demodulators [ 0 ] . set ( ) ;
mkscale ( ) ;
}
2019-10-02 22:36:26 +00:00
}
2019-07-13 19:40:48 +00:00
var reconnect _timeout = false ;
2019-10-16 11:17:47 +00:00
function on _ws _closed ( ) {
if ( reconnect _timeout ) {
// max value: roundabout 8 and a half minutes
reconnect _timeout = Math . min ( reconnect _timeout * 2 , 512000 ) ;
} else {
// initial value: 1s
reconnect _timeout = 1000 ;
}
divlog ( "WebSocket has closed unexpectedly. Attempting to reconnect in " + reconnect _timeout / 1000 + " seconds..." , 1 ) ;
setTimeout ( open _websocket , reconnect _timeout ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 15:11:09 +00:00
function on _ws _error ( ) {
2019-10-16 11:17:47 +00:00
divlog ( "WebSocket error." , 1 ) ;
}
2014-12-11 19:04:24 +00:00
2019-10-16 11:17:47 +00:00
String . prototype . startswith = function ( str ) {
return this . indexOf ( str ) === 0 ;
} ; //http://stackoverflow.com/questions/646628/how-to-check-if-a-string-startswith-another-string
2019-10-16 15:11:09 +00:00
var ws ;
2019-10-16 11:17:47 +00:00
function open _websocket ( ) {
var protocol = 'ws' ;
if ( window . location . toString ( ) . startsWith ( 'https://' ) ) {
protocol = 'wss' ;
}
2019-10-16 15:11:09 +00:00
var ws _url = protocol + "://" + ( window . location . origin . split ( "://" ) [ 1 ] ) + "/ws/" ; //guess automatically -> now default behaviour
2019-10-16 11:17:47 +00:00
if ( ! ( "WebSocket" in window ) )
divlog ( "Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser." ) ;
ws = new WebSocket ( ws _url ) ;
ws . onopen = on _ws _opened ;
ws . onmessage = on _ws _recv ;
ws . onclose = on _ws _closed ;
ws . binaryType = "arraybuffer" ;
window . onbeforeunload = function ( ) { //http://stackoverflow.com/questions/4812686/closing-websocket-correctly-html5-javascript
ws . onclose = function ( ) {
} ;
ws . close ( ) ;
} ;
ws . onerror = on _ws _error ;
}
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 ;
2019-10-16 15:11:09 +00:00
var full _scale = waterfall _max _level - waterfall _min _level ;
var relative _value = db _value - waterfall _min _level ;
var value _percent = relative _value / full _scale ;
var percent _for _one _color = 1 / ( waterfall _colors _arg . length - 1 ) ;
var index = Math . floor ( value _percent / percent _for _one _color ) ;
var remain = ( value _percent - percent _for _one _color * index ) / percent _for _one _color ;
2019-10-16 11:17:47 +00:00
return color _between ( waterfall _colors _arg [ index + 1 ] , waterfall _colors _arg [ index ] , remain ) ;
}
function color _between ( first , second , percent ) {
2019-10-16 15:11:09 +00:00
var output = 0 ;
for ( var i = 0 ; i < 4 ; i ++ ) {
var add = ( ( ( ( first & ( 0xff << ( i * 8 ) ) ) >>> 0 ) * percent ) + ( ( ( second & ( 0xff << ( i * 8 ) ) ) >>> 0 ) * ( 1 - percent ) ) ) & ( 0xff << ( i * 8 ) ) ;
2019-10-16 11:17:47 +00:00
output |= add >>> 0 ;
}
return output >>> 0 ;
2014-11-29 00:07:10 +00:00
}
var canvas _context ;
var canvases = [ ] ;
var canvas _default _height = 200 ;
var canvas _container ;
2019-10-16 15:11:09 +00:00
var canvas _actual _line ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function add _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 ;
new _canvas . openwebrx _top = ( - canvas _default _height + 1 ) ;
new _canvas . style . top = new _canvas . openwebrx _top . toString ( ) + "px" ;
canvas _context = new _canvas . getContext ( "2d" ) ;
canvas _container . appendChild ( new _canvas ) ;
canvases . push ( new _canvas ) ;
2016-10-29 19:43:18 +00:00
while ( canvas _container && canvas _container . clientHeight + canvas _default _height * 2 < canvases . length * canvas _default _height ) {
var c = canvases . shift ( ) ;
if ( ! c ) break ;
canvas _container . removeChild ( c ) ;
}
2014-11-29 00:07:10 +00:00
}
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
function init _canvas _container ( ) {
canvas _container = e ( "webrx-canvas-container" ) ;
mathbox _container = e ( "openwebrx-mathbox-container" ) ;
canvas _container . addEventListener ( "mouseleave" , canvas _container _mouseleave , false ) ;
canvas _container . addEventListener ( "mousemove" , canvas _mousemove , false ) ;
canvas _container . addEventListener ( "mouseup" , canvas _mouseup , false ) ;
canvas _container . addEventListener ( "mousedown" , canvas _mousedown , false ) ;
canvas _container . addEventListener ( "wheel" , canvas _mousewheel , false ) ;
var frequency _container = e ( "openwebrx-frequency-container" ) ;
frequency _container . addEventListener ( "wheel" , canvas _mousewheel , false ) ;
add _canvas ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
canvas _maxshift = 0 ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function shift _canvases ( ) {
canvases . forEach ( function ( p ) {
p . style . top = ( p . openwebrx _top ++ ) . toString ( ) + "px" ;
} ) ;
canvas _maxshift ++ ;
}
function resize _canvases ( zoom ) {
if ( typeof zoom === "undefined" ) zoom = false ;
if ( ! zoom ) mkzoomlevels ( ) ;
zoom _calc ( ) ;
2019-11-01 18:48:08 +00:00
$ ( '#webrx-canvas-container' ) . css ( {
width : waterfallWidth ( ) * zoom _levels [ zoom _level ] + 'px' ,
left : zoom _offset _px + "px"
2019-10-16 11:17:47 +00:00
} ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function waterfall _init ( ) {
init _canvas _container ( ) ;
resize _canvases ( ) ;
scale _setup ( ) ;
mkzoomlevels ( ) ;
waterfall _setup _done = 1 ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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 ++ ;
} ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
var mathbox _clear _data = function ( ) {
mathbox _data _index = 50 ;
mathbox _data _current _depth = 0 ;
} ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
var mathbox _get _data _line = function ( x ) {
return ( mathbox _data _max _depth + mathbox _data _index + x - 1 ) % mathbox _data _max _depth ;
} ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
var mathbox _data _index _valid = function ( x ) {
return x > mathbox _data _max _depth - mathbox _data _current _depth ;
} ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
function waterfall _add ( data ) {
if ( ! waterfall _setup _done ) return ;
var w = fft _size ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
if ( waterfall _measure _minmax ) waterfall _measure _minmax _do ( data ) ;
if ( waterfall _measure _minmax _now ) {
waterfall _measure _minmax _do ( data ) ;
waterfall _measure _minmax _now = false ;
waterfallColorsAuto ( ) ;
}
2016-03-20 10:32:37 +00:00
2019-10-16 11:17:47 +00:00
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 {
2019-05-04 18:26:11 +00:00
//Add line to waterfall image
2019-10-16 15:11:09 +00:00
var oneline _image = canvas _context . createImageData ( w , 1 ) ;
for ( var x = 0 ; x < w ; x ++ ) {
var color = waterfall _mkcolor ( data [ x ] ) ;
2019-10-16 11:17:47 +00:00
for ( i = 0 ; i < 4 ; i ++ )
oneline _image . data [ x * 4 + i ] = ( ( color >>> 0 ) >> ( ( 3 - i ) * 8 ) ) & 0xff ;
2019-05-04 18:26:11 +00:00
}
2014-11-29 00:07:10 +00:00
2019-05-04 18:26:11 +00:00
//Draw image
canvas _context . putImageData ( oneline _image , 0 , canvas _actual _line -- ) ;
shift _canvases ( ) ;
2019-10-16 11:17:47 +00:00
if ( canvas _actual _line < 0 ) add _canvas ( ) ;
}
2018-09-25 12:56:47 +00:00
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function check _top _bar _congestion ( ) {
var rmf = function ( x ) {
return x . offsetLeft + x . offsetWidth ;
} ;
var wet = e ( "webrx-rx-title" ) ;
var wed = e ( "webrx-rx-desc" ) ;
var tl = e ( "openwebrx-main-buttons" ) ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
[ wet , wed ] . map ( function ( what ) {
if ( rmf ( what ) > tl . offsetLeft - 20 ) what . style . opacity = what . style . opacity = "0" ;
else wet . style . opacity = wed . style . opacity = "1" ;
} ) ;
2015-08-17 18:32:58 +00:00
2014-11-29 00:07:10 +00:00
}
2018-09-25 12:56:47 +00:00
var MATHBOX _MODES =
2019-10-16 11:17:47 +00:00
{
UNINITIALIZED : 0 ,
NONE : 1 ,
WATERFALL : 2 ,
CONSTELLATION : 3
} ;
2018-09-25 12:56:47 +00:00
var mathbox _mode = MATHBOX _MODES . UNINITIALIZED ;
var mathbox ;
var mathbox _element ;
2019-10-16 15:11:09 +00:00
var mathbox _waterfall _colors ;
var mathbox _waterfall _frequency _resolution ;
var mathbox _waterfall _history _length ;
var mathbox _correction _for _z ;
var mathbox _data _max _depth ;
var mathbox _data _current _depth ;
var mathbox _data _index ;
var mathbox _data ;
var mathbox _data _global _index ;
var mathbox _container ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
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 }
2018-09-25 12:56:47 +00:00
} ) ;
2019-10-16 15:11:09 +00:00
var three = mathbox . three ;
2019-10-16 11:17:47 +00:00
if ( typeof three === "undefined" ) divlog ( "3D waterfall cannot be initialized because WebGL is not supported in your browser." , true ) ;
2018-09-25 12:56:47 +00:00
three . renderer . setClearColor ( new THREE . Color ( 0x808080 ) , 1.0 ) ;
2019-10-16 11:17:47 +00:00
mathbox _container . appendChild ( ( mathbox _element = three . renderer . domElement ) ) ;
2019-10-16 15:11:09 +00:00
var view = mathbox
2019-10-16 11:17:47 +00:00
. 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 ]
} ) ;
2018-09-25 12:56:47 +00:00
view . axis ( {
2019-10-16 11:17:47 +00:00
axis : 1 ,
width : 3 ,
color : "#fff"
} ) ;
2018-09-25 12:56:47 +00:00
view . axis ( {
2019-10-16 11:17:47 +00:00
axis : 2 ,
width : 3 ,
color : "#fff"
//offset: [0, 0, 0],
} ) ;
2018-09-25 12:56:47 +00:00
view . axis ( {
2019-10-16 11:17:47 +00:00
axis : 3 ,
width : 3 ,
color : "#fff"
} ) ;
2018-09-25 12:56:47 +00:00
view . grid ( {
2019-10-16 11:17:47 +00:00
width : 2 ,
opacity : 0.5 ,
axes : [ 1 , 3 ] ,
zOrder : 1 ,
color : "#fff"
2018-09-25 12:56:47 +00:00
} ) ;
2019-10-16 11:17:47 +00:00
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 } ;
var index = Math . trunc ( xIndex + realZIndex * fft _size ) ;
var dBValue = mathbox _data [ index ] ;
2019-10-16 15:11:09 +00:00
var y ;
2019-10-16 11:17:47 +00:00
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 ) ;
if ( ! y ) y = 0 ;
return { y : y , dBValue : dBValue , zAdd : zAdd } ;
} ;
2018-09-25 12:56:47 +00:00
2019-10-16 15:11:09 +00:00
view . area ( {
2019-10-16 11:17:47 +00:00
expr : function ( emit , x , z , i , j , t ) {
var y ;
2019-10-16 15:11:09 +00:00
var remapResult = remap ( x , z , t ) ;
2019-10-16 11:17:47 +00:00
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 ]
2018-09-25 12:56:47 +00:00
} ) ;
2019-10-16 15:11:09 +00:00
view . area ( {
2019-10-16 11:17:47 +00:00
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 ]
2018-09-25 12:56:47 +00:00
} ) ;
view . surface ( {
2019-10-16 11:17:47 +00:00
shaded : true ,
points : '<<' ,
colors : '<' ,
color : 0xFFFFFF
2018-09-25 12:56:47 +00:00
} ) ;
view . surface ( {
2019-10-16 11:17:47 +00:00
fill : false ,
lineX : false ,
lineY : false ,
points : '<<' ,
colors : '<' ,
color : 0xFFFFFF ,
width : 2 ,
blending : 'add' ,
opacity : . 25 ,
zBias : 5
2018-09-25 12:56:47 +00:00
} ) ;
2019-10-16 11:17:47 +00:00
mathbox _mode = MATHBOX _MODES . NONE ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function mathbox _toggle ( ) {
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
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 ( ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function waterfall _clear ( ) {
while ( canvases . length ) //delete all canvases
{
var x = canvases . shift ( ) ;
x . parentNode . removeChild ( x ) ;
}
add _canvas ( ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function openwebrx _resize ( ) {
resize _canvases ( ) ;
resize _scale ( ) ;
check _top _bar _congestion ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function init _header ( ) {
$ ( '#openwebrx-main-buttons' ) . find ( 'li[data-toggle-panel]' ) . click ( function ( ) {
2019-07-11 17:37:00 +00:00
toggle _panel ( $ ( this ) . data ( 'toggle-panel' ) ) ;
} ) ;
}
2019-10-22 20:35:54 +00:00
var audioBufferProgressBar ;
var networkSpeedProgressBar ;
var audioSpeedProgressBar ;
var audioOutputProgressBar ;
var clientProgressBar ;
var cpuProgressBar ;
function initProgressBars ( ) {
audioBufferProgressBar = new AudioBufferProgressBar ( $ ( '#openwebrx-bar-audio-buffer' ) , audioEngine . getSampleRate ( ) ) ;
networkSpeedProgressBar = new NetworkSpeedProgressBar ( $ ( '#openwebrx-bar-network-speed' ) ) ;
audioSpeedProgressBar = new AudioSpeedProgressBar ( $ ( '#openwebrx-bar-audio-speed' ) ) ;
audioOutputProgressBar = new AudioOutputProgressBar ( $ ( '#openwebrx-bar-audio-output' ) , audioEngine . getSampleRate ( ) ) ;
clientProgressBar = new ClientsProgressBar ( $ ( '#openwebrx-bar-clients' ) ) ;
cpuProgressBar = new CpuProgressBar ( $ ( '#openwebrx-bar-server-cpu' ) ) ;
2019-10-23 09:27:05 +00:00
}
2019-10-20 16:53:23 +00:00
function audioReporter ( stats ) {
if ( typeof ( stats . buffersize ) !== 'undefined' ) {
2019-10-22 20:35:54 +00:00
audioBufferProgressBar . setBuffersize ( stats . buffersize ) ;
2019-10-20 16:53:23 +00:00
}
if ( typeof ( stats . audioByteRate ) !== 'undefined' ) {
2019-10-22 20:35:54 +00:00
audioSpeedProgressBar . setSpeed ( stats . audioByteRate * 8 ) ;
2019-10-20 16:53:23 +00:00
}
if ( typeof ( stats . audioRate ) !== 'undefined' ) {
2019-10-22 20:35:54 +00:00
audioOutputProgressBar . setAudioRate ( stats . audioRate ) ;
2019-10-20 16:53:23 +00:00
}
}
2019-10-12 15:02:29 +00:00
var bookmarks ;
2019-10-20 16:53:23 +00:00
var audioEngine ;
2019-10-12 15:02:29 +00:00
2019-10-16 11:17:47 +00:00
function openwebrx _init ( ) {
2019-10-20 16:53:23 +00:00
audioEngine = new AudioEngine ( audio _buffer _maximal _length _sec , audioReporter ) ;
2019-11-01 15:58:36 +00:00
$overlay = $ ( '#openwebrx-autoplay-overlay' ) ;
$overlay . on ( 'click' , playButtonClick ) ;
2019-10-20 16:53:23 +00:00
if ( ! audioEngine . isAllowed ( ) ) {
2019-11-01 15:58:36 +00:00
$overlay . show ( ) ;
2019-10-20 16:53:23 +00:00
} else {
audioEngine . start ( onAudioStart ) ;
}
2019-10-22 20:35:54 +00:00
initProgressBars ( ) ;
2019-10-16 11:17:47 +00:00
init _rx _photo ( ) ;
open _websocket ( ) ;
2018-09-25 12:56:47 +00:00
secondary _demod _init ( ) ;
2019-06-15 17:10:33 +00:00
digimodes _init ( ) ;
2019-10-26 19:32:00 +00:00
initPanels ( ) ;
2019-10-16 11:17:47 +00:00
window . addEventListener ( "resize" , openwebrx _resize ) ;
check _top _bar _congestion ( ) ;
init _header ( ) ;
bookmarks = new BookmarkBar ( ) ;
2019-10-20 16:53:23 +00:00
parseHash ( ) ;
2019-10-24 18:00:30 +00:00
initSliders ( ) ;
}
function initSliders ( ) {
$ ( '#openwebrx-panel-receiver' ) . on ( 'wheel' , 'input[type=range]' , function ( ev ) {
var $slider = $ ( this ) ;
if ( ! $slider . attr ( 'step' ) ) return ;
var val = Number ( $slider . val ( ) ) ;
var step = Number ( $slider . attr ( 'step' ) ) ;
2019-10-27 14:09:34 +00:00
if ( ev . originalEvent . deltaY > 0 ) {
2019-10-24 18:00:30 +00:00
step *= - 1 ;
}
2019-10-27 14:09:34 +00:00
$slider . val ( val + step ) ;
2019-10-24 18:00:30 +00:00
$slider . trigger ( 'change' ) ;
} ) ;
2014-11-29 00:07:10 +00:00
}
2019-06-15 17:10:33 +00:00
function digimodes _init ( ) {
// initialze DMR timeslot muting
2019-10-16 11:17:47 +00:00
$ ( '.openwebrx-dmr-timeslot-panel' ) . click ( function ( e ) {
2019-06-15 17:10:33 +00:00
$ ( e . currentTarget ) . toggleClass ( "muted" ) ;
update _dmr _timeslot _filtering ( ) ;
} ) ;
}
function update _dmr _timeslot _filtering ( ) {
2019-10-16 11:17:47 +00:00
var filter = $ ( '.openwebrx-dmr-timeslot-panel' ) . map ( function ( index , el ) {
2019-06-15 17:10:33 +00:00
return ( ! $ ( el ) . hasClass ( "muted" ) ) << index ;
2019-10-16 11:17:47 +00:00
} ) . toArray ( ) . reduce ( function ( acc , v ) {
2019-06-15 17:10:33 +00:00
return acc | v ;
} , 0 ) ;
webrx _set _param ( "dmr_filter" , filter ) ;
}
2019-10-20 16:53:23 +00:00
function playButtonClick ( ) {
2019-10-16 11:17:47 +00:00
//On iOS, we can only start audio from a click or touch event.
2019-10-20 16:53:23 +00:00
audioEngine . start ( onAudioStart ) ;
2019-11-07 09:56:39 +00:00
var $overlay = $ ( '#openwebrx-autoplay-overlay' ) ;
2019-11-01 15:58:36 +00:00
$overlay . css ( 'opacity' , 0 ) ;
$overlay . on ( 'transitionend' , function ( ) {
$overlay . hide ( ) ;
} ) ;
2016-03-26 23:47:26 +00:00
}
2019-10-16 11:17:47 +00:00
var rt = function ( s , n ) {
return s . replace ( /[a-zA-Z]/g , function ( c ) {
return String . fromCharCode ( ( c <= "Z" ? 90 : 122 ) >= ( c = c . charCodeAt ( 0 ) + n ) ? c : c - 26 ) ;
} ) ;
} ;
2014-11-29 00:07:10 +00:00
// ========================================================
// ======================= PANELS =======================
// ========================================================
2019-10-26 19:32:00 +00:00
function panel _displayed ( el ) {
return ! ( el . style && el . style . display && el . style . display === 'none' )
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function toggle _panel ( what , on ) {
2019-10-26 19:32:00 +00:00
var item = $ ( '#' + what ) [ 0 ] ;
2019-06-09 15:39:15 +00:00
if ( ! item ) return ;
2019-10-26 19:32:00 +00:00
var displayed = panel _displayed ( item ) ;
if ( typeof on !== "undefined" && displayed === on ) {
return ;
2019-10-16 11:17:47 +00:00
}
if ( item . openwebrxDisableClick ) return ;
2019-10-26 19:32:00 +00:00
if ( displayed ) {
item . movement = 'collapse' ;
item . style . transform = "perspective(600px) rotateX(90deg)" ;
item . style . transitionProperty = 'transform' ;
} else {
item . movement = 'expand' ;
item . style . display = 'block' ;
setTimeout ( function ( ) {
item . style . transitionProperty = 'transform' ;
item . style . transform = 'perspective(600px) rotateX(0deg)' ;
} , 20 ) ;
2019-10-16 11:17:47 +00:00
}
2019-10-26 19:32:00 +00:00
item . style . transitionDuration = "600ms" ;
item . style . transitionDelay = "0ms" ;
2019-10-16 11:17:47 +00:00
item . openwebrxDisableClick = true ;
}
function first _show _panel ( panel ) {
panel . style . transitionDuration = 0 ;
panel . style . transitionDelay = 0 ;
2019-10-16 15:11:09 +00:00
var rotx = ( Math . random ( ) > 0.5 ) ? - 90 : 90 ;
var roty = 0 ;
2019-10-16 11:17:47 +00:00
if ( Math . random ( ) > 0.5 ) {
2019-10-16 15:11:09 +00:00
var rottemp = rotx ;
2019-10-16 11:17:47 +00:00
rotx = roty ;
roty = rottemp ;
}
if ( rotx !== 0 && Math . random ( ) > 0.5 ) rotx = 270 ;
2019-10-26 19:32:00 +00:00
panel . style . transform = "perspective(600px) rotateX(%1deg) rotateY(%2deg)"
2019-10-16 11:17:47 +00:00
. replace ( "%1" , rotx . toString ( ) ) . replace ( "%2" , roty . toString ( ) ) ;
window . setTimeout ( function ( ) {
2019-10-26 19:32:00 +00:00
panel . style . transitionDuration = "600ms" ;
2019-10-16 11:17:47 +00:00
panel . style . transitionDelay = ( Math . floor ( Math . random ( ) * 500 ) ) . toString ( ) + "ms" ;
2019-10-26 19:32:00 +00:00
panel . style . transform = "perspective(600px) rotateX(0deg) rotateY(0deg)" ;
2019-10-16 11:17:47 +00:00
} , 1 ) ;
}
2019-10-26 19:32:00 +00:00
function initPanels ( ) {
$ ( '#openwebrx-panels-container' ) . find ( '.openwebrx-panel' ) . each ( function ( ) {
var el = this ;
el . openwebrxPanelTransparent = ( ! ! el . dataset . panelTransparent ) ;
el . addEventListener ( 'transitionend' , function ( ev ) {
if ( ev . target !== el ) return ;
el . openwebrxDisableClick = false ;
el . style . transitionDuration = null ;
el . style . transitionDelay = null ;
el . style . transitionProperty = null ;
2019-11-07 09:56:39 +00:00
if ( el . movement && el . movement === 'collapse' ) {
2019-10-26 19:32:00 +00:00
el . style . display = 'none' ;
2019-10-16 11:17:47 +00:00
}
2019-10-26 19:32:00 +00:00
} ) ;
if ( panel _displayed ( el ) ) first _show _panel ( el ) ;
} ) ;
2019-10-16 11:17:47 +00:00
}
function demodulator _buttons _update ( ) {
$ ( ".openwebrx-demodulator-button" ) . removeClass ( "highlighted" ) ;
if ( secondary _demod ) {
$ ( "#openwebrx-button-dig" ) . addClass ( "highlighted" ) ;
$ ( '#openwebrx-secondary-demod-listbox' ) . val ( secondary _demod ) ;
} else switch ( demodulators [ 0 ] . subtype ) {
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 ;
default :
2019-05-14 21:36:37 +00:00
var mod = demodulators [ 0 ] . subtype ;
$ ( "#openwebrx-button-" + mod ) . addClass ( "highlighted" ) ;
break ;
2019-10-16 11:17:47 +00:00
}
}
function demodulator _analog _replace _last ( ) {
demodulator _analog _replace ( last _analog _demodulator _subtype ) ;
2018-09-25 12:56:47 +00:00
}
/ *
2019-10-16 11:17:47 +00:00
_ _ _ _ _ _ _ _
| _ _ \ ( _ ) ( _ ) | |
| | | | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | | _ _ _ _ _ _
2018-09-25 12:56:47 +00:00
| | | | | / _` | | '_ ` _ \ / _ \ / _ ` |/ _ \/ __|
| | _ _ | | | ( _ | | | | | | | | ( _ ) | ( _ | | _ _ / \ _ _ \
| _ _ _ _ _ / | _ | \ _ _ , | _ | _ | | _ | | _ | \ _ _ _ / \ _ _ , _ | \ _ _ _ || _ _ _ /
2019-10-16 11:17:47 +00:00
_ _ / |
| _ _ _ /
2018-09-25 12:56:47 +00:00
* /
2019-10-16 15:11:09 +00:00
var secondary _demod = false ;
var secondary _demod _fft _offset _db = 30 ; //need to calculate that later
var secondary _demod _canvases _initialized = false ;
var secondary _demod _listbox _updating = false ;
var secondary _demod _channel _freq = 1000 ;
var secondary _demod _waiting _for _set = false ;
var secondary _demod _low _cut ;
var secondary _demod _high _cut ;
var secondary _demod _mousedown = false ;
var secondary _demod _canvas _width ;
var secondary _demod _canvas _left ;
var secondary _demod _canvas _container ;
var secondary _demod _current _canvas _actual _line ;
var secondary _demod _current _canvas _context ;
var secondary _demod _current _canvas _index ;
var secondary _demod _canvases ;
2018-09-25 12:56:47 +00:00
2019-10-16 11:17:47 +00:00
function demodulator _digital _replace _last ( ) {
demodulator _digital _replace ( last _digital _demodulator _subtype ) ;
2018-09-25 12:56:47 +00:00
secondary _demod _listbox _update ( ) ;
}
2019-10-16 11:17:47 +00:00
function demodulator _digital _replace ( subtype ) {
switch ( subtype ) {
case "bpsk31" :
case "rtty" :
case "ft8" :
case "jt65" :
case "jt9" :
case "ft4" :
secondary _demod _start ( subtype ) ;
demodulator _analog _replace ( 'usb' , true ) ;
break ;
case "wspr" :
secondary _demod _start ( subtype ) ;
demodulator _analog _replace ( 'usb' , true ) ;
// WSPR only samples between 1400 and 1600 Hz
demodulators [ 0 ] . low _cut = 1350 ;
demodulators [ 0 ] . high _cut = 1650 ;
demodulators [ 0 ] . set ( ) ;
break ;
case "packet" :
secondary _demod _start ( subtype ) ;
demodulator _analog _replace ( 'nfm' , true ) ;
break ;
2018-09-25 12:56:47 +00:00
}
2019-09-29 15:16:08 +00:00
demodulator _buttons _update ( ) ;
2019-07-08 18:45:09 +00:00
$ ( '#openwebrx-panel-digimodes' ) . attr ( 'data-mode' , subtype ) ;
2018-09-25 12:56:47 +00:00
toggle _panel ( "openwebrx-panel-digimodes" , true ) ;
2019-07-20 11:38:25 +00:00
toggle _panel ( "openwebrx-panel-wsjt-message" , [ 'ft8' , 'wspr' , 'jt65' , 'jt9' , 'ft4' ] . indexOf ( subtype ) >= 0 ) ;
2019-10-16 11:17:47 +00:00
toggle _panel ( "openwebrx-panel-packet-message" , subtype === "packet" ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
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" ;
secondary _demod _current _canvas _actual _line = new _canvas . height - 1 ;
$ ( secondary _demod _canvas _container ) . children ( ) . last ( ) . before ( new _canvas ) ;
2018-09-25 12:56:47 +00:00
return new _canvas ;
}
2019-10-16 11:17:47 +00:00
function secondary _demod _remove _canvases ( ) {
2018-09-25 12:56:47 +00:00
$ ( secondary _demod _canvas _container ) . children ( "canvas" ) . remove ( ) ;
}
2019-10-16 11:17:47 +00:00
function secondary _demod _init _canvases ( ) {
2018-09-25 12:56:47 +00:00
secondary _demod _remove _canvases ( ) ;
2019-10-16 11:17:47 +00:00
secondary _demod _canvases = [ ] ;
2018-09-25 12:56:47 +00:00
secondary _demod _canvases . push ( secondary _demod _create _canvas ( ) ) ;
secondary _demod _canvases . push ( secondary _demod _create _canvas ( ) ) ;
2019-10-16 11:17:47 +00:00
secondary _demod _canvases [ 0 ] . openwebrx _top = - $ ( secondary _demod _canvas _container ) . height ( ) ;
secondary _demod _canvases [ 1 ] . openwebrx _top = 0 ;
2018-09-25 12:56:47 +00:00
secondary _demod _canvases _update _top ( ) ;
secondary _demod _current _canvas _context = secondary _demod _canvases [ 0 ] . getContext ( "2d" ) ;
2019-10-16 11:17:47 +00:00
secondary _demod _current _canvas _actual _line = $ ( secondary _demod _canvas _container ) . height ( ) - 1 ;
secondary _demod _current _canvas _index = 0 ;
secondary _demod _canvases _initialized = true ;
2018-09-25 12:56:47 +00:00
mkscale ( ) ; //so that the secondary waterfall zoom level will be initialized
}
2019-10-16 11:17:47 +00:00
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" ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _swap _canvases ( ) {
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 ;
2018-09-25 12:56:47 +00:00
secondary _demod _current _canvas _context = secondary _demod _canvases [ secondary _demod _current _canvas _index ] . getContext ( "2d" ) ;
2019-10-16 11:17:47 +00:00
secondary _demod _current _canvas _actual _line = $ ( secondary _demod _canvas _container ) . height ( ) - 1 ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _init ( ) {
2018-09-25 12:56:47 +00:00
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 )
2019-10-03 15:24:28 +00:00
. mouseleave ( secondary _demod _canvas _container _mouseleave ) ;
2019-09-13 20:29:04 +00:00
init _digital _removal _timer ( ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _start ( subtype ) {
2018-09-25 12:56:47 +00:00
secondary _demod _canvases _initialized = false ;
2019-10-16 11:17:47 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_mod" : subtype } } ) ) ;
2019-05-05 18:36:50 +00:00
secondary _demod = subtype ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _stop ( ) {
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_mod" : false } } ) ) ;
2019-05-05 18:36:50 +00:00
secondary _demod = false ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _push _data ( x ) {
x = Array . from ( x ) . filter ( function ( y ) {
var c = y . charCodeAt ( 0 ) ;
return ( c === 10 || ( c >= 32 && c <= 126 ) ) ;
} ) . map ( function ( y ) {
2019-11-07 09:56:39 +00:00
if ( y === "&" )
2019-10-16 11:17:47 +00:00
return "&" ;
if ( y === "<" ) return "<" ;
if ( y === ">" ) return ">" ;
if ( y === " " ) return " " ;
2018-09-25 12:56:47 +00:00
return y ;
2019-10-16 11:17:47 +00:00
} ) . map ( function ( y ) {
2019-11-07 09:56:39 +00:00
if ( y === "\n" )
2019-10-16 11:17:47 +00:00
return "<br />" ;
return "<span class=\"part\">" + y + "</span>" ;
2018-09-25 12:56:47 +00:00
} ) . join ( "" ) ;
2019-08-11 11:52:19 +00:00
$ ( "#openwebrx-cursor-blink" ) . before ( x ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _close _window ( ) {
2018-09-25 12:56:47 +00:00
secondary _demod _stop ( ) ;
toggle _panel ( "openwebrx-panel-digimodes" , false ) ;
2019-07-08 18:31:34 +00:00
toggle _panel ( "openwebrx-panel-wsjt-message" , false ) ;
2019-09-13 20:29:04 +00:00
toggle _panel ( "openwebrx-panel-packet-message" , false ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
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 ) ;
2019-10-16 15:11:09 +00:00
for ( var x = 0 ; x < w ; x ++ ) {
2019-10-16 11:17:47 +00:00
var color = waterfall _mkcolor ( data [ x ] + secondary _demod _fft _offset _db ) ;
2019-10-16 15:11:09 +00:00
for ( var i = 0 ; i < 4 ; i ++ ) oneline _image . data [ x * 4 + i ] = ( ( color >>> 0 ) >> ( ( 3 - i ) * 8 ) ) & 0xff ;
2019-10-16 11:17:47 +00:00
}
//Draw image
secondary _demod _current _canvas _context . putImageData ( oneline _image , 0 , secondary _demod _current _canvas _actual _line -- ) ;
secondary _demod _canvases . map ( function ( x ) {
x . openwebrx _top += 1 ;
} )
;
2018-09-25 12:56:47 +00:00
secondary _demod _canvases _update _top ( ) ;
2019-10-16 11:17:47 +00:00
if ( secondary _demod _current _canvas _actual _line < 0 ) secondary _demod _swap _canvases ( ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _listbox _changed ( ) {
2019-07-13 21:16:25 +00:00
if ( secondary _demod _listbox _updating ) return ;
var sdm = $ ( "#openwebrx-secondary-demod-listbox" ) [ 0 ] . value ;
2019-10-16 11:17:47 +00:00
if ( sdm === "none" ) {
2019-08-11 11:52:19 +00:00
demodulator _analog _replace _last ( ) ;
} else {
demodulator _digital _replace ( sdm ) ;
2018-09-25 12:56:47 +00:00
}
}
2019-10-16 11:17:47 +00:00
function secondary _demod _listbox _update ( ) {
2018-09-25 12:56:47 +00:00
secondary _demod _listbox _updating = true ;
2019-10-16 11:17:47 +00:00
$ ( "#openwebrx-secondary-demod-listbox" ) . val ( ( secondary _demod ) ? secondary _demod : "none" ) ;
2018-09-25 12:56:47 +00:00
secondary _demod _listbox _updating = false ;
}
2019-10-16 11:17:47 +00:00
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 ;
$ ( "#openwebrx-digimode-select-channel" ) . width ( width ) . css ( "left" , left + "px" )
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
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 ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
if ( ! secondary _demod _waiting _for _set ) {
2018-09-25 12:56:47 +00:00
secondary _demod _waiting _for _set = true ;
2019-10-16 11:17:47 +00:00
window . setTimeout ( function ( ) {
ws . send ( JSON . stringify ( {
"type" : "dspcontrol" ,
"params" : { "secondary_offset_freq" : Math . floor ( secondary _demod _channel _freq ) }
} ) ) ;
secondary _demod _waiting _for _set = false ;
} ,
50
)
;
2018-09-25 12:56:47 +00:00
}
secondary _demod _update _marker ( ) ;
}
2019-10-16 11:17:47 +00:00
function secondary _demod _canvas _container _mousein ( ) {
$ ( "#openwebrx-digimode-select-channel" ) . css ( "opacity" , "0.7" ) ; //.css("border-width", "1px");
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _canvas _container _mouseleave ( ) {
$ ( "#openwebrx-digimode-select-channel" ) . css ( "opacity" , "0" ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _canvas _container _mousemove ( evt ) {
if ( secondary _demod _mousedown ) secondary _demod _update _channel _freq _from _event ( evt ) ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _canvas _container _mousedown ( evt ) {
if ( evt . which === 1 ) secondary _demod _mousedown = true ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function secondary _demod _canvas _container _mouseup ( evt ) {
if ( evt . which === 1 ) secondary _demod _mousedown = false ;
2018-09-25 12:56:47 +00:00
secondary _demod _update _channel _freq _from _event ( evt ) ;
}
2019-10-16 11:17:47 +00:00
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 ) {
2018-09-25 12:56:47 +00:00
var hctmp = high _cut ;
var lctmp = low _cut ;
low _cut = - hctmp ;
2019-10-16 15:11:09 +00:00
high _cut = - lctmp ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
else if ( low _cut < 0 && high _cut > 0 ) {
high _cut = Math . max ( Math . abs ( high _cut ) , Math . abs ( low _cut ) ) ;
low _cut = 0 ;
2018-09-25 12:56:47 +00:00
}
secondary _demod _low _cut = low _cut ;
secondary _demod _high _cut = high _cut ;
2019-10-16 11:17:47 +00:00
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 ) ) ;
2018-09-25 12:56:47 +00:00
//console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
2019-10-16 11:17:47 +00:00
secondary _demod _canvases . map ( function ( x ) {
$ ( x ) . css ( "left" , secondary _demod _canvas _left + "px" ) . css ( "width" , secondary _demod _canvas _width + "px" ) ;
} )
;
2018-09-25 12:56:47 +00:00
secondary _demod _update _channel _freq _from _event ( ) ;
}
2019-05-10 14:14:16 +00:00
function sdr _profile _changed ( ) {
2019-10-16 15:11:09 +00:00
var value = $ ( '#openwebrx-sdr-profiles-listbox' ) . val ( ) ;
2019-10-16 11:17:47 +00:00
ws . send ( JSON . stringify ( { type : "selectprofile" , params : { profile : value } } ) ) ;
2019-05-10 14:14:16 +00:00
}