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 >
2021-01-22 17:10:51 +00:00
Copyright ( c ) 2019 - 2021 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 ;
2019-10-16 11:17:47 +00:00
var fft _compression = "none" ;
2020-01-05 22:33:07 +00:00
var fft _codec ;
2019-10-16 11:17:47 +00:00
var waterfall _setup _done = 0 ;
2018-09-25 12:56:47 +00:00
var secondary _fft _size ;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function updateVolume ( ) {
2021-01-15 17:09:18 +00:00
audioEngine . setVolume ( parseFloat ( $ ( "#openwebrx-panel-volume" ) . val ( ) ) / 100 ) ;
2016-02-06 13:49:10 +00:00
}
2019-10-16 11:17:47 +00:00
function toggleMute ( ) {
2021-01-15 17:09:18 +00:00
var $muteButton = $ ( '.openwebrx-mute-button' ) ;
var $volumePanel = $ ( '#openwebrx-panel-volume' ) ;
if ( $muteButton . hasClass ( 'muted' ) ) {
$muteButton . removeClass ( 'muted' ) ;
$volumePanel . prop ( 'disabled' , false ) . val ( volumeBeforeMute ) ;
2019-10-16 11:17:47 +00:00
} else {
2021-01-15 17:09:18 +00:00
$muteButton . addClass ( 'muted' ) ;
volumeBeforeMute = $volumePanel . val ( ) ;
$volumePanel . prop ( 'disabled' , true ) . val ( 0 ) ;
2019-10-16 11:17:47 +00:00
}
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 ( ) {
2022-06-18 13:57:06 +00:00
zoom _set ( zoom _levels _count ) ;
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 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 ;
2020-09-20 17:53:13 +00:00
var waterfall _colors = buildWaterfallColors ( [ '#000' , '#FFF' ] ) ;
2021-03-30 22:00:38 +00:00
var waterfall _auto _levels ;
var waterfall _auto _min _range ;
2019-10-16 15:11:09 +00:00
2020-09-19 13:51:54 +00:00
function buildWaterfallColors ( input ) {
return chroma . scale ( input ) . colors ( 256 , 'rgb' )
}
2019-10-16 11:17:47 +00:00
function updateWaterfallColors ( which ) {
2020-08-31 19:48:02 +00:00
var $wfmax = $ ( "#openwebrx-waterfall-color-max" ) ;
var $wfmin = $ ( "#openwebrx-waterfall-color-min" ) ;
waterfall _max _level = parseInt ( $wfmax . val ( ) ) ;
waterfall _min _level = parseInt ( $wfmin . val ( ) ) ;
if ( waterfall _min _level >= waterfall _max _level ) {
if ( ! which ) {
waterfall _min _level = waterfall _max _level - 1 ;
} else {
waterfall _max _level = waterfall _min _level + 1 ;
}
2019-10-16 11:17:47 +00:00
}
2020-09-13 11:57:12 +00:00
updateWaterfallSliders ( ) ;
}
function updateWaterfallSliders ( ) {
$ ( '#openwebrx-waterfall-color-max' )
. val ( waterfall _max _level )
. attr ( 'title' , 'Waterfall maximum level (' + Math . round ( waterfall _max _level ) + ' dB)' ) ;
$ ( '#openwebrx-waterfall-color-min' )
. val ( waterfall _min _level )
. attr ( 'title' , 'Waterfall minimum level (' + Math . round ( waterfall _min _level ) + ' dB)' ) ;
2019-10-16 11:17:47 +00:00
}
function waterfallColorsDefault ( ) {
waterfall _min _level = waterfall _min _level _default ;
waterfall _max _level = waterfall _max _level _default ;
2020-09-13 11:57:12 +00:00
updateWaterfallSliders ( ) ;
2020-09-12 20:06:12 +00:00
waterfallColorsContinuousReset ( ) ;
2019-10-16 11:17:47 +00:00
}
2020-08-31 19:48:02 +00:00
function waterfallColorsAuto ( levels ) {
2021-03-30 22:00:38 +00:00
var min _level = levels . min - waterfall _auto _levels . min ;
var max _level = levels . max + waterfall _auto _levels . max ;
max _level = Math . max ( min _level + ( waterfall _auto _min _range || 0 ) , max _level ) ;
2020-09-12 20:06:12 +00:00
waterfall _min _level = min _level ;
waterfall _max _level = max _level ;
2020-09-13 11:57:12 +00:00
updateWaterfallSliders ( ) ;
2020-09-12 20:06:12 +00:00
}
var waterfall _continuous = {
min : - 150 ,
max : 0
} ;
function waterfallColorsContinuousReset ( ) {
waterfall _continuous . min = waterfall _min _level ;
waterfall _continuous . max = waterfall _max _level ;
}
function waterfallColorsContinuous ( levels ) {
if ( levels . max > waterfall _continuous . max + 1 ) {
waterfall _continuous . max += 1 ;
} else if ( levels . max < waterfall _continuous . max - 1 ) {
waterfall _continuous . max -= . 1 ;
}
if ( levels . min < waterfall _continuous . min - 1 ) {
waterfall _continuous . min -= 1 ;
} else if ( levels . min > waterfall _continuous . min + 1 ) {
waterfall _continuous . min += . 1 ;
}
waterfallColorsAuto ( waterfall _continuous ) ;
2019-10-16 11:17:47 +00:00
}
function setSmeterRelativeValue ( value ) {
if ( value < 0 ) value = 0 ;
if ( value > 1.0 ) value = 1.0 ;
2021-01-02 17:16:25 +00:00
var $meter = $ ( "#openwebrx-smeter" ) ;
var $bar = $meter . find ( ".openwebrx-smeter-bar" ) ;
$bar . css ( { transform : 'translate(' + ( ( value - 1 ) * 100 ) + '%) translateZ(0)' } ) ;
if ( value > 0.9 ) {
// red
$bar . css ( { background : 'linear-gradient(to top, #ff5939 , #961700)' } ) ;
} else if ( value > 0.7 ) {
// yellow
$bar . css ( { background : 'linear-gradient(to top, #fff720 , #a49f00)' } ) ;
} else {
// red
$bar . css ( { background : 'linear-gradient(to top, #22ff2f , #008908)' } ) ;
}
2019-10-16 11:17:47 +00:00
}
2019-10-27 15:04:00 +00:00
function setSquelchSliderBackground ( val ) {
2020-05-03 17:55:48 +00:00
var $slider = $ ( '#openwebrx-panel-receiver .openwebrx-squelch-slider' ) ;
2019-10-27 15:04:00 +00:00
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 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 ) ;
2021-01-15 17:09:18 +00:00
$ ( "#openwebrx-smeter-db" ) . html ( 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 ================
// ========================================================
2020-05-01 22:05:20 +00:00
function getDemodulators ( ) {
return [
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . getDemodulator ( )
] . filter ( function ( d ) {
return ! ! d ;
} ) ;
2020-09-20 17:53:13 +00:00
}
2014-11-29 00:07:10 +00:00
function mkenvelopes ( visible _range ) //called from mkscale
{
2020-05-01 22:05:20 +00:00
var demodulators = getDemodulators ( ) ;
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 ) ;
}
2020-04-30 20:07:19 +00:00
if ( demodulators . length ) {
2020-09-20 17:53:13 +00:00
var bandpass = demodulators [ 0 ] . getBandpass ( ) ;
2020-04-30 20:07:19 +00:00
secondary _demod _waterfall _set _zoom ( bandpass . low _cut , bandpass . high _cut ) ;
}
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 ( ) {
2021-01-15 17:09:18 +00:00
scale _canvas = $ ( "#openwebrx-scale-canvas" ) [ 0 ] ;
2019-10-16 11:17:47 +00:00
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 ( ) ;
2021-01-15 17:09:18 +00:00
var frequency _container = $ ( "#openwebrx-frequency-container" ) ;
frequency _container . on ( "mousemove" , frequency _container _mousemove , false ) ;
2019-10-16 11:17:47 +00:00
}
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 ;
2020-05-01 22:05:20 +00:00
var demodulators = getDemodulators ( ) ;
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 ) ;
2020-01-19 09:50:40 +00:00
if ( ! event _handled ) demodulators [ 0 ] . set _offset _frequency ( scale _offset _freq _from _px ( evt . pageX ) ) ;
2019-10-16 11:17:47 +00:00
}
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 ) ;
2020-12-10 19:58:07 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . setMouseFrequency ( frequency ) ;
2019-10-03 15:24:28 +00:00
}
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 ;
2020-05-01 22:05:20 +00:00
var demodulators = getDemodulators ( ) ;
2019-10-16 15:11:09 +00:00
for ( var i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _end ( ) ;
2020-01-19 09:50:40 +00:00
if ( ! event _handled ) demodulators [ 0 ] . set _offset _frequency ( 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 ( ) {
2021-01-01 22:37:10 +00:00
if ( ! bandwidth ) return false ;
2019-10-16 15:11:09 +00:00
var fcalc = function ( x ) {
2022-06-18 13:57:06 +00:00
var canvasWidth = waterfallWidth ( ) * get _zoom ( 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
} ;
2021-01-01 22:37:10 +00:00
var out = {
start : fcalc ( 0 ) ,
center : fcalc ( waterfallWidth ( ) / 2 ) ,
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 ( ) ;
2021-01-01 22:37:10 +00:00
if ( ! range ) return ;
2019-10-16 11:17:47 +00:00
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 ;
2021-01-01 22:37:10 +00:00
while ( ( x = scale _px _from _freq ( marker _hz , range ) ) <= window . innerWidth ) {
2019-10-16 11:17:47 +00:00
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 ) {
2021-02-23 23:59:31 +00:00
var rel = ( relativeX / canvas _container . 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 ;
}
2022-06-18 13:57:06 +00:00
resize _canvases ( ) ;
2019-10-16 11:17:47 +00:00
canvas _drag _last _x = evt . pageX ;
canvas _drag _last _y = evt . pageY ;
mkscale ( ) ;
bookmarks . position ( ) ;
}
2020-01-18 20:33:10 +00:00
} else {
2020-12-10 19:58:07 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . setMouseFrequency ( canvas _get _frequency ( relativeX ) ) ;
2019-10-16 11:17:47 +00:00
}
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 ) {
2020-05-01 22:05:20 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . getDemodulator ( ) . set _offset _frequency ( canvas _get _freq _offset ( relativeX ) ) ;
2019-10-16 11:17:47 +00:00
}
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 ) {
2022-06-18 13:57:06 +00:00
if ( ! waterfall _setup _done ) return ;
var delta = - evt . deltaY ;
2022-06-17 17:42:40 +00:00
// deltaMode 0 means pixels instead of lines
if ( 'deltaMode' in evt && evt . deltaMode === 0 ) {
2022-06-18 13:57:06 +00:00
delta /= 50 ;
2022-06-17 17:42:40 +00:00
}
2019-10-16 11:17:47 +00:00
var relativeX = get _relative _x ( evt ) ;
2022-06-18 13:57:06 +00:00
zoom _step ( delta , relativeX , zoom _center _where _calc ( evt . pageX ) ) ;
2019-10-16 11:17:47 +00:00
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 _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
2022-06-18 13:57:06 +00:00
function get _zoom ( level ) {
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 ) ;
2022-06-18 13:57:06 +00:00
return Math . pow ( zoom _ratio , level ) ;
2019-10-16 11:17:47 +00:00
}
2022-06-18 13:57:06 +00:00
function zoom _step ( delta , where , onscreen ) {
zoom _level += delta ;
if ( zoom _level < 0 ) {
zoom _level = 0 ;
} else if ( zoom _level > zoom _levels _count ) {
zoom _level = zoom _levels _count ;
}
2019-10-16 11:17:47 +00:00
zoom _center _rel = canvas _get _freq _offset ( where ) ;
zoom _center _where = onscreen ;
2022-06-18 13:57:06 +00:00
resize _canvases ( ) ;
2019-10-16 11:17:47 +00:00
mkscale ( ) ;
bookmarks . position ( ) ;
}
function zoom _set ( level ) {
2022-06-18 13:57:06 +00:00
if ( level < 0 ) {
zoom _level = 0 ;
} else if ( level > zoom _levels _count ) {
zoom _level = zoom _levels _count ;
} else {
zoom _level = parseFloat ( 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
2020-05-01 22:05:20 +00:00
zoom _center _rel = $ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . getDemodulator ( ) . get _offset _frequency ( ) ;
2019-10-16 11:17:47 +00:00
zoom _center _where = 0.5 + ( zoom _center _rel / bandwidth ) ; //this is a kind of hack
2022-06-18 13:57:06 +00:00
resize _canvases ( ) ;
2019-10-16 11:17:47 +00:00
mkscale ( ) ;
bookmarks . position ( ) ;
}
function zoom _calc ( ) {
2019-11-01 18:48:08 +00:00
var winsize = waterfallWidth ( ) ;
2022-06-18 13:57:06 +00:00
var canvases _new _width = winsize * get _zoom ( zoom _level ) ;
2019-10-16 11:17:47 +00:00
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 ;
2021-01-30 15:17:05 +00:00
var currentprofile = {
toString : function ( ) {
return this [ 'sdr_id' ] + '|' + this [ 'profile_id' ] ;
}
} ;
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" ) {
2021-01-23 15:43:51 +00:00
params = Object . fromEntries (
evt . data . slice ( 17 ) . split ( ' ' ) . map ( function ( param ) {
var args = param . split ( '=' ) ;
return [ args [ 0 ] , args . slice ( 1 ) . join ( '=' ) ]
} )
) ;
var versionInfo = 'Unknown server' ;
if ( params . server && params . server === 'openwebrx' && params . version ) {
versionInfo = 'OpenWebRX version: ' + params . version ;
}
divlog ( 'Server acknowledged WebSocket connection, ' + versionInfo ) ;
2019-10-16 11:17:47 +00:00
} 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' ] ;
2020-12-30 16:15:48 +00:00
if ( 'waterfall_colors' in config )
waterfall _colors = buildWaterfallColors ( config [ 'waterfall_colors' ] ) ;
2021-02-25 14:13:39 +00:00
if ( 'waterfall_levels' in config ) {
waterfall _min _level _default = config [ 'waterfall_levels' ] [ 'min' ] ;
waterfall _max _level _default = config [ 'waterfall_levels' ] [ 'max' ] ;
}
2021-03-30 22:00:38 +00:00
if ( 'waterfall_auto_levels' in config )
waterfall _auto _levels = config [ 'waterfall_auto_levels' ] ;
if ( 'waterfall_auto_min_range' in config )
waterfall _auto _min _range = config [ 'waterfall_auto_min_range' ] ;
2019-05-04 14:56:23 +00:00
waterfallColorsDefault ( ) ;
2020-12-30 16:15:48 +00:00
var initial _demodulator _params = { } ;
if ( 'start_mod' in config )
initial _demodulator _params [ 'mod' ] = config [ 'start_mod' ] ;
if ( 'start_offset_freq' in config )
initial _demodulator _params [ 'offset_frequency' ] = config [ 'start_offset_freq' ] ;
if ( 'initial_squelch_level' in config )
initial _demodulator _params [ 'squelch_level' ] = Number . isInteger ( config [ 'initial_squelch_level' ] ) ? config [ 'initial_squelch_level' ] : - 150 ;
if ( 'samp_rate' in config )
bandwidth = config [ 'samp_rate' ] ;
if ( 'center_freq' in config )
center _freq = config [ 'center_freq' ] ;
2021-02-27 00:09:51 +00:00
if ( 'fft_size' in config ) {
2020-12-30 16:15:48 +00:00
fft _size = config [ 'fft_size' ] ;
2021-02-27 00:09:51 +00:00
waterfall _clear ( ) ;
}
2020-12-30 17:05:10 +00:00
if ( 'audio_compression' in config ) {
2020-12-30 16:15:48 +00:00
var audio _compression = config [ 'audio_compression' ] ;
audioEngine . setCompression ( audio _compression ) ;
divlog ( "Audio stream is " + ( ( audio _compression === "adpcm" ) ? "compressed" : "uncompressed" ) + "." ) ;
}
if ( 'fft_compression' in config ) {
fft _compression = config [ 'fft_compression' ] ;
divlog ( "FFT stream is " + ( ( fft _compression === "adpcm" ) ? "compressed" : "uncompressed" ) + "." ) ;
}
if ( 'max_clients' in config )
$ ( '#openwebrx-bar-clients' ) . progressbar ( ) . setMaxClients ( config [ 'max_clients' ] ) ;
2019-10-16 11:17:47 +00:00
2021-01-01 22:37:10 +00:00
waterfall _init ( ) ;
2020-12-31 22:03:36 +00:00
2020-05-02 13:07:47 +00:00
var demodulatorPanel = $ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) ;
demodulatorPanel . setCenterFrequency ( center _freq ) ;
2020-10-10 22:25:13 +00:00
demodulatorPanel . setInitialParams ( initial _demodulator _params ) ;
2020-12-30 16:15:48 +00:00
if ( 'squelch_auto_margin' in config )
demodulatorPanel . setSquelchMargin ( config [ 'squelch_auto_margin' ] ) ;
2019-10-16 11:17:47 +00:00
bookmarks . loadLocalBookmarks ( ) ;
2021-01-30 15:17:05 +00:00
if ( 'sdr_id' in config || 'profile_id' in config ) {
2021-01-30 15:18:30 +00:00
currentprofile [ 'sdr_id' ] = config [ 'sdr_id' ] || currentprofile [ 'sdr_id' ] ;
currentprofile [ 'profile_id' ] = config [ 'profile_id' ] || currentprofile [ 'profile_id' ] ;
2021-01-30 15:17:05 +00:00
$ ( '#openwebrx-sdr-profiles-listbox' ) . val ( currentprofile . toString ( ) ) ;
2019-10-20 16:53:23 +00:00
2021-01-30 15:04:29 +00:00
waterfall _clear ( ) ;
2022-07-31 19:01:15 +00:00
zoom _set ( 0 ) ;
2021-01-30 15:04:29 +00:00
}
2020-12-31 22:03:36 +00:00
2021-03-01 00:19:06 +00:00
if ( 'tuning_precision' in config )
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . setTuningPrecision ( config [ 'tuning_precision' ] ) ;
2020-12-10 19:58:07 +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' ] ;
2021-09-27 16:18:31 +00:00
secondary _fft _size = s [ 'secondary_fft_size' ] || secondary _fft _size ;
secondary _bw = s [ 'secondary_bw' ] || secondary _bw ;
if _samp _rate = s [ 'if_samp_rate' ] || if _samp _rate ;
if ( if _samp _rate ) secondary _demod _init _canvases ( ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-05 15:52:26 +00:00
case "receiver_details" :
2021-02-05 16:56:02 +00:00
$ ( '.webrx-top-container' ) . header ( ) . setDetails ( json [ 'value' ] ) ;
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" :
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-server-cpu' ) . progressbar ( ) . setUsage ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-10 20:47:40 +00:00
case "clients" :
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-clients' ) . progressbar ( ) . setClients ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
2019-05-10 14:14:16 +00:00
case "profiles" :
2021-01-15 17:09:18 +00:00
var listbox = $ ( "#openwebrx-sdr-profiles-listbox" ) ;
listbox . html ( json [ 'value' ] . map ( function ( profile ) {
2019-10-16 15:11:09 +00:00
return '<option value="' + profile [ 'id' ] + '">' + profile [ 'name' ] + "</option>" ;
2021-01-15 17:09:18 +00:00
} ) . join ( "" ) ) ;
2021-01-30 15:17:05 +00:00
$ ( '#openwebrx-sdr-profiles-listbox' ) . val ( currentprofile . toString ( ) ) ;
2021-03-20 23:14:18 +00:00
// this is a bit hacky since it only makes sense if the error is actually "no sdr devices"
// the only other error condition for which the overlay is used right now is "too many users"
// so there shouldn't be a problem here
2021-03-20 23:18:35 +00:00
if ( Object . keys ( json [ 'value' ] ) . length ) {
2021-03-20 23:14:18 +00:00
$ ( '#openwebrx-error-overlay' ) . hide ( ) ;
}
2019-10-16 11:17:47 +00:00
break ;
case "features" :
2020-04-26 15:19:05 +00:00
Modes . setFeatures ( json [ 'value' ] ) ;
2019-10-16 11:17:47 +00:00
break ;
case "metadata" :
2021-01-16 18:40:22 +00:00
$ ( '.openwebrx-meta-panel' ) . metaPanel ( ) . each ( function ( ) {
this . update ( 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 ( ) ,
2020-05-01 23:10:41 +00:00
modulation : d [ 'mode' ] ,
2019-10-16 15:11:09 +00:00
frequency : d [ 'frequency' ]
2019-10-16 11:17:47 +00:00
} ;
} ) ;
bookmarks . replace _bookmarks ( as _bookmarks , 'dial_frequencies' ) ;
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-12-23 20:21:45 +00:00
divlog ( json [ 'value' ] , true ) ;
2019-12-21 22:46:05 +00:00
var $overlay = $ ( '#openwebrx-error-overlay' ) ;
$overlay . find ( '.errormessage' ) . text ( json [ 'value' ] ) ;
$overlay . show ( ) ;
2021-03-20 23:14:18 +00:00
$ ( "#openwebrx-panel-receiver" ) . demodulatorPanel ( ) . stopDemodulator ( ) ;
2019-10-16 11:17:47 +00:00
break ;
2021-12-13 12:26:47 +00:00
case "demodulator_error" :
divlog ( json [ 'value' ] , true ) ;
break ;
2019-10-25 19:08:56 +00:00
case 'secondary_demod' :
2021-09-06 13:05:33 +00:00
var value = json [ 'value' ] ;
var panels = [
$ ( "#openwebrx-panel-wsjt-message" ) . wsjtMessagePanel ( ) ,
2021-09-06 18:00:14 +00:00
$ ( '#openwebrx-panel-packet-message' ) . packetMessagePanel ( ) ,
2021-09-06 20:50:57 +00:00
$ ( '#openwebrx-panel-pocsag-message' ) . pocsagMessagePanel ( ) ,
$ ( "#openwebrx-panel-js8-message" ) . js8 ( )
2021-09-06 13:05:33 +00:00
] ;
if ( ! panels . some ( function ( panel ) {
if ( ! panel . supportsMessage ( value ) ) return false ;
panel . pushMessage ( value ) ;
return true ;
} ) ) {
secondary _demod _push _data ( value ) ;
}
2019-10-25 19:08:56 +00:00
break ;
2019-12-23 20:18:06 +00:00
case 'log_message' :
divlog ( json [ 'value' ] , true ) ;
break ;
2020-01-10 20:38:46 +00:00
case 'backoff' :
divlog ( "Server is currently busy: " + json [ 'reason' ] , true ) ;
2020-01-10 20:43:21 +00:00
var $overlay = $ ( '#openwebrx-error-overlay' ) ;
$overlay . find ( '.errormessage' ) . text ( json [ 'reason' ] ) ;
$overlay . show ( ) ;
2020-01-10 20:38:46 +00:00
// set a higher reconnection timeout right away to avoid additional load
reconnect _timeout = 16000 ;
break ;
2020-04-26 13:17:03 +00:00
case 'modes' :
Modes . setModes ( 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 ;
2020-08-08 19:29:25 +00:00
case 4 :
// hd audio data
audioEngine . pushHdAudio ( data ) ;
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-10-16 15:11:09 +00:00
var waterfall _measure _minmax _now = false ;
2020-09-12 20:06:12 +00:00
var waterfall _measure _minmax _continuous = false ;
2015-08-17 18:32:58 +00:00
2019-10-16 11:17:47 +00:00
function waterfall _measure _minmax _do ( what ) {
2022-07-31 18:40:20 +00:00
// Get visible range
var range = get _visible _freq _range ( ) ;
var start = center _freq - bandwidth / 2 ;
2020-03-25 20:50:22 +00:00
// this is based on an oversampling factor of about 1,25
2022-07-31 18:40:20 +00:00
range . start = Math . max ( 0.1 , ( range . start - start ) / bandwidth ) ;
range . end = Math . min ( 0.9 , ( range . end - start ) / bandwidth ) ;
var data = what . slice ( range . start * what . length , range . end * what . length ) ;
2020-08-31 19:48:02 +00:00
return {
min : Math . min . apply ( Math , data ) ,
max : Math . max . apply ( Math , data )
} ;
2015-08-17 18:32:58 +00:00
}
2019-10-16 11:17:47 +00:00
function on _ws _opened ( ) {
2019-12-21 22:46:05 +00:00
$ ( '#openwebrx-error-overlay' ) . hide ( ) ;
2019-10-16 11:17:47 +00:00
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 ) {
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-network-speed' ) . progressbar ( ) . setSpeed ( rate ) ;
2019-10-26 20:44:54 +00:00
} ) ;
} 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 ( {
2019-11-26 19:10:26 +00:00
"type" : "connectionproperties" ,
2020-08-08 18:45:03 +00:00
"params" : {
"output_rate" : audioEngine . getOutputRate ( ) ,
"hd_output_rate" : audioEngine . getHdOutputRate ( )
}
2019-10-20 16:53:23 +00:00
} ) ) ;
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
}
2021-01-15 17:09:18 +00:00
$ ( '#openwebrx-debugdiv' ) [ 0 ] . 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
2020-08-23 15:56:13 +00:00
function onAudioStart ( apiType ) {
2019-10-20 16:53:23 +00:00
divlog ( 'Web Audio API succesfully initialized, using ' + apiType + ' API, sample rate: ' + audioEngine . getSampleRate ( ) + " Hz" ) ;
2019-10-16 11:17:47 +00:00
2020-08-23 15:56:13 +00:00
hideOverlay ( ) ;
2019-10-20 16:53:23 +00:00
// canvas_container is set after waterfall_init() has been called. we cannot initialize before.
2020-05-01 22:05:20 +00:00
//if (canvas_container) synchronize_demodulator_init();
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-07-13 19:40:48 +00:00
var reconnect _timeout = false ;
2019-10-16 11:17:47 +00:00
function on _ws _closed ( ) {
2021-04-27 21:13:44 +00:00
var demodulatorPanel = $ ( "#openwebrx-panel-receiver" ) . demodulatorPanel ( ) ;
demodulatorPanel . stopDemodulator ( ) ;
demodulatorPanel . resetInitialParams ( ) ;
2019-10-16 11:17:47 +00:00
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 15:11:09 +00:00
var ws ;
2019-10-16 11:17:47 +00:00
function open _websocket ( ) {
2019-12-03 17:57:32 +00:00
var protocol = window . location . protocol . match ( /https/ ) ? 'wss' : 'ws' ;
2019-12-03 17:53:57 +00:00
var href = window . location . href ;
var index = href . lastIndexOf ( '/' ) ;
if ( index > 0 ) {
href = href . substr ( 0 , index + 1 ) ;
}
href = href . split ( "://" ) [ 1 ] ;
href = protocol + "://" + href ;
if ( ! href . endsWith ( '/' ) ) {
href += '/' ;
2019-10-16 11:17:47 +00:00
}
2019-12-03 17:53:57 +00:00
var ws _url = href + "ws/" ;
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 ) {
2020-09-17 18:10:01 +00:00
waterfall _colors _arg = waterfall _colors _arg || waterfall _colors ;
var value _percent = ( db _value - waterfall _min _level ) / ( waterfall _max _level - waterfall _min _level ) ;
2020-09-19 13:51:54 +00:00
value _percent = Math . max ( 0 , Math . min ( 1 , value _percent ) ) ;
2020-09-19 19:53:29 +00:00
2020-09-20 17:53:13 +00:00
var scaled = value _percent * ( waterfall _colors _arg . length - 1 ) ;
2020-09-19 19:53:29 +00:00
var index = Math . floor ( scaled ) ;
var remain = scaled - index ;
2020-09-20 17:53:13 +00:00
if ( remain === 0 ) return waterfall _colors _arg [ index ] ;
2020-09-19 19:53:29 +00:00
return color _between ( waterfall _colors _arg [ index ] , waterfall _colors _arg [ index + 1 ] , remain ) ; }
2019-10-16 11:17:47 +00:00
function color _between ( first , second , percent ) {
2020-09-19 19:53:29 +00:00
return [
first [ 0 ] + percent * ( second [ 0 ] - first [ 0 ] ) ,
first [ 1 ] + percent * ( second [ 1 ] - first [ 1 ] ) ,
first [ 2 ] + percent * ( second [ 2 ] - first [ 2 ] )
] ;
2014-11-29 00:07:10 +00:00
}
var canvas _context ;
var canvases = [ ] ;
var canvas _default _height = 200 ;
var canvas _container ;
2021-02-23 23:58:50 +00:00
var canvas _actual _line = - 1 ;
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 ;
2021-02-23 23:58:50 +00:00
canvas _actual _line = canvas _default _height ;
new _canvas . openwebrx _top = - canvas _default _height ;
2021-02-27 16:06:53 +00:00
new _canvas . style . transform = 'translate(0, ' + new _canvas . openwebrx _top . toString ( ) + 'px)' ;
2019-10-16 11:17:47 +00:00
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 ( ) {
2021-01-15 17:09:18 +00:00
canvas _container = $ ( "#webrx-canvas-container" ) [ 0 ] ;
2019-10-16 11:17:47 +00:00
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 ) ;
2022-06-19 20:40:47 +00:00
$ ( "#openwebrx-frequency-container" ) . each ( function ( ) {
this . addEventListener ( "wheel" , canvas _mousewheel , false ) ;
} ) ;
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 ) {
2021-02-27 16:06:53 +00:00
p . style . transform = 'translate(0, ' + ( p . openwebrx _top ++ ) . toString ( ) + 'px)' ;
2019-10-16 11:17:47 +00:00
} ) ;
canvas _maxshift ++ ;
}
2022-06-18 13:57:06 +00:00
function resize _canvases ( ) {
2019-10-16 11:17:47 +00:00
zoom _calc ( ) ;
2019-11-01 18:48:08 +00:00
$ ( '#webrx-canvas-container' ) . css ( {
2022-06-18 13:57:06 +00:00
width : waterfallWidth ( ) * get _zoom ( zoom _level ) + 'px' ,
2019-11-01 18:48:08 +00:00
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 ( ) ;
waterfall _setup _done = 1 ;
2014-11-29 00:07:10 +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 _now ) {
2020-08-31 19:48:02 +00:00
var levels = waterfall _measure _minmax _do ( data ) ;
2019-10-16 11:17:47 +00:00
waterfall _measure _minmax _now = false ;
2020-08-31 19:48:02 +00:00
waterfallColorsAuto ( levels ) ;
2020-09-12 20:06:12 +00:00
waterfallColorsContinuousReset ( ) ;
}
if ( waterfall _measure _minmax _continuous ) {
var level = waterfall _measure _minmax _do ( data ) ;
waterfallColorsContinuous ( level ) ;
2019-10-16 11:17:47 +00:00
}
2016-03-20 10:32:37 +00:00
2021-02-23 23:58:50 +00:00
// create new canvas if the current one is full (or there isn't one)
if ( canvas _actual _line <= 0 ) add _canvas ( ) ;
2020-01-09 20:52:47 +00:00
//Add line to waterfall image
var oneline _image = canvas _context . createImageData ( w , 1 ) ;
for ( var x = 0 ; x < w ; x ++ ) {
2020-09-19 13:51:54 +00:00
var color = waterfall _mkcolor ( data [ x ] ) ;
2020-09-17 18:10:01 +00:00
for ( i = 0 ; i < 3 ; i ++ ) oneline _image . data [ x * 4 + i ] = color [ i ] ;
oneline _image . data [ x * 4 + 3 ] = 255 ;
2019-10-16 11:17:47 +00:00
}
2018-09-25 12:56:47 +00:00
2020-01-09 20:52:47 +00:00
//Draw image
2021-02-23 23:58:50 +00:00
canvas _context . putImageData ( oneline _image , 0 , -- canvas _actual _line ) ;
2020-01-09 20:52:47 +00:00
shift _canvases ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function waterfall _clear ( ) {
2021-02-23 23:58:50 +00:00
//delete all canvases
while ( canvases . length ) {
2019-10-16 11:17:47 +00:00
var x = canvases . shift ( ) ;
x . parentNode . removeChild ( x ) ;
}
2021-02-23 23:58:50 +00:00
canvas _actual _line = - 1 ;
2018-09-25 12:56:47 +00:00
}
2019-10-16 11:17:47 +00:00
function openwebrx _resize ( ) {
resize _canvases ( ) ;
resize _scale ( ) ;
2014-11-29 00:07:10 +00:00
}
2019-10-22 20:35:54 +00:00
function initProgressBars ( ) {
2020-05-08 19:35:45 +00:00
$ ( ".openwebrx-progressbar" ) . each ( function ( ) {
var bar = $ ( this ) . progressbar ( ) ;
if ( 'setSampleRate' in bar ) {
bar . setSampleRate ( audioEngine . getSampleRate ( ) ) ;
}
} )
2019-10-23 09:27:05 +00:00
}
2019-10-20 16:53:23 +00:00
function audioReporter ( stats ) {
if ( typeof ( stats . buffersize ) !== 'undefined' ) {
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-audio-buffer' ) . progressbar ( ) . setBuffersize ( stats . buffersize ) ;
2019-10-20 16:53:23 +00:00
}
if ( typeof ( stats . audioByteRate ) !== 'undefined' ) {
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-audio-speed' ) . progressbar ( ) . setSpeed ( stats . audioByteRate * 8 ) ;
2019-10-20 16:53:23 +00:00
}
if ( typeof ( stats . audioRate ) !== 'undefined' ) {
2020-05-08 19:18:03 +00:00
$ ( '#openwebrx-bar-audio-output' ) . progressbar ( ) . 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 ) ;
2021-05-16 15:47:02 +00:00
var $overlay = $ ( '#openwebrx-autoplay-overlay' ) ;
$overlay . on ( 'click' , function ( ) {
2020-08-23 15:56:13 +00:00
audioEngine . resume ( ) ;
} ) ;
audioEngine . onStart ( onAudioStart ) ;
2019-10-20 16:53:23 +00:00
if ( ! audioEngine . isAllowed ( ) ) {
2021-05-05 17:56:14 +00:00
$ ( 'body' ) . append ( $overlay ) ;
2019-11-01 15:58:36 +00:00
$overlay . show ( ) ;
2019-10-20 16:53:23 +00:00
}
2020-01-05 22:33:07 +00:00
fft _codec = new ImaAdpcmCodec ( ) ;
2019-10-22 20:35:54 +00:00
initProgressBars ( ) ;
2019-10-16 11:17:47 +00:00
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 ( ) ;
2020-05-01 22:05:20 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) ;
2019-10-16 11:17:47 +00:00
window . addEventListener ( "resize" , openwebrx _resize ) ;
bookmarks = new BookmarkBar ( ) ;
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' ) ;
} ) ;
2020-09-12 20:06:12 +00:00
var waterfallAutoButton = $ ( '#openwebrx-waterfall-colors-auto' ) ;
2020-09-20 17:53:13 +00:00
waterfallAutoButton . on ( 'click' , function ( ) {
2020-09-12 20:06:12 +00:00
waterfall _measure _minmax _now = true ;
} ) . on ( 'contextmenu' , function ( ) {
waterfall _measure _minmax _continuous = ! waterfall _measure _minmax _continuous ;
waterfallColorsContinuousReset ( ) ;
2020-09-13 11:38:44 +00:00
waterfallAutoButton [ waterfall _measure _minmax _continuous ? 'addClass' : 'removeClass' ] ( 'highlighted' ) ;
$ ( '#openwebrx-waterfall-color-min, #openwebrx-waterfall-color-max' ) . prop ( 'disabled' , waterfall _measure _minmax _continuous ) ;
2020-09-12 20:06:12 +00:00
return false ;
} ) ;
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 ( ) ;
2021-09-17 16:24:33 +00:00
// don't mute when the location icon is clicked
} ) . find ( '.location' ) . click ( function ( e ) {
e . stopPropagation ( ) ;
2019-06-15 17:10:33 +00:00
} ) ;
2021-01-16 18:40:22 +00:00
$ ( '.openwebrx-meta-panel' ) . metaPanel ( ) ;
2019-06-15 17:10:33 +00:00
}
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 ) ;
2020-05-01 22:05:20 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . getDemodulator ( ) . setDmrFilter ( filter ) ;
2019-06-15 17:10:33 +00:00
}
2020-08-23 15:56:13 +00:00
function hideOverlay ( ) {
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 ) {
2021-02-28 17:23:35 +00:00
return ! ( el . style && el . style . display && el . style . display === 'none' ) && ! ( el . movement && el . movement === 'collapse' ) ;
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
}
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' ;
2021-01-16 18:40:22 +00:00
item . style . display = null ;
2019-10-26 19:32:00 +00:00
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
}
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 . 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
}
2021-02-28 17:23:35 +00:00
delete el . movement ;
2019-10-26 19:32:00 +00:00
} ) ;
if ( panel _displayed ( el ) ) first _show _panel ( el ) ;
} ) ;
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 11:17:47 +00:00
_ _ / |
| _ _ _ /
2018-09-25 12:56:47 +00:00
* /
2020-09-17 20:43:39 +00:00
var secondary _demod _fft _offset _db = 18 ; //need to calculate that later
2019-10-16 15:11:09 +00:00
var secondary _demod _canvases _initialized = 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 ;
2021-09-27 16:18:31 +00:00
var secondary _bw = 31.25 ;
var if _samp _rate ;
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 ( ) {
2021-02-27 16:06:53 +00:00
for ( var i = 0 ; i < 2 ; i ++ ) {
secondary _demod _canvases [ i ] . style . transform = 'translate(0, ' + 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 ) ;
2020-12-09 18:26:34 +00:00
$ ( '#openwebrx-panel-wsjt-message' ) . wsjtMessagePanel ( ) ;
2020-12-09 18:42:46 +00:00
$ ( '#openwebrx-panel-packet-message' ) . packetMessagePanel ( ) ;
2020-12-09 18:53:37 +00:00
$ ( '#openwebrx-panel-pocsag-message' ) . pocsagMessagePanel ( ) ;
2020-12-09 20:19:22 +00:00
$ ( '#openwebrx-panel-js8-message' ) . js8 ( ) ;
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 " " ;
2021-10-13 11:59:55 +00:00
if ( y === "\n" ) return "<br />" ;
2018-09-25 12:56:47 +00:00
return y ;
} ) . 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 _waterfall _add ( data ) {
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 ++ ) {
2020-09-19 13:51:54 +00:00
var color = waterfall _mkcolor ( data [ x ] + secondary _demod _fft _offset _db ) ;
2020-09-17 18:10:01 +00:00
for ( var i = 0 ; i < 3 ; i ++ ) oneline _image . data [ x * 4 + i ] = color [ i ] ;
oneline _image . data [ x * 4 + 3 ] = 255 ;
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 _update _marker ( ) {
2020-09-17 20:21:49 +00:00
var width = Math . max ( ( secondary _bw / if _samp _rate ) * secondary _demod _canvas _width , 5 ) ;
var center _at = ( ( secondary _demod _channel _freq - secondary _demod _low _cut ) / if _samp _rate ) * secondary _demod _canvas _width ;
2019-10-16 11:17:47 +00:00
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 ( ) {
2020-05-01 22:05:20 +00:00
$ ( '#openwebrx-panel-receiver' ) . demodulatorPanel ( ) . getDemodulator ( ) . set _secondary _offset _freq ( Math . floor ( secondary _demod _channel _freq ) ) ;
2019-10-16 11:17:47 +00:00
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 ) {
2020-05-01 22:05:20 +00:00
if ( ! secondary _demod _canvases _initialized ) return ;
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 ;
2020-09-17 20:21:49 +00:00
secondary _demod _canvas _width = $ ( secondary _demod _canvas _container ) . width ( ) * ( if _samp _rate ) / shown _bw ;
secondary _demod _canvas _left = ( - secondary _demod _canvas _width / 2 ) - ( low _cut / if _samp _rate ) * secondary _demod _canvas _width ;
2019-10-16 11:17:47 +00:00
secondary _demod _canvases . map ( function ( x ) {
2020-09-17 20:21:49 +00:00
$ ( x ) . css ( {
left : secondary _demod _canvas _left + "px" ,
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
}