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 >
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
* /
is _firefox = navigator . userAgent . indexOf ( "Firefox" ) != - 1 ;
function arrayBufferToString ( buf ) {
//http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
return String . fromCharCode . apply ( null , new Uint8Array ( buf ) ) ;
}
function getFirstChars ( buf , num )
{
var u8buf = new Uint8Array ( buf ) ;
var output = String ( ) ;
num = Math . min ( num , u8buf . length ) ;
for ( i = 0 ; i < num ; i ++ ) output += String . fromCharCode ( u8buf [ i ] ) ;
return output ;
}
var bandwidth ;
var center _freq ;
var audio _buffer _current _size _debug = 0 ;
var audio _buffer _all _size _debug = 0 ;
var audio _buffer _current _count _debug = 0 ;
var audio _buffer _current _size = 0 ;
var fft _size ;
var fft _fps ;
2015-08-17 18:32:58 +00:00
var fft _compression = "none" ;
var fft _codec = new sdrjs . ImaAdpcm ( ) ;
var audio _compression = "none" ;
2014-11-29 00:07:10 +00:00
var waterfall _setup _done = 0 ;
var waterfall _queue = [ ] ;
var waterfall _timer ;
2017-05-06 19:51:03 +00:00
var secondary _fft _size ;
2019-05-07 13:21:16 +00:00
var audio _allowed ;
2014-11-29 00:07:10 +00:00
/ * f u n c t i o n f a d e ( s o m e t h i n g , f r o m , t o , t i m e _ m s , f p s )
{
something . style . opacity = from ;
something . fade _i = 0 ;
n _of _iters = time _ms / ( 1000 / fps ) ;
change = ( to - from ) / ( n _of _iters - 1 ) ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
something . fade _timer = window . setInterval (
function ( ) {
if ( something . fade _i ++ < n _of _iters )
something . style . opacity = parseFloat ( something . style . opacity ) + change ;
2016-03-20 10:32:37 +00:00
else
2014-11-29 00:07:10 +00:00
{ something . style . opacity = to ; window . clearInterval ( something . fade _timer ) ; }
} , 1000 / fps ) ;
} * /
var rx _photo _state = 1 ;
function e ( what ) { return document . getElementById ( what ) ; }
2018-05-07 20:42:20 +00:00
ios = /iPad|iPod|iPhone|Chrome/ . test ( navigator . userAgent ) ;
is _chrome = /Chrome/ . test ( navigator . userAgent ) ;
2016-03-26 23:47:26 +00:00
//alert("ios="+ios.toString()+" "+navigator.userAgent);
2014-11-29 00:07:10 +00:00
function init _rx _photo ( )
{
2019-05-04 14:56:23 +00:00
var clip = e ( "webrx-top-photo-clip" ) ;
2019-05-05 15:10:49 +00:00
rx _photo _height = clip . clientHeight
clip . style . maxHeight = rx _photo _height + "px" ;
2014-11-29 00:07:10 +00:00
window . setTimeout ( function ( ) { animate ( e ( "webrx-rx-photo-title" ) , "opacity" , "" , 1 , 0 , 1 , 500 , 30 ) ; } , 1000 ) ;
window . setTimeout ( function ( ) { animate ( e ( "webrx-rx-photo-desc" ) , "opacity" , "" , 1 , 0 , 1 , 500 , 30 ) ; } , 1500 ) ;
window . setTimeout ( function ( ) { close _rx _photo ( ) } , 2500 ) ;
}
dont _toggle _rx _photo _flag = 0 ;
function dont _toggle _rx _photo ( )
{
dont _toggle _rx _photo _flag = 1 ;
}
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 ( )
}
function close _rx _photo ( )
{
rx _photo _state = 0 ;
animate _to ( e ( "webrx-top-photo-clip" ) , "maxHeight" , "px" , 67 , 0.93 , 1000 , 60 , function ( ) { resize _waterfall _container ( true ) ; } ) ;
e ( "openwebrx-rx-details-arrow-down" ) . style . display = "block" ;
e ( "openwebrx-rx-details-arrow-up" ) . style . display = "none" ;
}
function open _rx _photo ( )
{
rx _photo _state = 1 ;
e ( "webrx-rx-photo-desc" ) . style . opacity = 1 ;
e ( "webrx-rx-photo-title" ) . style . opacity = 1 ;
animate _to ( e ( "webrx-top-photo-clip" ) , "maxHeight" , "px" , rx _photo _height , 0.93 , 1000 , 60 , function ( ) { resize _waterfall _container ( true ) ; } ) ;
e ( "openwebrx-rx-details-arrow-down" ) . style . display = "none" ;
e ( "openwebrx-rx-details-arrow-up" ) . style . display = "block" ;
}
function style _value ( of _what , which )
{
if ( of _what . currentStyle ) return of _what . currentStyle [ which ] ;
2016-03-20 10:32:37 +00:00
else if ( window . getComputedStyle ) return document . defaultView . getComputedStyle ( of _what , null ) . getPropertyValue ( which ) ;
2014-11-29 00:07:10 +00:00
}
2016-02-06 13:49:10 +00:00
function updateVolume ( )
{
2016-03-20 10:32:37 +00:00
volume = parseFloat ( e ( "openwebrx-panel-volume" ) . value ) / 100 ;
2016-02-06 13:49:10 +00:00
}
function toggleMute ( )
{
if ( mute ) {
mute = false ;
e ( "openwebrx-mute-on" ) . id = "openwebrx-mute-off" ;
2016-02-13 23:31:28 +00:00
e ( "openwebrx-mute-img" ) . src = "gfx/openwebrx-speaker.png" ;
e ( "openwebrx-panel-volume" ) . disabled = false ;
e ( "openwebrx-panel-volume" ) . style . opacity = 1.0 ;
e ( "openwebrx-panel-volume" ) . value = volumeBeforeMute ;
2016-02-06 13:49:10 +00:00
} else {
mute = true ;
2016-02-13 23:31:28 +00:00
e ( "openwebrx-mute-off" ) . id = "openwebrx-mute-on" ;
e ( "openwebrx-mute-img" ) . src = "gfx/openwebrx-speaker-muted.png" ;
e ( "openwebrx-panel-volume" ) . disabled = true ;
2016-03-20 10:32:37 +00:00
e ( "openwebrx-panel-volume" ) . style . opacity = 0.5 ;
volumeBeforeMute = e ( "openwebrx-panel-volume" ) . value ;
2016-02-13 23:31:28 +00:00
e ( "openwebrx-panel-volume" ) . value = 0 ;
2016-02-06 13:49:10 +00:00
}
updateVolume ( ) ;
}
2016-03-20 10:32:37 +00:00
function zoomInOneStep ( ) { zoom _set ( zoom _level + 1 ) ; }
function zoomOutOneStep ( ) { zoom _set ( zoom _level - 1 ) ; }
function zoomInTotal ( ) { zoom _set ( zoom _levels . length - 1 ) ; }
function zoomOutTotal ( ) { zoom _set ( 0 ) ; }
function setSquelchDefault ( ) { e ( "openwebrx-panel-squelch" ) . value = 0 ; }
2016-03-21 08:10:41 +00:00
function setSquelchToAuto ( ) { e ( "openwebrx-panel-squelch" ) . value = ( getLogSmeterValue ( smeter _level ) + 10 ) . toString ( ) ; updateSquelch ( ) ; }
function updateSquelch ( )
{
var sliderValue = parseInt ( e ( "openwebrx-panel-squelch" ) . value ) ;
var outputValue = ( sliderValue == parseInt ( e ( "openwebrx-panel-squelch" ) . min ) ) ? 0 : getLinearSmeterValue ( sliderValue ) ;
2019-05-04 21:11:13 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "squelch_level" : outputValue } } ) ) ;
2016-03-21 08:10:41 +00:00
}
2016-02-06 13:49:10 +00:00
2016-03-20 10:32:37 +00:00
function updateWaterfallColors ( which )
{
wfmax = e ( "openwebrx-waterfall-color-max" ) ;
wfmin = e ( "openwebrx-waterfall-color-min" ) ;
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 ( )
{
2016-10-30 09:14:27 +00:00
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 ( ) ;
2016-03-20 10:32:37 +00:00
updateWaterfallColors ( 0 ) ;
}
2016-02-06 13:49:10 +00:00
2016-03-20 15:06:10 +00:00
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" ;
bgRed = "linear-gradient(to top, #ff5939 , #961700)" ;
bgGreen = "linear-gradient(to top, #22ff2f , #008908)" ;
bgYellow = "linear-gradient(to top, #fff720 , #a49f00)" ;
bar . style . background = ( value > 0.9 ) ? bgRed : ( ( value > 0.7 ) ? bgYellow : bgGreen ) ;
//bar.style.backgroundColor=(value>0.9)?"#ff5939":((value>0.7)?"#fff720":"#22ff2f");
}
function getLogSmeterValue ( value )
{
return 10 * Math . log10 ( value ) ;
}
2016-03-21 08:10:41 +00:00
function getLinearSmeterValue ( db _value )
{
return Math . pow ( 10 , db _value / 10 ) ;
}
2016-03-20 15:06:10 +00:00
function setSmeterAbsoluteValue ( value ) //the value that comes from `csdr squelch_and_smeter_cc`
{
var logValue = getLogSmeterValue ( value ) ;
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
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 ) ;
}
2014-11-29 00:07:10 +00:00
// ========================================================
// ================= ANIMATION ROUTINES =================
// ========================================================
function animate ( object , style _name , unit , from , to , accel , time _ms , fps , to _exec )
{
//console.log(object.className);
if ( typeof to _exec == "undefined" ) to _exec = 0 ;
object . style [ style _name ] = from . toString ( ) + unit ;
object . anim _i = 0 ;
n _of _iters = time _ms / ( 1000 / fps ) ;
change = ( to - from ) / ( n _of _iters ) ;
if ( typeof object . anim _timer != "undefined" ) { window . clearInterval ( object . anim _timer ) ; }
object . anim _timer = window . setInterval (
function ( ) {
if ( object . anim _i ++ < n _of _iters )
{
if ( accel == 1 ) object . style [ style _name ] = ( parseFloat ( object . style [ style _name ] ) + change ) . toString ( ) + unit ;
2016-03-20 10:32:37 +00:00
else
{
2014-11-29 00:07:10 +00:00
remain = parseFloat ( object . style [ style _name ] ) - to ;
if ( Math . abs ( remain ) > 9 || unit != "px" ) new _val = ( to + accel * remain ) ;
else { if ( Math . abs ( remain ) < 2 ) new _val = to ;
else new _val = to + remain - ( remain / Math . abs ( remain ) ) ; }
object . style [ style _name ] = new _val . toString ( ) + unit ;
}
}
2016-03-20 10:32:37 +00:00
else
2014-11-29 00:07:10 +00:00
{ object . style [ style _name ] = to . toString ( ) + unit ; window . clearInterval ( object . anim _timer ) ; delete object . anim _timer ; }
if ( to _exec != 0 ) to _exec ( ) ;
} , 1000 / fps ) ;
}
function animate _to ( object , style _name , unit , to , accel , time _ms , fps , to _exec )
{
from = parseFloat ( style _value ( object , style _name ) ) ;
animate ( object , style _name , unit , from , to , accel , time _ms , fps , to _exec ) ;
}
// ========================================================
// ================ DEMODULATOR ROUTINES ================
// ========================================================
demodulators = [ ]
demodulator _color _index = 0 ;
demodulator _colors = [ "#ffff00" , "#00ff00" , "#00ffff" , "#058cff" , "#ff9600" , "#a1ff39" , "#ff4e39" , "#ff5dbd" ]
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
2016-03-20 10:32:37 +00:00
env _bounding _line _w = 5 ; //
2014-11-29 00:07:10 +00:00
env _att _w = 5 ; // _______ ___env_h2 in px ___|_____
env _h1 = 17 ; // _/| \_ ___env_h1 in px _/ |_ \_
env _h2 = 5 ; // |||env_att_line_w |_env_lineplus
env _lineplus = 1 ; // ||env_bounding_line_w
env _line _click _area = 6 ;
//range=get_visible_freq_range();
from _px = scale _px _from _freq ( from , range ) ;
to _px = scale _px _from _freq ( to , range ) ;
if ( to _px < from _px ) /* swap'em */ { temp _px = to _px ; to _px = from _px ; from _px = temp _px ; }
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +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 ;
to _px += env _bounding _line _w / 2 ; * /
from _px -= ( env _att _w + env _bounding _line _w ) ;
2016-03-20 10:32:37 +00:00
to _px += ( env _att _w + env _bounding _line _w ) ;
2014-11-29 00:07:10 +00:00
// 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 ( ) ;
}
2016-03-20 10:32:37 +00:00
if ( typeof line != "undefined" ) // out of screen?
2014-11-29 00:07:10 +00:00
{
line _px = scale _px _from _freq ( line , range ) ;
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().
in _range = function ( x , range ) { return range . x1 <= x && range . x2 >= x ; }
dr = demodulator . draggable _ranges ;
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 )
2016-03-20 10:32:37 +00:00
{
2014-11-29 00:07:10 +00:00
// 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
2016-03-20 10:32:37 +00:00
if ( in _range ( x , drag _ranges . whole _envelope ) ) return dr . anything _else ;
2014-11-29 00:07:10 +00:00
}
return dr . none ; //User doesn't drag the envelope for this demodulator
}
//******* class demodulator *******
// this can be used as a base class for ANY demodulator
demodulator = function ( offset _frequency )
{
//console.log("this too");
this . offset _frequency = offset _frequency ;
this . has _audio _output = true ;
this . has _text _output = false ;
this . envelope = { } ;
this . color = demodulators _get _next _color ( ) ;
this . stop = function ( ) { } ;
}
//ranges on filter envelope that can be dragged:
demodulator . draggable _ranges = { none : 0 , beginning : 1 /*from*/ , ending : 2 /*to*/ , anything _else : 3 , bfo : 4 /*line (while holding shift)*/ , pbs : 5 } //to which parameter these correspond in demod_envelope_draw()
//******* class demodulator_default_analog *******
// 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
2016-03-20 10:32:37 +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
function demodulator _default _analog ( offset _frequency , subtype )
{
//console.log("hopefully this happens");
//http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
demodulator . call ( this , offset _frequency ) ;
this . subtype = subtype ;
this . filter = {
min _passband : 100 ,
2016-03-21 10:08:59 +00:00
high _cut _limit : ( audio _server _output _rate / 2 ) - 1 , //audio_context.sampleRate/2,
low _cut _limit : ( - audio _server _output _rate / 2 ) + 1 //-audio_context.sampleRate/2
2014-11-29 00:07:10 +00:00
} ;
2016-03-20 10:32:37 +00:00
//Subtypes only define some filter parameters and the mod string sent to server,
2014-11-29 00:07:10 +00:00
//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" ;
2016-03-20 10:32:37 +00:00
}
2014-11-29 00:07:10 +00:00
else if ( subtype == "nfm" )
{
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
2016-03-20 10:32:37 +00:00
}
2014-11-29 00:07:10 +00:00
else if ( subtype == "am" )
{
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
2016-03-20 10:32:37 +00:00
}
2014-11-29 00:07:10 +00:00
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.
2016-03-20 10:32:37 +00:00
if ( ! this . wait _for _timer )
2014-11-29 00:07:10 +00:00
{
this . doset ( false ) ;
this . set _after = false ;
this . wait _for _timer = true ;
timeout _this = this ; //http://stackoverflow.com/a/2130411
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-05-04 21:11:13 +00:00
params = {
"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 } ) ) ;
2014-11-29 00:07:10 +00:00
}
this . doset ( true ) ; //we set parameters on object creation
//******* envelope object *******
// for drawing the filter envelope above scale
this . envelope . parent = this ;
2016-03-20 10:32:37 +00:00
this . envelope . draw = function ( visible _range )
2014-11-29 00:07:10 +00:00
{
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 ) ;
} ;
// 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 ) ;
//console.log("dragged_range: "+this.dragged_range.toString());
this . drag _origin = {
x : x ,
low _cut : this . parent . low _cut ,
high _cut : this . parent . high _cut ,
offset _frequency : this . parent . offset _frequency
} ;
return this . dragged _range != demodulator . draggable _ranges . none ;
} ;
this . envelope . drag _move = function ( x )
{
dr = demodulator . draggable _ranges ;
if ( this . dragged _range == dr . none ) return false ; // we return if user is not dragging (us) at all
freq _change = Math . round ( this . visible _range . hps * ( x - this . drag _origin . x ) ) ;
/ * i f ( t h i s . d r a g g e d _ r a n g e = = d r . b e g i n n i n g | | t h i s . d r a g g e d _ r a n g e = = d r . e n d i n g )
{
//we don't let the passband be too small
if ( this . parent . low _cut + new _freq _change <= this . parent . high _cut - this . parent . filter . min _passband ) this . freq _change = new _freq _change ;
else return ;
}
var new _value ; * /
//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.
minus = ( this . dragged _range == dr . bfo ) ? - 1 : 1 ;
//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.
2016-03-20 10:32:37 +00:00
if ( this . dragged _range == dr . beginning || this . dragged _range == dr . bfo || this . dragged _range == dr . pbs )
2014-11-29 00:07:10 +00:00
{
//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
2016-03-20 10:32:37 +00:00
if ( this . parent . high _cut - new _value < this . parent . filter . min _passband ) return true ;
2014-11-29 00:07:10 +00:00
//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 ;
}
2016-03-20 10:32:37 +00:00
if ( this . dragged _range == dr . ending || this . dragged _range == dr . bfo || this . dragged _range == dr . pbs )
2014-11-29 00:07:10 +00:00
{
//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
2016-03-20 10:32:37 +00:00
if ( new _value - this . parent . low _cut < this . parent . filter . min _passband ) return true ;
2014-11-29 00:07:10 +00:00
//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:
2016-03-20 10:32:37 +00:00
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , center _freq + this . parent . offset _frequency , 1e6 , 4 ) ;
2014-11-29 00:07:10 +00:00
return true ;
} ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
this . envelope . drag _end = function ( x )
{ //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
2017-04-19 17:59:26 +00:00
demodulator _buttons _update ( ) ;
2014-11-29 00:07:10 +00:00
to _return = this . dragged _range != demodulator . draggable _ranges . none ; //this part is required for cliking anywhere on the scale to set offset
this . dragged _range = demodulator . draggable _ranges . none ;
return to _return ;
} ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
demodulator _default _analog . prototype = new demodulator ( ) ;
function mkenvelopes ( visible _range ) //called from mkscale
{
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 ) ;
}
2017-05-22 06:37:14 +00:00
if ( demodulators . length ) secondary _demod _waterfall _set _zoom ( demodulators [ 0 ] . low _cut , demodulators [ 0 ] . high _cut ) ;
2014-11-29 00:07:10 +00:00
}
function demodulator _remove ( which )
{
demodulators [ which ] . stop ( ) ;
demodulators . splice ( which , 1 ) ;
}
function demodulator _add ( what )
{
demodulators . push ( what ) ;
mkenvelopes ( get _visible _freq _range ( ) ) ;
}
2017-05-02 22:32:08 +00:00
last _analog _demodulator _subtype = 'nfm' ;
last _digital _demodulator _subtype = 'bpsk31' ;
function demodulator _analog _replace ( subtype , for _digital )
2016-03-20 10:32:37 +00:00
{ //this function should only exist until the multi-demodulator capability is added
2017-05-03 14:32:47 +00:00
if ( ! ( typeof for _digital !== "undefined" && for _digital && secondary _demod ) )
{
secondary _demod _close _window ( ) ;
secondary _demod _listbox _update ( ) ;
}
2017-05-02 22:32:08 +00:00
last _analog _demodulator _subtype = subtype ;
2014-11-29 00:07:10 +00:00
var temp _offset = 0 ;
2016-03-20 10:32:37 +00:00
if ( demodulators . length )
2014-11-29 00:07:10 +00:00
{
temp _offset = demodulators [ 0 ] . offset _frequency ;
demodulator _remove ( 0 ) ;
}
demodulator _add ( new demodulator _default _analog ( temp _offset , subtype ) ) ;
2017-04-19 17:59:26 +00:00
demodulator _buttons _update ( ) ;
2014-11-29 00:07:10 +00:00
}
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 ( ) ) ;
}
// ========================================================
// =================== SCALE ROUTINES ===================
// ========================================================
var scale _ctx ;
var scale _canvas ;
function scale _setup ( )
{
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , canvas _get _frequency ( window . innerWidth / 2 ) , 1e6 , 4 ) ;
2016-03-20 10:32:37 +00:00
scale _canvas = e ( "openwebrx-scale-canvas" ) ;
2014-11-29 00:07:10 +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 ( ) ;
}
var scale _canvas _drag _params = {
mouse _down : false ,
drag : false ,
start _x : 0 ,
key _modifiers : { shiftKey : false , altKey : false , ctrlKey : false }
} ;
function scale _canvas _mousedown ( evt )
{
with ( scale _canvas _drag _params )
{
mouse _down = true ;
drag = false ;
start _x = evt . pageX ;
key _modifiers . shiftKey = evt . shiftKey ;
key _modifiers . altKey = evt . altKey ;
key _modifiers . ctrlKey = evt . ctrlKey ;
}
evt . preventDefault ( ) ;
}
function scale _offset _freq _from _px ( x , visible _range )
{
if ( typeof visible _range === "undefined" ) visible _range = get _visible _freq _range ( ) ;
return ( visible _range . start + visible _range . bw * ( x / canvas _container . clientWidth ) ) - center _freq ;
}
function scale _canvas _mousemove ( evt )
{
var event _handled ;
2016-03-20 10:32:37 +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 )
2014-11-29 00:07:10 +00:00
//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)
for ( var i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _start ( evt . pageX , scale _canvas _drag _params . key _modifiers ) ;
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)
for ( var i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _move ( evt . pageX ) ;
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
}
function scale _canvas _end _drag ( x )
{
canvas _container . style . cursor = "default" ;
scale _canvas _drag _params . drag = false ;
scale _canvas _drag _params . mouse _down = false ;
var event _handled = false ;
for ( var i = 0 ; i < demodulators . length ; i ++ ) event _handled |= demodulators [ i ] . envelope . drag _end ( x ) ;
//console.log(event_handled);
if ( ! event _handled ) demodulator _set _offset _frequency ( 0 , scale _offset _freq _from _px ( x ) ) ;
}
function scale _canvas _mouseup ( evt )
{
scale _canvas _end _drag ( evt . pageX ) ;
}
function scale _px _from _freq ( f , range ) { return Math . round ( ( ( f - range . start ) / range . bw ) * canvas _container . clientWidth ) ; }
function get _visible _freq _range ( )
{
out = { } ;
fcalc = function ( x ) { return Math . round ( ( ( - zoom _offset _px + x ) / canvases [ 0 ] . clientWidth ) * bandwidth ) + ( center _freq - bandwidth / 2 ) ; }
out . start = fcalc ( 0 ) ;
out . center = fcalc ( canvas _container . clientWidth / 2 ) ;
out . end = fcalc ( canvas _container . clientWidth ) ;
out . bw = out . end - out . start ;
out . hps = out . bw / canvas _container . clientWidth ;
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
}
] ;
var scale _min _space _bw _texts = 50 ;
var scale _min _space _bw _small _markers = 7 ;
function get _scale _mark _spacing ( range )
{
out = { } ;
2016-03-20 10:32:37 +00:00
fcalc = function ( freq )
{
2014-11-29 00:07:10 +00:00
out . numlarge = ( range . bw / freq ) ;
out . large = canvas _container . clientWidth / out . numlarge ; //distance between large markers (these have text)
out . ratio = 5 ; //(ratio-1) small markers exist per large marker
out . small = out . large / out . ratio ; //distance between small markers
2016-03-20 10:32:37 +00:00
if ( out . small < scale _min _space _bw _small _markers ) return false ;
2014-11-29 00:07:10 +00:00
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 ;
}
for ( i = scale _markers _levels . length - 1 ; i >= 0 ; i -- )
{
mp = scale _markers _levels [ i ] ;
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 ;
}
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" ;
spacing = get _scale _mark _spacing ( range ) ;
//console.log(spacing);
marker _hz = Math . ceil ( range . start / spacing . smallbw ) * spacing . smallbw ;
text _h _pos = 22 + 10 + ( ( is _firefox ) ? 3 : 0 ) ;
var text _to _draw ;
var ftext = function ( f ) { text _to _draw = format _frequency ( spacing . params . format , f , spacing . params . pre _divide , spacing . params . decimals ) ; }
var last _large ;
for ( ; ; )
{
var x = scale _px _from _freq ( marker _hz , range ) ;
if ( x > window . innerWidth ) break ;
2016-03-20 10:32:37 +00:00
scale _ctx . beginPath ( ) ;
2014-11-29 00:07:10 +00:00
scale _ctx . moveTo ( x , 22 ) ;
if ( marker _hz % spacing . params . large _marker _per _hz == 0 )
{ //large marker
2016-03-20 10:32:37 +00:00
if ( typeof first _large == "undefined" ) var first _large = marker _hz ;
2014-11-29 00:07:10 +00:00
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
2015-08-17 18:32:58 +00:00
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" ;
2016-03-20 10:32:37 +00:00
scale _ctx . fillText ( text _to _draw , 0 , text _h _pos ) ;
2014-11-29 00:07:10 +00:00
}
}
2016-03-20 10:32:37 +00:00
else if ( zoom _level == 0 && ( range . end - spacing . smallbw * spacing . ratio < marker _hz ) && ( x > window . innerWidth - text _measured . width / 2 ) )
2015-08-17 18:32:58 +00:00
{ // 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" ;
2016-03-20 10:32:37 +00:00
scale _ctx . fillText ( text _to _draw , window . innerWidth , text _h _pos ) ;
}
2014-11-29 00:07:10 +00:00
}
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 ;
var x = scale _px _from _freq ( f , range ) ;
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 ) ;
}
}
function resize _scale ( )
{
scale _ctx . canvas . width = window . innerWidth ;
scale _ctx . canvas . height = 47 ;
mkscale ( ) ;
}
function canvas _mouseover ( evt )
{
if ( ! waterfall _setup _done ) return ;
2016-03-20 10:32:37 +00:00
//e("webrx-freq-show").style.visibility="visible";
2014-11-29 00:07:10 +00:00
}
function canvas _mouseout ( evt )
{
if ( ! waterfall _setup _done ) return ;
//e("webrx-freq-show").style.visibility="hidden";
}
function canvas _get _freq _offset ( relativeX )
{
rel = ( relativeX / canvases [ 0 ] . clientWidth ) ;
return Math . round ( ( bandwidth * rel ) - ( bandwidth / 2 ) ) ;
}
function canvas _get _frequency ( relativeX )
{
return center _freq + canvas _get _freq _offset ( relativeX ) ;
}
/ * f u n c t i o n c a n v a s _ f o r m a t _ f r e q u e n c y ( r e l a t i v e X )
{
return ( canvas _get _frequency ( relativeX ) / 1e6 ) . toFixed ( 3 ) + " MHz" ;
} * /
function format _frequency ( format , freq _hz , pre _divide , decimals )
{
out = format . replace ( "{x}" , ( freq _hz / pre _divide ) . toFixed ( decimals ) ) ;
at = out . indexOf ( "." ) + 4 ;
while ( decimals > 3 )
{
out = out . substr ( 0 , at ) + "," + out . substr ( at ) ;
at += 4 ;
decimals -= 3 ;
}
return out ;
}
canvas _drag = false ;
canvas _drag _min _delta = 1 ;
canvas _mouse _down = false ;
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
}
function canvas _mousemove ( evt )
{
if ( ! waterfall _setup _done ) return ;
//element=e("webrx-freq-show");
relativeX = ( evt . offsetX ) ? evt . offsetX : evt . layerX ;
/ * r e a l X = ( r e l a t i v e X - e l e m e n t . c l i e n t W i d t h / 2 ) ;
maxX = ( canvases [ 0 ] . clientWidth - element . clientWidth ) ;
if ( realX > maxX ) realX = maxX ;
if ( realX < 0 ) realX = 0 ;
element . style . left = realX . toString ( ) + "px" ; * /
if ( canvas _mouse _down )
{
2016-03-20 10:32:37 +00:00
if ( ! canvas _drag && Math . abs ( evt . pageX - canvas _drag _start _x ) > canvas _drag _min _delta )
2014-11-29 00:07:10 +00:00
{
canvas _drag = true ;
canvas _container . style . cursor = "move" ;
}
2016-03-20 10:32:37 +00:00
if ( canvas _drag )
2014-11-29 00:07:10 +00:00
{
var deltaX = canvas _drag _last _x - evt . pageX ;
var deltaY = canvas _drag _last _y - evt . pageY ;
//zoom_center_where=zoom_center_where_calc(evt.pageX);
2016-03-20 10:32:37 +00:00
var dpx = range . hps * deltaX ;
2014-11-29 00:07:10 +00:00
if (
! ( zoom _center _rel + dpx > ( bandwidth / 2 - canvas _container . clientWidth * ( 1 - zoom _center _where ) * range . hps ) ) &&
! ( zoom _center _rel + dpx < - bandwidth / 2 + canvas _container . clientWidth * zoom _center _where * range . hps )
) { zoom _center _rel += dpx ; }
// -((canvases_new_width*(0.5+zoom_center_rel/bandwidth))-(winsize*zoom_center_where));
resize _canvases ( false ) ;
canvas _drag _last _x = evt . pageX ;
canvas _drag _last _y = evt . pageY ;
mkscale ( ) ;
}
}
else e ( "webrx-mouse-freq" ) . innerHTML = format _frequency ( "{x} MHz" , canvas _get _frequency ( relativeX ) , 1e6 , 4 ) ;
}
function canvas _container _mouseout ( evt )
{
canvas _end _drag ( ) ;
}
//function body_mouseup() { canvas_end_drag(); console.log("body_mouseup"); }
//function window_mouseout() { canvas_end_drag(); console.log("document_mouseout"); }
function canvas _mouseup ( evt )
{
if ( ! waterfall _setup _done ) return ;
relativeX = ( evt . offsetX ) ? evt . offsetX : evt . layerX ;
2016-03-20 10:32:37 +00:00
if ( ! canvas _drag )
2014-11-29 00:07:10 +00:00
{
//ws.send("SET offset_freq="+canvas_get_freq_offset(relativeX).toString());
2016-03-20 10:32:37 +00:00
demodulator _set _offset _frequency ( 0 , canvas _get _freq _offset ( relativeX ) ) ;
2014-12-21 22:38:45 +00:00
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , canvas _get _frequency ( relativeX ) , 1e6 , 4 ) ;
2014-11-29 00:07:10 +00:00
}
else
{
canvas _end _drag ( ) ;
}
canvas _mouse _down = false ;
}
function canvas _end _drag ( )
{
canvas _container . style . cursor = "crosshair" ;
canvas _mouse _down = false ;
}
function zoom _center _where _calc ( screenposX )
{
//return (screenposX-(window.innerWidth-canvas_container.clientWidth))/canvas_container.clientWidth;
return screenposX / canvas _container . clientWidth ;
}
function canvas _mousewheel ( evt )
{
if ( ! waterfall _setup _done ) return ;
//var i=Math.abs(evt.wheelDelta);
//var dir=(i/evt.wheelDelta)<0;
//console.log(evt);
var relativeX = ( evt . offsetX ) ? evt . offsetX : evt . layerX ;
var dir = ( evt . deltaY / Math . abs ( evt . deltaY ) ) > 0 ;
2014-11-30 11:21:19 +00:00
//console.log(dir);
2014-11-29 00:07:10 +00:00
//i/=120;
/*while (i--)*/ zoom _step ( dir , relativeX , zoom _center _where _calc ( evt . pageX ) ) ;
2016-03-20 10:32:37 +00:00
evt . preventDefault ( ) ;
2014-11-29 00:07:10 +00:00
//evt.returnValue = false; //disable scrollbar move
}
zoom _max _level _hps = 33 ; //Hz/pixel
2016-06-07 19:55:58 +00:00
zoom _levels _count = 14 ;
2014-11-29 00:07:10 +00:00
function get _zoom _coeff _from _hps ( hps )
{
var shown _bw = ( window . innerWidth * hps ) ;
return bandwidth / shown _bw ;
}
zoom _levels = [ 1 ] ;
zoom _level = 0 ;
zoom _freq = 0 ;
zoom _offset _px = 0 ;
zoom _center _rel = 0 ;
zoom _center _where = 0 ;
2016-03-21 08:10:41 +00:00
smeter _level = 0 ;
2014-11-29 00:07:10 +00:00
function mkzoomlevels ( )
{
zoom _levels = [ 1 ] ;
maxc = get _zoom _coeff _from _hps ( zoom _max _level _hps ) ;
if ( maxc < 1 ) return ;
2016-06-07 19:55:58 +00:00
// logarithmic interpolation
zoom _ratio = Math . pow ( maxc , 1 / zoom _levels _count ) ;
2014-11-29 00:07:10 +00:00
for ( i = 1 ; i < zoom _levels _count ; i ++ )
2016-06-07 19:55:58 +00:00
zoom _levels . push ( Math . pow ( zoom _ratio , i ) ) ;
2014-11-29 00:07:10 +00:00
}
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 ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
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 ;
2017-05-03 14:32:47 +00:00
//console.log(zoom_center_where, zoom_center_rel, where);
2016-03-20 10:32:37 +00:00
resize _canvases ( true ) ;
mkscale ( ) ;
}
function zoom _set ( level )
{
if ( ! ( level >= 0 && level <= zoom _levels . length - 1 ) ) return ;
level = parseInt ( level ) ;
zoom _level = level ;
//zoom_center_rel=canvas_get_freq_offset(-canvases[0].offsetLeft+canvas_container.clientWidth/2); //zoom to screen center instead of demod envelope
zoom _center _rel = demodulators [ 0 ] . offset _frequency ;
zoom _center _where = 0.5 + ( zoom _center _rel / bandwidth ) ; //this is a kind of hack
console . log ( zoom _center _where , zoom _center _rel , - canvases [ 0 ] . offsetLeft + canvas _container . clientWidth / 2 ) ;
2014-11-29 00:07:10 +00:00
resize _canvases ( true ) ;
mkscale ( ) ;
}
function zoom _calc ( )
{
winsize = canvas _container . clientWidth ;
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 ;
2016-03-20 10:32:37 +00:00
if ( zoom _offset _px < winsize - canvases _new _width )
2014-11-29 00:07:10 +00:00
zoom _offset _px = winsize - canvases _new _width ;
//console.log("zoom_calc || zopx:"+zoom_offset_px.toString()+ " maxoff:"+(winsize-canvases_new_width).toString()+" relval:"+(0.5+zoom_center_rel/bandwidth).toString() );
}
function resize _waterfall _container ( check _init )
{
if ( check _init && ! waterfall _setup _done ) return ;
2016-08-07 17:03:48 +00:00
var numHeight ;
mathbox _container . style . height = canvas _container . style . height = ( numHeight = window . innerHeight - e ( "webrx-top-container" ) . clientHeight - e ( "openwebrx-scale-container" ) . clientHeight ) . toString ( ) + "px" ;
if ( mathbox )
{
//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight;
//mathbox.three.camera.updateProjectionMatrix();
mathbox . three . renderer . setSize ( document . body . offsetWidth , numHeight ) ;
console . log ( document . body . offsetWidth , numHeight ) ;
}
2014-11-29 00:07:10 +00:00
2016-08-07 17:03:48 +00:00
}
2015-09-30 14:06:30 +00:00
audio _server _output _rate = 11025 ;
audio _client _resampling _factor = 4 ;
function audio _calculate _resampling ( targetRate )
{ //both at the server and the client
output _range _max = 12000 ;
output _range _min = 8000 ;
i = 1 ;
while ( true )
{
audio _server _output _rate = Math . floor ( targetRate / i ) ;
2016-03-20 10:32:37 +00:00
if ( audio _server _output _rate < output _range _min )
{
audio _client _resampling _factor = audio _server _output _rate = 0 ;
2015-09-30 14:06:30 +00:00
divlog ( "Your audio card sampling rate (" + targetRate . toString ( ) + ") is not supported.<br />Please change your operating system default settings in order to fix this." , 1 ) ;
}
if ( audio _server _output _rate >= output _range _min && audio _server _output _rate <= output _range _max ) break ; //okay, we're done
i ++ ;
}
audio _client _resampling _factor = i ;
console . log ( "audio_calculate_resampling() :: " + audio _client _resampling _factor . toString ( ) + ", " + audio _server _output _rate . toString ( ) ) ;
}
2015-08-17 18:32:58 +00:00
debug _ws _data _received = 0 ;
max _clients _num = 0 ;
var COMPRESS _FFT _PAD _N = 10 ; //should be the same as in csdr.c
2014-11-29 00:07:10 +00:00
function on _ws _recv ( evt )
{
2019-05-04 14:56:23 +00:00
if ( typeof evt . data == 'string' ) {
// text messages
if ( evt . data . substr ( 0 , 16 ) == "CLIENT DE SERVER" ) {
divlog ( "Server acknowledged WebSocket connection." ) ;
} else {
try {
json = JSON . parse ( evt . data )
switch ( json . type ) {
case "config" :
config = json . value ;
window . waterfall _colors = config . waterfall _colors ;
window . waterfall _min _level _default = config . waterfall _min _level ;
window . waterfall _max _level _default = config . waterfall _max _level ;
window . waterfall _auto _level _margin = config . waterfall _auto _level _margin ;
waterfallColorsDefault ( ) ;
2019-05-04 18:40:13 +00:00
window . starting _mod = config . start _mod
window . starting _offset _frequency = config . start _offset _frequency ;
2019-05-04 21:11:13 +00:00
window . audio _buffering _fill _to = config . client _audio _buffer _size ;
2019-05-04 14:56:23 +00:00
bandwidth = config . samp _rate ;
center _freq = config . shown _center _freq ;
fft _size = config . fft _size ;
fft _fps = config . fft _fps ;
audio _compression = config . audio _compression ;
divlog ( "Audio stream is " + ( ( audio _compression == "adpcm" ) ? "compressed" : "uncompressed" ) + "." )
fft _compression = config . fft _compression ;
divlog ( "FFT stream is " + ( ( fft _compression == "adpcm" ) ? "compressed" : "uncompressed" ) + "." )
max _clients _num = config . max _clients ;
waterfall _init ( ) ;
audio _preinit ( ) ;
2019-05-07 13:21:16 +00:00
if ( audio _allowed ) audio _init ( ) ;
waterfall _clear ( ) ;
2019-05-04 14:56:23 +00:00
break ;
2019-05-05 20:09:48 +00:00
case "secondary_config" :
window . secondary _fft _size = json . value . secondary _fft _size ;
window . secondary _bw = json . value . secondary _bw ;
window . if _samp _rate = json . value . if _samp _rate ;
secondary _demod _init _canvases ( ) ;
break ;
2019-05-05 15:52:26 +00:00
case "receiver_details" :
var r = json . value ;
e ( 'webrx-rx-title' ) . innerHTML = r . receiver _name ;
e ( 'webrx-rx-desc' ) . innerHTML = r . receiver _location + ' | Loc: ' + r . receiver _qra + ', 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 ;
break ;
2019-05-05 14:17:55 +00:00
case "smeter" :
setSmeterAbsoluteValue ( json . value ) ;
break ;
2019-05-05 15:34:40 +00:00
case "cpuusage" :
var server _cpu _usage = json . value ;
progressbar _set ( e ( "openwebrx-bar-server-cpu" ) , server _cpu _usage / 100 , "Server CPU [" + server _cpu _usage + "%]" , server _cpu _usage > 85 ) ;
break ;
2019-05-04 14:56:23 +00:00
default :
2019-05-05 15:34:40 +00:00
console . warn ( 'received message of unknown type: ' + json . type ) ;
2019-05-04 14:56:23 +00:00
}
} catch ( e ) {
// don't lose exception
console . error ( e )
}
}
} else if ( evt . data instanceof ArrayBuffer ) {
// binary messages
2019-05-04 18:26:11 +00:00
type = new Uint8Array ( evt . data , 0 , 1 ) [ 0 ]
data = evt . data . slice ( 1 )
switch ( type ) {
case 1 :
2019-05-04 21:11:13 +00:00
// FFT data
2019-05-04 18:26:11 +00:00
if ( fft _compression == "none" ) {
waterfall _add _queue ( new Float32Array ( data ) ) ;
} else if ( fft _compression == "adpcm" ) {
fft _codec . reset ( ) ;
var waterfall _i16 = fft _codec . decode ( new Uint8Array ( data ) ) ;
var waterfall _f32 = new Float32Array ( waterfall _i16 . length - COMPRESS _FFT _PAD _N ) ;
for ( var i = 0 ; i < waterfall _i16 . length ; i ++ ) waterfall _f32 [ i ] = waterfall _i16 [ i + COMPRESS _FFT _PAD _N ] / 100 ;
waterfall _add _queue ( waterfall _f32 ) ;
}
break ;
2019-05-04 21:11:13 +00:00
case 2 :
// audio data
var audio _data ;
if ( audio _compression == "adpcm" ) {
audio _data = new Uint8Array ( data ) ;
} else {
audio _data = new Int16Array ( data ) ;
}
audio _prepare ( audio _data ) ;
audio _buffer _current _size _debug += audio _data . length ;
audio _buffer _all _size _debug += audio _data . length ;
if ( ! ( ios || is _chrome ) && ( audio _initialized == 0 && audio _prepared _buffers . length > audio _buffering _fill _to ) ) audio _init ( )
break ;
2019-05-05 20:09:48 +00:00
case 3 :
// secondary FFT
if ( fft _compression == "none" ) {
secondary _demod _waterfall _add _queue ( new Float32Array ( data ) ) ;
} else if ( fft _compression == "adpcm" ) {
fft _codec . reset ( ) ;
var waterfall _i16 = fft _codec . decode ( new Uint8Array ( data ) ) ;
var waterfall _f32 = new Float32Array ( waterfall _i16 . length - COMPRESS _FFT _PAD _N ) ;
for ( var i = 0 ; i < waterfall _i16 . length ; i ++ ) waterfall _f32 [ i ] = waterfall _i16 [ i + COMPRESS _FFT _PAD _N ] / 100 ;
secondary _demod _waterfall _add _queue ( waterfall _f32 ) ; //TODO digimodes
}
break ;
case 4 :
// secondary demod
secondary _demod _push _data ( arrayBufferToString ( data ) ) ;
break ;
2019-05-04 18:26:11 +00:00
default :
console . warn ( 'unknown type of binary message: ' + type )
}
2019-05-04 14:56:23 +00:00
}
2014-11-29 00:07:10 +00:00
}
function add _problem ( what )
{
problems _span = e ( "openwebrx-problems" ) ;
for ( var i = 0 ; i < problems _span . children . length ; i ++ ) if ( problems _span . children [ i ] . innerHTML == what ) return ;
new _span = document . createElement ( "span" ) ;
new _span . innerHTML = what ;
problems _span . appendChild ( new _span ) ;
window . setTimeout ( function ( ps , ns ) { ps . removeChild ( ns ) ; } , 1000 , problems _span , new _span ) ;
}
2015-08-17 18:32:58 +00:00
waterfall _measure _minmax = false ;
2016-03-20 10:32:37 +00:00
waterfall _measure _minmax _now = false ;
2015-08-17 18:32:58 +00:00
waterfall _measure _minmax _min = 1e100 ;
waterfall _measure _minmax _max = - 1e100 ;
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 ) ) ;
}
function waterfall _measure _minmax _print ( )
{
console . log ( "Waterfall | min = " + waterfall _measure _minmax _min . toString ( ) + " dB | max = " + waterfall _measure _minmax _max . toString ( ) + " dB" ) ;
}
2014-11-29 00:07:10 +00:00
function waterfall _add _queue ( what )
{
2015-08-17 18:32:58 +00:00
if ( waterfall _measure _minmax ) waterfall _measure _minmax _do ( what ) ;
2016-03-20 10:32:37 +00:00
if ( waterfall _measure _minmax _now ) { waterfall _measure _minmax _do ( what ) ; waterfall _measure _minmax _now = false ; waterfallColorsAuto ( ) ; }
2014-11-29 00:07:10 +00:00
waterfall _queue . push ( what ) ;
}
function waterfall _dequeue ( )
{
if ( waterfall _queue . length ) waterfall _add ( waterfall _queue . shift ( ) ) ;
2016-03-20 10:32:37 +00:00
if ( waterfall _queue . length > Math . max ( fft _fps / 2 , 20 ) ) //in case of emergency
2014-11-29 00:07:10 +00:00
{
2015-08-17 18:32:58 +00:00
console . log ( "waterfall queue length:" , waterfall _queue . length ) ;
2014-11-29 00:07:10 +00:00
add _problem ( "fft overflow" ) ;
while ( waterfall _queue . length ) waterfall _add ( waterfall _queue . shift ( ) ) ;
}
}
function on _ws _opened ( )
{
ws . send ( "SERVER DE CLIENT openwebrx.js" ) ;
divlog ( "WebSocket opened to " + ws _url ) ;
}
2015-08-17 18:32:58 +00:00
var was _error = 0 ;
2014-11-29 00:07:10 +00:00
function divlog ( what , is _error )
{
2015-08-17 18:32:58 +00:00
is _error = ! ! is _error ;
was _error |= is _error ;
2016-03-20 10:32:37 +00:00
if ( is _error )
2015-08-17 18:32:58 +00:00
{
what = "<span class=\"webrx-error\">" + what + "</span>" ;
if ( e ( "openwebrx-panel-log" ) . openwebrxHidden ) toggle _panel ( "openwebrx-panel-log" ) ; //show panel if any error is present
}
2014-11-29 00:07:10 +00:00
e ( "openwebrx-debugdiv" ) . innerHTML += what + "<br />" ;
2017-05-03 14:32:47 +00:00
//var wls=e("openwebrx-log-scroll");
//wls.scrollTop=wls.scrollHeight; //scroll to bottom
$ ( ".nano" ) . nanoScroller ( ) ;
$ ( ".nano" ) . nanoScroller ( { scroll : 'bottom' } ) ;
2014-11-29 00:07:10 +00:00
}
var audio _context ;
var audio _initialized = 0 ;
2016-02-13 23:31:28 +00:00
var volume = 1.0 ;
var volumeBeforeMute = 100.0 ;
2016-02-06 13:49:10 +00:00
var mute = false ;
2014-11-29 00:07:10 +00:00
var audio _received = Array ( ) ;
var audio _buffer _index = 0 ;
2015-09-30 14:06:30 +00:00
var audio _resampler ;
2015-08-17 18:32:58 +00:00
var audio _codec = new sdrjs . ImaAdpcm ( ) ;
var audio _compression = "unknown" ;
2014-11-29 00:07:10 +00:00
var audio _node ;
//var audio_received_sample_rate = 48000;
var audio _input _buffer _size ;
// Optimalise these if audio lags or is choppy:
2016-07-24 11:51:09 +00:00
var audio _buffer _size ;
2015-08-17 18:32:58 +00:00
var audio _buffer _maximal _length _sec = 3 ; //actual number of samples are calculated from sample rate
var audio _buffer _decrease _to _on _overrun _sec = 2.2 ;
var audio _flush _interval _ms = 500 ; //the interval in which audio_flush() is called
2014-11-29 00:07:10 +00:00
var audio _prepared _buffers = Array ( ) ;
2016-07-24 11:51:09 +00:00
var audio _rebuffer ;
var audio _last _output _buffer ;
2014-11-29 00:07:10 +00:00
var audio _last _output _offset = 0 ;
var audio _buffering = false ;
2015-08-17 18:32:58 +00:00
//var audio_buffering_fill_to=4; //on audio underrun we wait until this n*audio_buffer_size samples are present
//tnx to the hint from HA3FLT, now we have about half the response time! (original value: 10)
function gain _ff ( gain _value , data ) //great! solved clicking! will have to move to sdr.js
{
for ( var i = 0 ; i < data . length ; i ++ )
data [ i ] *= gain _value ;
return data ;
}
2014-11-29 00:07:10 +00:00
function audio _prepare ( data )
2015-08-17 18:32:58 +00:00
{
2016-03-20 10:32:37 +00:00
2015-08-17 18:32:58 +00:00
//audio_rebuffer.push(sdrjs.ConvertI16_F(data));//no resampling
//audio_rebuffer.push(audio_resampler.process(sdrjs.ConvertI16_F(data)));//resampling without ADPCM
if ( audio _compression == "none" )
2016-02-06 13:49:10 +00:00
audio _rebuffer . push ( audio _resampler . process ( gain _ff ( volume , sdrjs . ConvertI16 _F ( data ) ) ) ) ; //resampling without ADPCM
2015-08-17 18:32:58 +00:00
else if ( audio _compression == "adpcm" )
2016-02-06 13:49:10 +00:00
audio _rebuffer . push ( audio _resampler . process ( gain _ff ( volume , sdrjs . ConvertI16 _F ( audio _codec . decode ( data ) ) ) ) ) ; //resampling & ADPCM
2015-08-17 18:32:58 +00:00
else return ;
//console.log("prepare",data.length,audio_rebuffer.remaining());
2016-03-20 10:32:37 +00:00
while ( audio _rebuffer . remaining ( ) )
2015-08-17 18:32:58 +00:00
{
audio _prepared _buffers . push ( audio _rebuffer . take ( ) ) ;
audio _buffer _current _count _debug ++ ;
}
2015-09-30 14:06:30 +00:00
if ( audio _buffering && audio _prepared _buffers . length > audio _buffering _fill _to ) { console . log ( "buffers now: " + audio _prepared _buffers . length . toString ( ) ) ; audio _buffering = false ; }
2015-08-17 18:32:58 +00:00
}
function audio _prepare _without _resampler ( data )
{
audio _rebuffer . push ( sdrjs . ConvertI16 _F ( data ) ) ;
console . log ( "prepare" , data . length , audio _rebuffer . remaining ( ) ) ;
2016-03-20 10:32:37 +00:00
while ( audio _rebuffer . remaining ( ) )
2015-08-17 18:32:58 +00:00
{
audio _prepared _buffers . push ( audio _rebuffer . take ( ) ) ;
audio _buffer _current _count _debug ++ ;
}
if ( audio _buffering && audio _prepared _buffers . length > audio _buffering _fill _to ) audio _buffering = false ;
}
function audio _prepare _old ( data )
2014-11-29 00:07:10 +00:00
{
//console.log("audio_prepare :: "+data.length.toString());
//console.log("data.len = "+data.length.toString());
var dopush = function ( )
{
2015-08-17 18:32:58 +00:00
console . log ( audio _last _output _buffer ) ;
2014-11-29 00:07:10 +00:00
audio _prepared _buffers . push ( audio _last _output _buffer ) ;
audio _last _output _offset = 0 ;
audio _last _output _buffer = new Float32Array ( audio _buffer _size ) ;
audio _buffer _current _count _debug ++ ;
} ;
2015-08-17 18:32:58 +00:00
var original _data _length = data . length ;
var f32data = new Float32Array ( data . length ) ;
for ( var i = 0 ; i < data . length ; i ++ ) f32data [ i ] = data [ i ] / 32768 ; //convert_i16_f
data = audio _resampler . process ( f32data ) ;
console . log ( data , data . length , original _data _length ) ;
2014-11-29 00:07:10 +00:00
if ( data . length == 0 ) return ;
if ( audio _last _output _offset + data . length <= audio _buffer _size )
{ //array fits into output buffer
2015-08-17 18:32:58 +00:00
for ( var i = 0 ; i < data . length ; i ++ ) audio _last _output _buffer [ i + audio _last _output _offset ] = data [ i ] ;
2014-11-29 00:07:10 +00:00
audio _last _output _offset += data . length ;
2015-08-17 18:32:58 +00:00
console . log ( "fits into; offset=" + audio _last _output _offset . toString ( ) ) ;
2014-11-29 00:07:10 +00:00
if ( audio _last _output _offset == audio _buffer _size ) dopush ( ) ;
}
else
2016-03-20 10:32:37 +00:00
{ //array is larger than the remaining space in the output buffer
2014-11-29 00:07:10 +00:00
var copied = audio _buffer _size - audio _last _output _offset ;
var remain = data . length - copied ;
for ( var i = 0 ; i < audio _buffer _size - audio _last _output _offset ; i ++ ) //fill the remaining space in the output buffer
2015-08-17 18:32:58 +00:00
audio _last _output _buffer [ i + audio _last _output _offset ] = data [ i ] ; ///32768;
2014-11-29 00:07:10 +00:00
dopush ( ) ; //push the output buffer and create a new one
2015-08-17 18:32:58 +00:00
console . log ( "larger than; copied half: " + copied . toString ( ) + ", now at: " + audio _last _output _offset . toString ( ) ) ;
2014-11-29 00:07:10 +00:00
for ( var i = 0 ; i < remain ; i ++ ) //copy the remaining input samples to the new output buffer
2015-08-17 18:32:58 +00:00
audio _last _output _buffer [ i ] = data [ i + copied ] ; ///32768;
2014-11-29 00:07:10 +00:00
audio _last _output _offset += remain ;
2015-08-17 18:32:58 +00:00
console . log ( "larger than; remained: " + remain . toString ( ) + ", now at: " + audio _last _output _offset . toString ( ) ) ;
2014-11-29 00:07:10 +00:00
}
if ( audio _buffering && audio _prepared _buffers . length > audio _buffering _fill _to ) audio _buffering = false ;
}
if ( ! AudioBuffer . prototype . copyToChannel )
{ //Chrome 36 does not have it, Firefox does
AudioBuffer . prototype . copyToChannel = function ( input , channel ) //input is Float32Array
{
var cd = this . getChannelData ( channel ) ;
for ( var i = 0 ; i < input . length ; i ++ ) cd [ i ] = input [ i ] ;
}
}
function audio _onprocess ( e )
2015-08-17 18:32:58 +00:00
{
//console.log("audio onprocess");
2014-11-29 00:07:10 +00:00
if ( audio _buffering ) return ;
2015-08-17 18:32:58 +00:00
if ( audio _prepared _buffers . length == 0 ) { audio _buffer _progressbar _update ( ) ; /*add_problem("audio underrun");*/ audio _buffering = true ; }
else { e . outputBuffer . copyToChannel ( audio _prepared _buffers . shift ( ) , 0 ) ; }
2014-11-29 00:07:10 +00:00
}
2015-08-17 18:32:58 +00:00
var audio _buffer _progressbar _update _disabled = false ;
var audio _buffer _total _average _level = 0 ;
var audio _buffer _total _average _level _length = 0 ;
2015-09-30 14:06:30 +00:00
var audio _overrun _cnt = 0 ;
var audio _underrun _cnt = 0 ;
2015-08-17 18:32:58 +00:00
function audio _buffer _progressbar _update ( )
{
if ( audio _buffer _progressbar _update _disabled ) return ;
2016-07-24 11:34:15 +00:00
var audio _buffer _value = ( audio _prepared _buffers . length * audio _buffer _size ) / audio _context . sampleRate ;
2015-08-17 18:32:58 +00:00
audio _buffer _total _average _level _length ++ ; audio _buffer _total _average _level = ( audio _buffer _total _average _level * ( ( audio _buffer _total _average _level _length - 1 ) / audio _buffer _total _average _level _length ) ) + ( audio _buffer _value / audio _buffer _total _average _level _length ) ;
var overrun = audio _buffer _value > audio _buffer _maximal _length _sec ;
var underrun = audio _prepared _buffers . length == 0 ;
var text = "buffer" ;
2015-09-30 14:06:30 +00:00
if ( overrun ) { text = "overrun" ; console . log ( "audio overrun, " + ( ++ audio _overrun _cnt ) . toString ( ) ) ; }
if ( underrun ) { text = "underrun" ; console . log ( "audio underrun, " + ( ++ audio _underrun _cnt ) . toString ( ) ) ; }
2016-03-20 10:32:37 +00:00
if ( overrun || underrun )
{
2015-08-17 18:32:58 +00:00
audio _buffer _progressbar _update _disabled = true ;
window . setTimeout ( function ( ) { audio _buffer _progressbar _update _disabled = false ; audio _buffer _progressbar _update ( ) ; } , 1000 ) ;
}
2016-03-20 10:32:37 +00:00
progressbar _set ( e ( "openwebrx-bar-audio-buffer" ) , ( underrun ) ? 1 : audio _buffer _value / 1.5 , "Audio " + text + " [" + ( audio _buffer _value ) . toFixed ( 1 ) + " s]" , overrun || underrun || audio _buffer _value < 0.25 ) ;
2015-08-17 18:32:58 +00:00
}
2014-11-29 00:07:10 +00:00
function audio _flush ( )
{
flushed = false ;
2015-08-17 18:32:58 +00:00
we _have _more _than = function ( sec ) { return sec * audio _context . sampleRate < audio _prepared _buffers . length * audio _buffer _size ; }
if ( we _have _more _than ( audio _buffer _maximal _length _sec ) ) while ( we _have _more _than ( audio _buffer _decrease _to _on _overrun _sec ) )
2014-11-29 00:07:10 +00:00
{
2015-08-17 18:32:58 +00:00
if ( ! flushed ) audio _buffer _progressbar _update ( ) ;
2014-11-29 00:07:10 +00:00
flushed = true ;
audio _prepared _buffers . shift ( ) ;
}
2015-08-17 18:32:58 +00:00
//if(flushed) add_problem("audio overrun");
2014-11-29 00:07:10 +00:00
}
2016-03-20 10:32:37 +00:00
function audio _onprocess _notused ( e )
2014-11-29 00:07:10 +00:00
{
//https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js
2016-03-20 10:32:37 +00:00
if ( audio _received . length == 0 )
2014-11-29 00:07:10 +00:00
{ add _problem ( "audio underrun" ) ; return ; }
output = e . outputBuffer . getChannelData ( 0 ) ;
int _buffer = audio _received [ 0 ] ;
read _remain = audio _buffer _size ;
//audio_buffer_maximal_length=120;
obi = 0 ; //output buffer index
debug _str = ""
2016-03-20 10:32:37 +00:00
while ( 1 )
2014-11-29 00:07:10 +00:00
{
if ( int _buffer . length - audio _buffer _index > read _remain )
{
for ( i = audio _buffer _index ; i < audio _buffer _index + read _remain ; i ++ )
output [ obi ++ ] = int _buffer [ i ] / 32768 ;
//debug_str+="added whole ibl="+int_buffer.length.toString()+" abi="+audio_buffer_index.toString()+" "+(int_buffer.length-audio_buffer_index).toString()+">"+read_remain.toString()+" obi="+obi.toString()+"\n";
audio _buffer _index += read _remain ;
break ;
}
else
2016-03-20 10:32:37 +00:00
{
2014-11-29 00:07:10 +00:00
for ( i = audio _buffer _index ; i < int _buffer . length ; i ++ )
output [ obi ++ ] = int _buffer [ i ] / 32768 ;
read _remain -= ( int _buffer . length - audio _buffer _index ) ;
audio _buffer _current _size -= audio _received [ 0 ] . length ;
/ * i f ( a u d i o _ r e c e i v e d . l e n g t h > a u d i o _ b u f f e r _ m a x i m a l _ l e n g t h )
{
add _problem ( "audio overrun" ) ;
audio _received . splice ( 0 , audio _received . length - audio _buffer _maximal _length ) ;
}
else * /
audio _received . splice ( 0 , 1 ) ;
//debug_str+="added remain, remain="+read_remain.toString()+" abi="+audio_buffer_index.toString()+" alen="+int_buffer.length.toString()+" i="+i.toString()+" arecva="+audio_received.length.toString()+" obi="+obi.toString()+"\n";
2016-03-20 10:32:37 +00:00
audio _buffer _index = 0 ;
2014-11-29 00:07:10 +00:00
if ( audio _received . length == 0 || read _remain == 0 ) return ;
int _buffer = audio _received [ 0 ] ;
}
}
//debug_str+="obi="+obi.toString();
//alert(debug_str);
}
function audio _flush _notused ( )
{
if ( audio _buffer _current _size > audio _buffer _maximal _length _sec * audio _context . sampleRate )
2016-03-20 10:32:37 +00:00
{
2014-11-29 00:07:10 +00:00
add _problem ( "audio overrun" ) ;
console . log ( "audio_flush() :: size: " + audio _buffer _current _size . toString ( ) + " allowed: " + ( audio _buffer _maximal _length _sec * audio _context . sampleRate ) . toString ( ) ) ;
while ( audio _buffer _current _size > audio _buffer _maximal _length _sec * audio _context . sampleRate * 0.5 )
{
audio _buffer _current _size -= audio _received [ 0 ] . length ;
audio _received . splice ( 0 , 1 ) ;
}
}
}
function webrx _set _param ( what , value )
{
2019-05-04 21:11:13 +00:00
params = { } ;
params [ what ] = value ;
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : params } ) ) ;
2014-11-29 00:07:10 +00:00
}
2016-08-10 09:09:43 +00:00
var starting _mute = false ;
2015-09-30 14:06:30 +00:00
function parsehash ( )
2014-11-29 00:07:10 +00:00
{
2016-03-20 10:32:37 +00:00
if ( h = window . location . hash )
2015-09-30 14:06:30 +00:00
{
h . substring ( 1 ) . split ( "," ) . forEach ( function ( x ) {
harr = x . split ( "=" ) ;
2017-05-06 19:51:03 +00:00
//console.log(harr);
2017-04-19 17:59:26 +00:00
if ( harr [ 0 ] == "mute" ) toggleMute ( ) ;
else if ( harr [ 0 ] == "mod" ) starting _mod = harr [ 1 ] ;
else if ( harr [ 0 ] == "sql" )
2019-05-05 20:15:27 +00:00
{
2017-04-19 17:59:26 +00:00
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 ;
2015-09-30 14:06:30 +00:00
}
} ) ;
2016-03-20 10:32:37 +00:00
2015-09-30 14:06:30 +00:00
}
}
2014-12-12 12:55:10 +00:00
2015-09-30 14:06:30 +00:00
function audio _preinit ( )
{
2016-03-20 10:32:37 +00:00
try
2014-11-29 00:07:10 +00:00
{
window . AudioContext = window . AudioContext || window . webkitAudioContext ;
audio _context = new AudioContext ( ) ;
}
2016-03-20 10:32:37 +00:00
catch ( e )
2014-11-29 00:07:10 +00:00
{
divlog ( 'Your browser does not support Web Audio API, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.' , 1 ) ;
2016-07-24 11:51:09 +00:00
return ;
2014-11-29 00:07:10 +00:00
}
2016-07-24 11:51:09 +00:00
if ( audio _context . sampleRate < 44100 * 2 )
audio _buffer _size = 4096 ;
else if ( audio _context . sampleRate >= 44100 * 2 && audio _context . sampleRate < 44100 * 4 )
audio _buffer _size = 4096 * 2 ;
else if ( audio _context . sampleRate > 44100 * 4 )
audio _buffer _size = 4096 * 4 ;
audio _rebuffer = new sdrjs . Rebuffer ( audio _buffer _size , sdrjs . REBUFFER _FIXED ) ;
audio _last _output _buffer = new Float32Array ( audio _buffer _size ) ;
2016-03-20 10:32:37 +00:00
2016-07-24 11:51:09 +00:00
//we send our setup packet
2015-09-30 14:06:30 +00:00
parsehash ( ) ;
audio _calculate _resampling ( audio _context . sampleRate ) ;
audio _resampler = new sdrjs . RationalResamplerFF ( audio _client _resampling _factor , 1 ) ;
2019-05-04 21:11:13 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "action" : "start" , "params" : { "output_rate" : audio _server _output _rate } } ) ) ;
2015-09-30 14:06:30 +00:00
}
function audio _init ( )
{
2018-05-07 20:42:20 +00:00
if ( is _chrome ) audio _context . resume ( )
2016-08-10 09:09:43 +00:00
if ( starting _mute ) toggleMute ( ) ;
2015-09-30 14:06:30 +00:00
if ( audio _client _resampling _factor == 0 ) return ; //if failed to find a valid resampling factor...
audio _debug _time _start = ( new Date ( ) ) . getTime ( ) ;
audio _debug _time _last _start = audio _debug _time _start ;
//https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js
audio _initialized = 1 ; // only tell on_ws_recv() not to call it again
2014-11-29 00:07:10 +00:00
//on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor
createjsnode _function = ( audio _context . createJavaScriptNode == undefined ) ? audio _context . createScriptProcessor . bind ( audio _context ) : audio _context . createJavaScriptNode . bind ( audio _context ) ;
audio _node = createjsnode _function ( audio _buffer _size , 0 , 1 ) ;
audio _node . onaudioprocess = audio _onprocess ;
audio _node . connect ( audio _context . destination ) ;
2016-03-20 10:32:37 +00:00
// --- Resampling ---
2014-11-29 00:07:10 +00:00
//https://github.com/grantgalitz/XAudioJS/blob/master/XAudioServer.js
//audio_resampler = new Resampler(audio_received_sample_rate, audio_context.sampleRate, 1, audio_buffer_size, true);
//audio_input_buffer_size = audio_buffer_size*(audio_received_sample_rate/audio_context.sampleRate);
2015-08-17 18:32:58 +00:00
webrx _set _param ( "audio_rate" , audio _context . sampleRate ) ; //Don't try to resample //TODO remove this
2014-11-29 00:07:10 +00:00
window . setInterval ( audio _flush , audio _flush _interval _ms ) ;
divlog ( 'Web Audio API succesfully initialized, sample rate: ' + audio _context . sampleRate . toString ( ) + " sps" ) ;
/ * a u d i o _ s o u r c e = a u d i o _ c o n t e x t . c r e a t e B u f f e r S o u r c e ( ) ;
audio _buffer = audio _context . createBuffer ( xhr . response , false ) ;
audio _source . buffer = buffer ;
audio _source . noteOn ( 0 ) ; * /
2015-09-30 14:06:30 +00:00
demodulator _analog _replace ( starting _mod ) ;
2016-03-20 10:32:37 +00:00
if ( starting _offset _frequency )
{
2015-09-30 14:06:30 +00:00
demodulators [ 0 ] . offset _frequency = starting _offset _frequency ;
2016-03-11 09:01:07 +00:00
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , center _freq + starting _offset _frequency , 1e6 , 4 ) ;
2015-09-30 14:06:30 +00:00
demodulators [ 0 ] . set ( ) ;
mkscale ( ) ;
}
2015-08-17 18:32:58 +00:00
//hide log panel in a second (if user has not hidden it yet)
window . setTimeout ( function ( ) {
if ( typeof e ( "openwebrx-panel-log" ) . openwebrxHidden == "undefined" && ! was _error )
{
2016-06-17 11:47:15 +00:00
toggle _panel ( "openwebrx-panel-log" ) ;
//animate(e("openwebrx-panel-log"),"opacity","",1,0,0.9,1000,60);
//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
2015-08-17 18:32:58 +00:00
}
2016-03-21 09:09:06 +00:00
} , 2000 ) ;
2016-08-10 09:09:43 +00:00
2014-11-29 00:07:10 +00:00
}
function on _ws _closed ( )
{
try
2016-03-20 10:32:37 +00:00
{
2014-11-29 00:07:10 +00:00
audio _node . disconnect ( ) ;
}
catch ( dont _care ) { }
2019-05-07 13:21:16 +00:00
divlog ( "WebSocket has closed unexpectedly. Attempting to reconnect in 5 seconds..." , 1 ) ;
setTimeout ( open _websocket , 5000 ) ;
2014-11-29 00:07:10 +00:00
}
function on _ws _error ( event )
{
divlog ( "WebSocket error." , 1 ) ;
}
2014-12-11 19:04:24 +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
2014-11-29 00:07:10 +00:00
function open _websocket ( )
{
2019-05-04 14:56:23 +00:00
ws _url = "ws://" + ( window . location . origin . split ( "://" ) [ 1 ] ) + "/ws/" ; //guess automatically -> now default behaviour
2016-03-20 10:32:37 +00:00
if ( ! ( "WebSocket" in window ) )
2014-11-29 00:07:10 +00:00
divlog ( "Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser." ) ;
2019-05-04 14:56:23 +00:00
ws = new WebSocket ( ws _url ) ;
2014-11-29 00:07:10 +00:00
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 ;
}
2016-08-12 12:51:06 +00:00
function waterfall _mkcolor ( db _value , waterfall _colors _arg )
2014-11-29 00:07:10 +00:00
{
2016-08-12 12:51:06 +00:00
if ( typeof waterfall _colors _arg === 'undefined' ) waterfall _colors _arg = waterfall _colors ;
2016-03-18 23:11:40 +00:00
if ( db _value < waterfall _min _level ) db _value = waterfall _min _level ;
if ( db _value > waterfall _max _level ) db _value = waterfall _max _level ;
full _scale = waterfall _max _level - waterfall _min _level ;
relative _value = db _value - waterfall _min _level ;
2014-11-29 00:07:10 +00:00
value _percent = relative _value / full _scale ;
2016-08-12 12:51:06 +00:00
percent _for _one _color = 1 / ( waterfall _colors _arg . length - 1 ) ;
2014-11-29 00:07:10 +00:00
index = Math . floor ( value _percent / percent _for _one _color ) ;
remain = ( value _percent - percent _for _one _color * index ) / percent _for _one _color ;
2016-08-12 12:51:06 +00:00
return color _between ( waterfall _colors _arg [ index + 1 ] , waterfall _colors _arg [ index ] , remain ) ;
2014-11-29 00:07:10 +00:00
}
function color _between ( first , second , percent )
{
output = 0 ;
for ( i = 0 ; i < 4 ; i ++ )
{
add = ( ( ( ( first & ( 0xff << ( i * 8 ) ) ) >>> 0 ) * percent ) + ( ( ( second & ( 0xff << ( i * 8 ) ) ) >>> 0 ) * ( 1 - percent ) ) ) & ( 0xff << ( i * 8 ) ) ;
output |= add >>> 0 ;
}
return output >>> 0 ;
}
var canvas _context ;
var canvases = [ ] ;
var canvas _default _height = 200 ;
var canvas _container ;
var canvas _phantom ;
function add _canvas ( )
2016-03-20 10:32:37 +00:00
{
2017-05-07 09:04:14 +00:00
var new _canvas = document . createElement ( "canvas" ) ;
2014-11-29 00:07:10 +00:00
new _canvas . width = fft _size ;
new _canvas . height = canvas _default _height ;
canvas _actual _line = canvas _default _height - 1 ;
2016-03-20 10:32:37 +00:00
new _canvas . style . width = ( canvas _container . clientWidth * zoom _levels [ zoom _level ] ) . toString ( ) + "px" ;
2014-11-29 00:07:10 +00:00
new _canvas . style . left = zoom _offset _px . toString ( ) + "px" ;
new _canvas . style . height = canvas _default _height . toString ( ) + "px" ;
2016-03-20 10:32:37 +00:00
new _canvas . openwebrx _top = ( - canvas _default _height + 1 ) ;
2014-11-29 00:07:10 +00:00
new _canvas . style . top = new _canvas . openwebrx _top . toString ( ) + "px" ;
canvas _context = new _canvas . getContext ( "2d" ) ;
canvas _container . appendChild ( new _canvas ) ;
new _canvas . addEventListener ( "mouseover" , canvas _mouseover , false ) ;
new _canvas . addEventListener ( "mouseout" , canvas _mouseout , false ) ;
new _canvas . addEventListener ( "mousemove" , canvas _mousemove , false ) ;
new _canvas . addEventListener ( "mouseup" , canvas _mouseup , false ) ;
new _canvas . addEventListener ( "mousedown" , canvas _mousedown , false ) ;
new _canvas . addEventListener ( "wheel" , canvas _mousewheel , false ) ;
canvases . push ( new _canvas ) ;
}
2016-08-07 17:03:48 +00:00
2014-11-29 00:07:10 +00:00
function init _canvas _container ( )
{
canvas _container = e ( "webrx-canvas-container" ) ;
2016-08-07 17:03:48 +00:00
mathbox _container = e ( "openwebrx-mathbox-container" ) ;
2014-11-29 00:07:10 +00:00
canvas _container . addEventListener ( "mouseout" , canvas _container _mouseout , false ) ;
//window.addEventListener("mouseout",window_mouseout,false);
//document.body.addEventListener("mouseup",body_mouseup,false);
canvas _phantom = e ( "openwebrx-phantom-canvas" ) ;
canvas _phantom . addEventListener ( "mouseover" , canvas _mouseover , false ) ;
canvas _phantom . addEventListener ( "mouseout" , canvas _mouseout , false ) ;
canvas _phantom . addEventListener ( "mousemove" , canvas _mousemove , false ) ;
canvas _phantom . addEventListener ( "mouseup" , canvas _mouseup , false ) ;
canvas _phantom . addEventListener ( "mousedown" , canvas _mousedown , false ) ;
canvas _phantom . addEventListener ( "wheel" , canvas _mousewheel , false ) ;
canvas _phantom . style . width = canvas _container . clientWidth + "px" ;
add _canvas ( ) ;
}
canvas _maxshift = 0 ;
function shift _canvases ( )
{
2016-03-20 10:32:37 +00:00
canvases . forEach ( function ( p )
2014-11-29 00:07:10 +00:00
{
p . style . top = ( p . openwebrx _top ++ ) . toString ( ) + "px" ;
} ) ;
canvas _maxshift ++ ;
if ( canvas _container . clientHeight > canvas _maxshift )
{
canvas _phantom . style . top = canvas _maxshift . toString ( ) + "px" ;
canvas _phantom . style . height = ( canvas _container . clientHeight - canvas _maxshift ) . toString ( ) + "px" ;
canvas _phantom . style . display = "block" ;
}
else
canvas _phantom . style . display = "none" ;
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
//canvas_container.style.height=(((canvases.length-1)*canvas_default_height)+(canvas_default_height-canvas_actual_line)).toString()+"px";
//canvas_container.style.height="100%";
}
function resize _canvases ( zoom )
{
if ( typeof zoom == "undefined" ) zoom = false ;
if ( ! zoom ) mkzoomlevels ( ) ;
zoom _calc ( ) ;
new _width = ( canvas _container . clientWidth * zoom _levels [ zoom _level ] ) . toString ( ) + "px" ;
var zoom _value = zoom _offset _px . toString ( ) + "px" ;
2016-03-20 10:32:37 +00:00
canvases . forEach ( function ( p )
2014-11-29 00:07:10 +00:00
{
p . style . width = new _width ;
p . style . left = zoom _value ;
} ) ;
canvas _phantom . style . width = new _width ;
canvas _phantom . style . left = zoom _value ;
}
function waterfall _init ( )
{
init _canvas _container ( ) ;
2017-05-03 14:32:47 +00:00
waterfall _timer = window . setInterval ( ( ) => { waterfall _dequeue ( ) ; secondary _demod _waterfall _dequeue ( ) ; } , 900 / fft _fps ) ;
2014-11-29 00:07:10 +00:00
resize _waterfall _container ( false ) ; /* then */ resize _canvases ( ) ;
scale _setup ( ) ;
mkzoomlevels ( ) ;
waterfall _setup _done = 1 ;
}
var waterfall _dont _scale = 0 ;
2016-08-10 11:12:02 +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 ++ ;
2016-08-10 12:54:56 +00:00
mathbox _data _global _index ++ ;
2016-08-10 11:12:02 +00:00
}
var mathbox _clear _data = function ( )
{
mathbox _data _index = 50 ;
mathbox _data _current _depth = 0 ;
}
2016-08-10 11:46:42 +00:00
//var mathbox_get_data_line = function(x) //x counts from 0 to mathbox_data_current_depth
//{
// return (mathbox_data_max_depth + mathbox_data_index - mathbox_data_current_depth + x - 1) % mathbox_data_max_depth;
//}
//
//var mathbox_data_index_valid = function(x) //x counts from 0 to mathbox_data_current_depth
//{
// return x<mathbox_data_current_depth;
//}
var mathbox _get _data _line = function ( x )
2016-08-10 11:12:02 +00:00
{
2016-08-10 11:46:42 +00:00
return ( mathbox _data _max _depth + mathbox _data _index + x - 1 ) % mathbox _data _max _depth ;
2016-08-10 11:12:02 +00:00
}
2016-08-10 11:46:42 +00:00
var mathbox _data _index _valid = function ( x )
2016-08-10 11:12:02 +00:00
{
2016-08-10 11:46:42 +00:00
return x > mathbox _data _max _depth - mathbox _data _current _depth ;
2016-08-10 11:12:02 +00:00
}
2016-08-10 11:46:42 +00:00
2014-11-29 00:07:10 +00:00
function waterfall _add ( data )
{
if ( ! waterfall _setup _done ) return ;
var w = fft _size ;
//waterfall_shift();
// ==== do scaling if required ====
/ * i f ( w a t e r f a l l _ d o n t _ s c a l e )
{
scaled = data ;
for ( i = scaled . length ; i < w ; i ++ ) scaled [ i ] = - 100 ;
}
else
{
if ( ( to - from ) == w )
{
scaled = data ;
}
else if ( ( to - from ) < w )
{ //make line bigger
pixel _per _point = w / ( to - from ) ;
scaled = Array ( ) ;
j = 0 ;
remain = pixel _per _point ;
for ( i = 0 ; i < w ; i ++ )
{
//thiscolor=data[j]*(remain-floor(remain))+data[j+1]*(1-(remain-floor(remain)))
//nextcolor=data[j+1]*(remain-floor(remain))+data[j+2]*(1-(remain-floor(remain)))
if ( remain > 1 )
{
scaled [ i ] = data [ j ] * ( remain / pixel _per _point ) + data [ j + 1 ] * ( ( 1 - remain ) / pixel _per _point ) ;
remain -- ;
}
else
{
j ++ ;
scaled [ i ] = data [ j ] * ( remain / pixel _per _point ) + data [ j + 1 ] * ( ( 1 - remain ) / pixel _per _point ) ;
remain = pixel _per _point - ( 1 - remain ) ;
}
}
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
else
{ //make line smaller (linear decimation, moving average)
point _per _pixel = ( to - from ) / w ;
scaled = Array ( ) ;
j = 0 ;
remain = point _per _pixel ;
last _pixel = 0 ;
for ( i = from ; i < to ; i ++ )
{
if ( remain > 1 )
{
2016-03-20 10:32:37 +00:00
last _pixel += data [ i ] ;
2014-11-29 00:07:10 +00:00
remain -- ;
}
else
{
last _pixel += data [ i ] * remain ;
scaled [ j ++ ] = last _pixel / point _per _pixel ;
last _pixel = data [ i ] * ( 1 - remain ) ;
remain = point _per _pixel - ( 1 - remain ) ; //?
}
}
}
}
2016-03-20 10:32:37 +00:00
//Add line to waterfall image
base = ( h - 1 ) * w * 4 ;
2014-11-29 00:07:10 +00:00
for ( x = 0 ; x < w ; x ++ )
{
color = waterfall _mkcolor ( scaled [ x ] ) ;
for ( i = 0 ; i < 4 ; i ++ )
waterfall _image . data [ base + x * 4 + i ] = ( ( color >>> 0 ) >> ( ( 3 - i ) * 8 ) ) & 0xff ;
} * /
2019-05-04 18:26:11 +00:00
if ( mathbox _mode == MATHBOX _MODES . WATERFALL ) {
2016-08-11 20:53:28 +00:00
//Handle mathbox
2016-08-10 11:12:02 +00:00
for ( var i = 0 ; i < fft _size ; i ++ ) mathbox _data [ i + mathbox _data _index * fft _size ] = data [ i ] ;
mathbox _shift ( ) ;
2019-05-04 18:26:11 +00:00
} else {
//Add line to waterfall image
oneline _image = canvas _context . createImageData ( w , 1 ) ;
for ( x = 0 ; x < w ; x ++ ) {
color = waterfall _mkcolor ( data [ x ] ) ;
for ( i = 0 ; i < 4 ; i ++ )
oneline _image . data [ x * 4 + i ] = ( ( color >>> 0 ) >> ( ( 3 - i ) * 8 ) ) & 0xff ;
}
//Draw image
canvas _context . putImageData ( oneline _image , 0 , canvas _actual _line -- ) ;
shift _canvases ( ) ;
if ( canvas _actual _line < 0 ) add _canvas ( ) ;
2016-08-11 20:53:28 +00:00
}
2016-03-20 10:32:37 +00:00
2014-11-29 00:07:10 +00:00
}
/ *
function waterfall _shift ( )
{
w = canvas . width ;
h = canvas . height ;
for ( y = 0 ; y < h - 1 ; y ++ )
{
for ( i = 0 ; i < w * 4 ; i ++ )
waterfall _image . data [ y * w * 4 + i ] = waterfall _image . data [ ( y + 1 ) * w * 4 + i ] ;
}
} * /
function check _top _bar _congestion ( )
{
2015-08-17 18:32:58 +00:00
var rmf = function ( x ) { return x . offsetLeft + x . offsetWidth ; } ;
var wet = e ( "webrx-rx-title" ) ;
var wed = e ( "webrx-rx-desc" ) ;
var rightmost = Math . max ( rmf ( wet ) , rmf ( wed ) ) ;
var tl = e ( "openwebrx-main-buttons" ) ;
2016-03-20 10:32:37 +00:00
[ wet , wed ] . map ( function ( what ) {
if ( rmf ( what ) > tl . offsetLeft - 20 ) what . style . opacity = what . style . opacity = "0" ;
2015-08-17 18:32:58 +00:00
else wet . style . opacity = wed . style . opacity = "1" ;
} ) ;
2014-11-29 00:07:10 +00:00
}
2016-08-07 17:03:48 +00:00
var MATHBOX _MODES =
{
UNINITIALIZED : 0 ,
NONE : 1 ,
WATERFALL : 2 ,
CONSTELLATION : 3
} ;
var mathbox _mode = MATHBOX _MODES . UNINITIALIZED ;
var mathbox ;
var mathbox _element ;
function mathbox _init ( )
{
2016-08-12 12:51:06 +00:00
//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
2016-08-10 11:12:02 +00:00
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 ) ;
2016-08-10 12:54:56 +00:00
mathbox _data _global _index = 0 ;
2016-08-11 20:18:46 +00:00
mathbox _correction _for _z = 0 ;
2016-08-10 11:12:02 +00:00
2016-08-07 17:03:48 +00:00
mathbox = mathBox ( {
plugins : [ 'core' , 'controls' , 'cursor' , 'stats' ] ,
controls : { klass : THREE . OrbitControls } ,
} ) ;
three = mathbox . three ;
2017-05-30 20:44:07 +00:00
if ( typeof three == "undefined" ) divlog ( "3D waterfall cannot be initialized because WebGL is not supported in your browser." , true ) ;
2016-08-07 17:03:48 +00:00
2016-08-07 17:27:46 +00:00
three . renderer . setClearColor ( new THREE . Color ( 0x808080 ) , 1.0 ) ;
2016-08-07 17:03:48 +00:00
mathbox _container . appendChild ( ( mathbox _element = three . renderer . domElement ) ) ;
view = mathbox
. set ( {
2016-08-10 08:57:35 +00:00
scale : 1080 ,
2016-08-07 17:03:48 +00:00
focus : 3 ,
} )
. camera ( {
proxy : true ,
2016-08-10 08:57:35 +00:00
position : [ - 2 , 1 , 3 ] ,
2016-08-07 17:03:48 +00:00
} )
. cartesian ( {
2016-08-10 08:57:35 +00:00
range : [ [ - 1 , 1 ] , [ 0 , 1 ] , [ 0 , 1 ] ] ,
scale : [ 2 , 2 / 3 , 1 ] ,
2016-08-07 17:03:48 +00:00
} ) ;
view . axis ( {
axis : 1 ,
width : 3 ,
2016-08-07 17:27:46 +00:00
color : "#fff" ,
2016-08-07 17:24:20 +00:00
} ) ;
2016-08-07 17:03:48 +00:00
view . axis ( {
axis : 2 ,
width : 3 ,
2016-08-07 17:27:46 +00:00
color : "#fff" ,
2016-08-10 08:57:35 +00:00
//offset: [0, 0, 0],
2016-08-07 17:24:20 +00:00
} ) ;
2016-08-07 17:03:48 +00:00
view . axis ( {
axis : 3 ,
width : 3 ,
2016-08-07 17:27:46 +00:00
color : "#fff" ,
2016-08-07 17:24:20 +00:00
} ) ;
2016-08-07 17:03:48 +00:00
view . grid ( {
width : 2 ,
opacity : 0.5 ,
axes : [ 1 , 3 ] ,
zOrder : 1 ,
2016-08-07 17:27:46 +00:00
color : "#fff" ,
2016-08-07 17:03:48 +00:00
} ) ;
2016-08-10 11:12:02 +00:00
//var remap = function (v) { return Math.sqrt(.5 + .5 * v); };
2016-08-07 17:03:48 +00:00
2016-08-11 20:18:46 +00:00
2016-08-10 12:54:56 +00:00
var remap = function ( x , z , t )
2016-08-10 11:51:04 +00:00
{
2016-08-10 12:54:56 +00:00
var currentTimePos = mathbox _data _global _index / ( fft _fps * 1.0 ) ;
2016-08-12 12:51:06 +00:00
var realZAdd = ( - ( t - currentTimePos ) / mathbox _waterfall _history _length ) ;
2016-08-11 21:22:52 +00:00
var zAdd = realZAdd - mathbox _correction _for _z ;
if ( zAdd < - 0.2 || zAdd > 0.2 ) { mathbox _correction _for _z = realZAdd ; }
2016-08-11 20:18:46 +00:00
2016-08-10 11:46:42 +00:00
var xIndex = Math . trunc ( ( ( x + 1 ) / 2.0 ) * fft _size ) ; //x: frequency
var zIndex = Math . trunc ( z * ( mathbox _data _max _depth - 1 ) ) ; //z: time
2016-08-10 11:12:02 +00:00
var realZIndex = mathbox _get _data _line ( zIndex ) ;
2016-08-10 12:54:56 +00:00
if ( ! mathbox _data _index _valid ( zIndex ) ) return { y : undefined , dBValue : undefined , zAdd : 0 } ;
2016-08-10 11:46:42 +00:00
//if(realZIndex>=(mathbox_data_max_depth-1)) console.log("realZIndexundef", realZIndex, zIndex);
2016-08-10 11:12:02 +00:00
var index = Math . trunc ( xIndex + realZIndex * fft _size ) ;
2016-08-10 11:46:42 +00:00
/ * i f ( m a t h b o x _ d a t a [ i n d e x ] = = u n d e f i n e d ) c o n s o l e . l o g ( " U n d e f " , i n d e x , m a t h b o x _ d a t a . l e n g t h , z I n d e x ,
2016-08-10 11:12:02 +00:00
realZIndex , mathbox _data _max _depth ,
2016-08-10 11:46:42 +00:00
mathbox _data _current _depth , mathbox _data _index ) ; * /
2016-08-10 11:51:04 +00:00
var dBValue = mathbox _data [ index ] ;
2016-08-10 11:46:42 +00:00
//y=1;
2016-08-10 11:12:02 +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 ) ;
2016-08-10 11:48:43 +00:00
mathbox _dbg = { dbv : dBValue , indexval : index , mbd : mathbox _data . length , yval : y } ;
2016-08-10 11:51:04 +00:00
if ( ! y ) y = 0 ;
2016-08-11 20:07:22 +00:00
return { y : y , dBValue : dBValue , zAdd : zAdd } ;
2016-08-10 11:51:04 +00:00
}
var points = view . area ( {
expr : function ( emit , x , z , i , j , t ) {
var y ;
2016-08-10 12:54:56 +00:00
remapResult = remap ( x , z , t ) ;
if ( ( y = remapResult . y ) == undefined ) return ;
emit ( x , y , z + remapResult . zAdd ) ;
2016-08-07 17:03:48 +00:00
} ,
2016-08-12 12:51:06 +00:00
width : mathbox _waterfall _frequency _resolution ,
2016-08-11 20:03:29 +00:00
height : mathbox _data _max _depth - 1 ,
2016-08-07 17:03:48 +00:00
channels : 3 ,
axes : [ 1 , 3 ] ,
} ) ;
var colors = view . area ( {
expr : function ( emit , x , z , i , j , t ) {
2016-08-10 12:32:23 +00:00
var dBValue ;
2016-08-10 12:54:56 +00:00
if ( ( dBValue = remap ( x , z , t ) . dBValue ) == undefined ) return ;
2016-08-12 12:51:06 +00:00
var color = waterfall _mkcolor ( dBValue , mathbox _waterfall _colors ) ;
2016-08-10 12:32:23 +00:00
var b = ( color & 0xff ) / 255.0 ;
var g = ( ( color & 0xff00 ) >> 8 ) / 255.0 ;
var r = ( ( color & 0xff0000 ) >> 16 ) / 255.0 ;
2016-08-07 17:03:48 +00:00
emit ( r , g , b , 1.0 ) ;
} ,
2016-08-12 12:51:06 +00:00
width : mathbox _waterfall _frequency _resolution ,
2016-08-11 20:03:29 +00:00
height : mathbox _data _max _depth - 1 ,
2016-08-07 17:03:48 +00:00
channels : 4 ,
axes : [ 1 , 3 ] ,
} ) ;
view . surface ( {
shaded : true ,
points : '<<' ,
colors : '<' ,
color : 0xFFFFFF ,
} ) ;
view . surface ( {
fill : false ,
2016-08-07 17:24:20 +00:00
lineX : false ,
lineY : false ,
2016-08-07 17:03:48 +00:00
points : '<<' ,
colors : '<' ,
color : 0xFFFFFF ,
width : 2 ,
blending : 'add' ,
opacity : . 25 ,
zBias : 5 ,
} ) ;
mathbox _mode = MATHBOX _MODES . NONE ;
//mathbox_element.style.width="100%";
//mathbox_element.style.height="100%";
}
function mathbox _toggle ( )
{
2016-08-11 20:57:46 +00:00
2016-08-07 17:03:48 +00:00
if ( mathbox _mode == MATHBOX _MODES . UNINITIALIZED ) mathbox _init ( ) ;
mathbox _mode = ( mathbox _mode == MATHBOX _MODES . NONE ) ? MATHBOX _MODES . WATERFALL : MATHBOX _MODES . NONE ;
2016-08-11 20:57:46 +00:00
mathbox _container . style . display = ( mathbox _mode == MATHBOX _MODES . WATERFALL ) ? "block" : "none" ;
2016-08-10 11:12:02 +00:00
mathbox _clear _data ( ) ;
2016-08-11 20:53:28 +00:00
waterfall _clear ( ) ;
}
function waterfall _clear ( )
{
while ( canvases . length ) //delete all canvases
{
var x = canvases . shift ( ) ;
x . parentNode . removeChild ( x ) ;
delete x ;
}
add _canvas ( ) ;
2016-08-07 17:03:48 +00:00
}
2016-03-20 10:32:37 +00:00
function openwebrx _resize ( )
2014-11-29 00:07:10 +00:00
{
resize _canvases ( ) ;
resize _waterfall _container ( true ) ;
resize _scale ( ) ;
check _top _bar _congestion ( ) ;
}
function openwebrx _init ( )
{
2018-05-07 20:42:20 +00:00
if ( ios || is _chrome ) e ( "openwebrx-big-grey" ) . style . display = "table-cell" ;
2016-03-26 23:47:26 +00:00
( opb = e ( "openwebrx-play-button-text" ) ) . style . marginTop = ( window . innerHeight / 2 - opb . clientHeight / 2 ) . toString ( ) + "px" ;
2014-11-29 00:07:10 +00:00
init _rx _photo ( ) ;
open _websocket ( ) ;
2017-05-03 14:32:47 +00:00
secondary _demod _init ( ) ;
2016-06-17 11:47:15 +00:00
place _panels ( first _show _panel ) ;
2014-11-29 00:07:10 +00:00
window . setTimeout ( function ( ) { window . setInterval ( debug _audio , 1000 ) ; } , 1000 ) ;
window . addEventListener ( "resize" , openwebrx _resize ) ;
2015-08-17 18:32:58 +00:00
check _top _bar _congestion ( ) ;
2016-02-06 13:49:10 +00:00
//Synchronise volume with slider
updateVolume ( ) ;
2019-05-04 14:56:23 +00:00
2014-11-29 00:07:10 +00:00
}
2016-03-26 23:47:26 +00:00
function iosPlayButtonClick ( )
{
//On iOS, we can only start audio from a click or touch event.
audio _init ( ) ;
e ( "openwebrx-big-grey" ) . style . opacity = 0 ;
window . setTimeout ( function ( ) { e ( "openwebrx-big-grey" ) . style . display = "none" ; } , 1100 ) ;
2019-05-07 13:21:16 +00:00
audio _allowed = 1 ;
2016-03-26 23:47:26 +00:00
}
2014-11-29 00:07:10 +00:00
/ *
2016-03-20 10:32:37 +00:00
window . setInterval ( function ( ) {
2014-11-29 00:07:10 +00:00
sum = 0 ;
for ( i = 0 ; i < audio _received . length ; i ++ )
sum += audio _received [ i ] . length ;
divlog ( "audio buffer bytes: " + sum ) ;
} , 2000 ) ; * /
/ * f u n c t i o n e m a i l ( w h a t )
{
//| http://stackoverflow.com/questions/617647/where-is-my-one-line-implementation-of-rot13-in-javascript-going-wrong
what = what . replace ( /[a-zA-Z]/g , function ( c ) { return String . fromCharCode ( ( c <= "Z" ? 90 : 122 ) >= ( c = c . charCodeAt ( 0 ) + 13 ) ? c : c - 26 ) ; } ) ;
window . location . href = "mailto:" + what ;
} * /
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 ) ; } ) ; }
var irt = function ( s , n ) { return s . replace ( /[a-zA-Z]/g , function ( c ) { return String . fromCharCode ( ( c >= "a" ? 97 : 65 ) <= ( c = c . charCodeAt ( 0 ) - n ) ? c : c + 26 ) ; } ) ; }
var sendmail2 = function ( s ) { window . location . href = "mailto:" + irt ( s . replace ( "=" , String . fromCharCode ( 0100 ) ) . replace ( "$" , "." ) , 8 ) ; }
2014-12-12 12:55:10 +00:00
var audio _debug _time _start = 0 ;
var audio _debug _time _last _start = 0 ;
2014-11-29 00:07:10 +00:00
function debug _audio ( )
{
2014-12-12 12:55:10 +00:00
if ( audio _debug _time _start == 0 ) return ; //audio_init has not been called
time _now = ( new Date ( ) ) . getTime ( ) ;
audio _debug _time _since _last _call = ( time _now - audio _debug _time _last _start ) / 1000 ;
audio _debug _time _last _start = time _now ; //now
audio _debug _time _taken = ( time _now - audio _debug _time _start ) / 1000 ;
2015-08-17 18:32:58 +00:00
kbps _mult = ( audio _compression == "adpcm" ) ? 8 : 16 ;
//e("openwebrx-audio-sps").innerHTML=
// ((audio_compression=="adpcm")?"ADPCM compressed":"uncompressed")+" audio downlink:<br/> "+(audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call).toFixed(0)+" kbps ("+
// (audio_buffer_all_size_debug*kbps_mult/audio_debug_time_taken).toFixed(1)+" kbps avg.), feed at "+
// ((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
var audio _speed _value = audio _buffer _current _size _debug * kbps _mult / audio _debug _time _since _last _call ;
2016-03-20 10:32:37 +00:00
progressbar _set ( e ( "openwebrx-bar-audio-speed" ) , audio _speed _value / 500000 , "Audio stream [" + ( audio _speed _value / 1000 ) . toFixed ( 0 ) + " kbps]" , false ) ;
2015-08-17 18:32:58 +00:00
var audio _output _value = ( audio _buffer _current _count _debug * audio _buffer _size ) / audio _debug _time _taken ;
2016-03-20 10:32:37 +00:00
progressbar _set ( e ( "openwebrx-bar-audio-output" ) , audio _output _value / 55000 , "Audio output [" + ( audio _output _value / 1000 ) . toFixed ( 1 ) + " ksps]" , audio _output _value > 55000 || audio _output _value < 10000 ) ;
2015-08-17 18:32:58 +00:00
audio _buffer _progressbar _update ( ) ;
var network _speed _value = debug _ws _data _received / audio _debug _time _taken ;
2016-03-20 10:32:37 +00:00
progressbar _set ( e ( "openwebrx-bar-network-speed" ) , network _speed _value * 8 / 2000 , "Network usage [" + ( network _speed _value * 8 ) . toFixed ( 1 ) + " kbps]" , false ) ;
2015-08-17 18:32:58 +00:00
2014-11-29 00:07:10 +00:00
audio _buffer _current _size _debug = 0 ;
2015-08-17 18:32:58 +00:00
if ( waterfall _measure _minmax ) waterfall _measure _minmax _print ( ) ;
2014-11-29 00:07:10 +00:00
}
// ========================================================
// ======================= PANELS =======================
// ========================================================
2015-08-17 18:32:58 +00:00
panel _margin = 5.9 ;
2014-11-29 00:07:10 +00:00
function pop _bottommost _panel ( from )
{
min _order = parseInt ( from [ 0 ] . dataset . panelOrder ) ;
min _index = 0 ;
2016-03-20 10:32:37 +00:00
for ( i = 0 ; i < from . length ; i ++ )
2014-11-29 00:07:10 +00:00
{
actual _order = parseInt ( from [ i ] . dataset . panelOrder ) ;
2016-03-20 10:32:37 +00:00
if ( actual _order < min _order )
2014-11-29 00:07:10 +00:00
{
min _index = i ;
min _order = actual _order ;
}
}
to _return = from [ min _index ] ;
from . splice ( min _index , 1 ) ;
return to _return ;
}
2017-05-03 14:32:47 +00:00
function toggle _panel ( what , on )
2015-08-17 18:32:58 +00:00
{
2017-05-03 14:32:47 +00:00
var item = e ( what ) ;
if ( typeof on !== "undefined" )
{
if ( item . openwebrxHidden && ! on ) return ;
if ( ! item . openwebrxHidden && on ) return ;
}
2015-08-17 18:32:58 +00:00
if ( item . openwebrxDisableClick ) return ;
2016-06-17 11:47:15 +00:00
item . style . transitionDuration = "599ms" ;
item . style . transitionDelay = "0ms" ;
if ( ! item . openwebrxHidden )
{
window . setTimeout ( function ( ) { item . openwebrxHidden = ! item . openwebrxHidden ; place _panels ( ) ; item . openwebrxDisableClick = false ; } , 700 ) ;
item . style . transform = "perspective( 599px ) rotateX( 90deg )" ;
}
else
{
item . openwebrxHidden = ! item . openwebrxHidden ; place _panels ( ) ;
window . setTimeout ( function ( ) { item . openwebrxDisableClick = false ; } , 700 ) ;
item . style . transform = "perspective( 599px ) rotateX( 0deg )" ;
}
item . style . transitionDuration = "0" ;
2015-08-17 18:32:58 +00:00
item . openwebrxDisableClick = true ;
2016-06-17 11:47:15 +00:00
2015-08-17 18:32:58 +00:00
}
2016-06-17 11:47:15 +00:00
function first _show _panel ( panel )
2014-11-29 00:07:10 +00:00
{
2016-06-17 11:47:15 +00:00
panel . style . transitionDuration = 0 ;
panel . style . transitionDelay = 0 ;
rotx = ( Math . random ( ) > 0.5 ) ? - 90 : 90 ;
roty = 0 ;
if ( Math . random ( ) > 0.5 )
{
rottemp = rotx ;
rotx = roty ;
roty = rottemp ;
}
if ( rotx != 0 && Math . random ( ) > 0.5 ) rotx = 270 ;
2016-06-17 19:27:17 +00:00
//console.log(rotx,roty);
2016-06-17 11:47:15 +00:00
transformString = "perspective( 599px ) rotateX( %1deg ) rotateY( %2deg )"
. replace ( "%1" , rotx . toString ( ) ) . replace ( "%2" , roty . toString ( ) ) ;
2016-06-17 19:27:17 +00:00
//console.log(transformString);
//console.log(panel);
2016-06-17 11:47:15 +00:00
panel . style . transform = transformString ;
window . setTimeout ( function ( ) {
panel . style . transitionDuration = "599ms" ;
panel . style . transitionDelay = ( Math . floor ( Math . random ( ) * 500 ) ) . toString ( ) + "ms" ;
panel . style . transform = "perspective( 599px ) rotateX( 0deg ) rotateY( 0deg )" ;
//panel.style.transitionDuration="0ms";
//panel.style.transitionDelay="0";
} , 1 ) ;
}
function place _panels ( function _apply )
{
if ( function _apply == undefined ) function _apply = function ( x ) { } ;
2015-08-17 18:32:58 +00:00
var hoffset = 0 ; //added this because the first panel should not have such great gap below
2014-11-29 00:07:10 +00:00
var left _col = [ ] ;
var right _col = [ ] ;
var plist = e ( "openwebrx-panels-container" ) . children ;
for ( i = 0 ; i < plist . length ; i ++ )
{
c = plist [ i ] ;
if ( c . className == "openwebrx-panel" )
{
2016-03-20 10:32:37 +00:00
if ( c . openwebrxHidden )
2015-08-17 18:32:58 +00:00
{
c . style . display = "none" ;
continue ;
}
c . style . display = "block" ;
c . openwebrxPanelTransparent = ( ! ! c . dataset . panelTransparent ) ;
2014-11-29 00:07:10 +00:00
newSize = c . dataset . panelSize . split ( "," ) ;
if ( c . dataset . panelPos == "left" ) { left _col . push ( c ) ; }
else if ( c . dataset . panelPos == "right" ) { right _col . push ( c ) ; }
c . style . width = newSize [ 0 ] + "px" ;
2016-02-06 16:42:35 +00:00
//c.style.height=newSize[1]+"px";
2015-08-17 18:32:58 +00:00
if ( ! c . openwebrxPanelTransparent ) c . style . margin = panel _margin . toString ( ) + "px" ;
else c . style . marginLeft = panel _margin . toString ( ) + "px" ;
2016-03-20 10:32:37 +00:00
c . openwebrxPanelWidth = parseInt ( newSize [ 0 ] ) ;
2014-11-29 00:07:10 +00:00
c . openwebrxPanelHeight = parseInt ( newSize [ 1 ] ) ;
}
}
2016-02-06 16:42:35 +00:00
2015-08-17 18:32:58 +00:00
y = hoffset ; //was y=0 before hoffset
2014-11-29 00:07:10 +00:00
while ( left _col . length > 0 )
{
p = pop _bottommost _panel ( left _col ) ;
p . style . left = "0px" ;
p . style . bottom = y . toString ( ) + "px" ;
p . style . visibility = "visible" ;
2015-08-17 18:32:58 +00:00
y += p . openwebrxPanelHeight + ( ( p . openwebrxPanelTransparent ) ? 0 : 3 ) * panel _margin ;
2016-06-17 11:47:15 +00:00
if ( function _apply ) function _apply ( p ) ;
2017-05-03 14:32:47 +00:00
//console.log(p.id, y, p.openwebrxPanelTransparent);
2014-11-29 00:07:10 +00:00
}
2015-08-17 18:32:58 +00:00
y = hoffset ;
2014-11-29 00:07:10 +00:00
while ( right _col . length > 0 )
{
p = pop _bottommost _panel ( right _col ) ;
2015-08-17 18:32:58 +00:00
p . style . right = ( e ( "webrx-canvas-container" ) . offsetWidth - e ( "webrx-canvas-container" ) . clientWidth ) . toString ( ) + "px" ; //get scrollbar width
2014-11-29 00:07:10 +00:00
p . style . bottom = y . toString ( ) + "px" ;
p . style . visibility = "visible" ;
2017-05-03 14:32:47 +00:00
y += p . openwebrxPanelHeight + ( ( p . openwebrxPanelTransparent ) ? 0 : 3 ) * panel _margin ;
2016-06-17 11:47:15 +00:00
if ( function _apply ) function _apply ( p ) ;
2015-08-17 18:32:58 +00:00
}
}
function progressbar _set ( obj , val , text , over )
{
if ( val < 0.05 ) val = 0 ;
if ( val > 1 ) val = 1 ;
var innerBar = null ;
var innerText = null ;
2016-03-20 10:32:37 +00:00
for ( var i = 0 ; i < obj . children . length ; i ++ )
2015-08-17 18:32:58 +00:00
{
if ( obj . children [ i ] . className == "openwebrx-progressbar-text" ) innerText = obj . children [ i ] ;
else if ( obj . children [ i ] . className == "openwebrx-progressbar-bar" ) innerBar = obj . children [ i ] ;
2014-11-29 00:07:10 +00:00
}
2015-08-17 18:32:58 +00:00
if ( innerBar == null ) return ;
//.h: function animate(object,style_name,unit,from,to,accel,time_ms,fps,to_exec)
animate ( innerBar , "width" , "px" , innerBar . clientWidth , val * obj . clientWidth , 0.7 , 700 , 60 ) ;
//innerBar.style.width=(val*100).toFixed(0)+"%";
innerBar . style . backgroundColor = ( over ) ? "#ff6262" : "#00aba6" ;
if ( innerText == null ) return ;
innerText . innerHTML = text ;
2014-11-29 00:07:10 +00:00
}
2017-04-19 17:59:26 +00:00
function demodulator _buttons _update ( )
{
$ ( ".openwebrx-demodulator-button" ) . removeClass ( "highlighted" ) ;
2017-05-03 14:32:47 +00:00
if ( secondary _demod ) $ ( "#openwebrx-button-dig" ) . addClass ( "highlighted" ) ;
else switch ( demodulators [ 0 ] . subtype )
2017-04-19 17:59:26 +00:00
{
case "nfm" :
$ ( "#openwebrx-button-nfm" ) . addClass ( "highlighted" ) ;
break ;
case "am" :
$ ( "#openwebrx-button-am" ) . addClass ( "highlighted" ) ;
break ;
case "lsb" :
case "usb" :
case "cw" :
if ( demodulators [ 0 ] . high _cut - demodulators [ 0 ] . low _cut < 300 )
$ ( "#openwebrx-button-cw" ) . addClass ( "highlighted" ) ;
else
{
if ( demodulators [ 0 ] . high _cut < 0 )
$ ( "#openwebrx-button-lsb" ) . addClass ( "highlighted" ) ;
else if ( demodulators [ 0 ] . low _cut > 0 )
$ ( "#openwebrx-button-usb" ) . addClass ( "highlighted" ) ;
else $ ( "#openwebrx-button-lsb, #openwebrx-button-usb" ) . addClass ( "highlighted" ) ;
}
break ;
}
}
2017-05-02 22:32:08 +00:00
function demodulator _analog _replace _last ( ) { demodulator _analog _replace ( last _analog _demodulator _subtype ) ; }
/ *
_ _ _ _ _ _ _ _
| _ _ \ ( _ ) ( _ ) | |
| | | | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ | | _ _ _ _ _ _
| | | | | / _` | | '_ ` _ \ / _ \ / _ ` |/ _ \/ __|
| | _ _ | | | ( _ | | | | | | | | ( _ ) | ( _ | | _ _ / \ _ _ \
| _ _ _ _ _ / | _ | \ _ _ , | _ | _ | | _ | | _ | \ _ _ _ / \ _ _ , _ | \ _ _ _ || _ _ _ /
_ _ / |
| _ _ _ /
* /
secondary _demod = false ;
secondary _demod _offset _freq = 0 ;
2017-05-03 14:32:47 +00:00
secondary _demod _waterfall _queue = [ ] ;
2017-05-02 22:32:08 +00:00
2017-05-03 14:32:47 +00:00
function demodulator _digital _replace _last ( )
{
demodulator _digital _replace ( last _digital _demodulator _subtype ) ;
secondary _demod _listbox _update ( ) ;
}
2017-05-02 22:32:08 +00:00
function demodulator _digital _replace ( subtype )
{
switch ( subtype )
{
case "bpsk31" :
case "rtty" :
secondary _demod _start ( subtype ) ;
2017-05-03 14:32:47 +00:00
demodulator _analog _replace ( 'usb' , true ) ;
demodulator _buttons _update ( ) ;
2017-05-02 22:32:08 +00:00
break ;
}
2017-05-03 14:32:47 +00:00
toggle _panel ( "openwebrx-panel-digimodes" , true ) ;
}
2017-05-06 19:51:03 +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" ;
console . log ( new _canvas . width , new _canvas . height , new _canvas . style . width , new _canvas . style . height ) ;
2017-05-07 10:03:18 +00:00
secondary _demod _current _canvas _actual _line = new _canvas . height - 1 ;
2017-05-07 14:30:41 +00:00
$ ( secondary _demod _canvas _container ) . children ( ) . last ( ) . before ( new _canvas ) ;
2017-05-06 19:51:03 +00:00
return new _canvas ;
}
function secondary _demod _remove _canvases ( )
{
2017-05-07 14:30:41 +00:00
$ ( secondary _demod _canvas _container ) . children ( "canvas" ) . remove ( ) ;
2017-05-06 19:51:03 +00:00
}
function secondary _demod _init _canvases ( )
{
secondary _demod _remove _canvases ( ) ;
secondary _demod _canvases = [ ] ;
secondary _demod _canvases . push ( secondary _demod _create _canvas ( ) ) ;
secondary _demod _canvases . push ( secondary _demod _create _canvas ( ) ) ;
secondary _demod _canvases [ 0 ] . openwebrx _top = - $ ( secondary _demod _canvas _container ) . height ( ) ;
secondary _demod _canvases [ 1 ] . openwebrx _top = 0 ;
secondary _demod _canvases _update _top ( ) ;
secondary _demod _current _canvas _context = secondary _demod _canvases [ 0 ] . getContext ( "2d" ) ;
secondary _demod _current _canvas _actual _line = $ ( secondary _demod _canvas _container ) . height ( ) - 1 ;
secondary _demod _current _canvas _index = 0 ;
secondary _demod _canvases _initialized = true ;
2017-05-23 09:16:57 +00:00
//secondary_demod_update_channel_freq_from_event();
mkscale ( ) ; //so that the secondary waterfall zoom level will be initialized
2017-05-06 19:51:03 +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" ;
}
function secondary _demod _swap _canvases ( )
{
2017-05-07 09:04:14 +00:00
console . log ( "swap" ) ;
secondary _demod _canvases [ 0 + ! secondary _demod _current _canvas _index ] . openwebrx _top -= $ ( secondary _demod _canvas _container ) . height ( ) * 2 ;
2017-05-06 19:51:03 +00:00
secondary _demod _current _canvas _index = 0 + ! secondary _demod _current _canvas _index ;
secondary _demod _current _canvas _context = secondary _demod _canvases [ secondary _demod _current _canvas _index ] . getContext ( "2d" ) ;
secondary _demod _current _canvas _actual _line = $ ( secondary _demod _canvas _container ) . height ( ) - 1 ;
}
2017-05-03 14:32:47 +00:00
function secondary _demod _init ( )
{
$ ( "#openwebrx-panel-digimodes" ) [ 0 ] . openwebrxHidden = true ;
2017-05-06 19:51:03 +00:00
secondary _demod _canvas _container = $ ( "#openwebrx-digimode-canvas-container" ) [ 0 ] ;
2017-05-07 14:30:41 +00:00
$ ( secondary _demod _canvas _container )
. mousemove ( secondary _demod _canvas _container _mousemove )
. mouseup ( secondary _demod _canvas _container _mouseup )
. mousedown ( secondary _demod _canvas _container _mousedown )
. mouseenter ( secondary _demod _canvas _container _mousein )
. mouseleave ( secondary _demod _canvas _container _mouseout ) ;
2017-05-02 22:32:08 +00:00
}
function secondary _demod _start ( subtype )
{
2017-05-06 19:51:03 +00:00
secondary _demod _canvases _initialized = false ;
2019-05-05 18:36:50 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_mod" : subtype } } ) ) ;
secondary _demod = subtype ;
2017-05-02 22:32:08 +00:00
}
function secondary _demod _set ( )
{
2019-05-05 18:36:50 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_offset_freq" : secondary _demod _offset _freq } } ) ) ;
2017-05-02 22:32:08 +00:00
}
function secondary _demod _stop ( )
{
2019-05-05 18:36:50 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_mod" : false } } ) ) ;
secondary _demod = false ;
2017-05-03 14:32:47 +00:00
secondary _demod _waterfall _queue = [ ] ;
2017-05-02 22:32:08 +00:00
}
2017-05-03 14:32:47 +00:00
function secondary _demod _waterfall _add _queue ( x )
2017-05-02 22:32:08 +00:00
{
2017-05-06 19:51:03 +00:00
secondary _demod _waterfall _queue . push ( x ) ;
2017-05-02 22:32:08 +00:00
}
2017-05-06 14:15:32 +00:00
function secondary _demod _push _binary _data ( x )
{
secondary _demod _push _data ( Array . from ( x ) . map ( y => ( y ) ? "1" : "0" ) . join ( "" ) ) ;
}
2017-05-02 22:32:08 +00:00
function secondary _demod _push _data ( x )
{
2017-05-07 16:12:43 +00:00
x = Array . from ( x ) . map ( ( y ) => {
var c = y . charCodeAt ( 0 ) ;
2017-06-28 20:32:19 +00:00
if ( y == "\r" ) return " " ;
if ( y == "\n" ) return " " ;
//if(y=="\n") return "<br />";
2017-05-07 16:12:43 +00:00
if ( c < 32 || c > 126 ) return "" ;
if ( y == "&" ) return "&" ;
if ( y == "<" ) return "<" ;
if ( y == ">" ) return ">" ;
2017-05-07 21:45:53 +00:00
if ( y == " " ) return " " ;
2017-05-07 16:12:43 +00:00
return y ;
} ) . join ( "" ) ;
2017-05-02 22:32:08 +00:00
$ ( "#openwebrx-cursor-blink" ) . before ( "<span class=\"part\"><span class=\"subpart\">" + x + "</span></span>" ) ;
}
2017-05-06 14:15:32 +00:00
function secondary _demod _data _clear ( )
{
$ ( "#openwebrx-cursor-blink" ) . prevAll ( ) . remove ( ) ;
}
2017-05-03 14:32:47 +00:00
function secondary _demod _close _window ( )
{
secondary _demod _stop ( ) ;
toggle _panel ( "openwebrx-panel-digimodes" , false ) ;
}
2017-05-02 22:32:08 +00:00
2017-05-07 09:04:14 +00:00
secondary _demod _fft _offset _db = 30 ; //need to calculate that later
2017-05-06 19:51:03 +00:00
function secondary _demod _waterfall _add ( data )
2017-05-03 14:32:47 +00:00
{
2017-05-06 19:51:03 +00:00
if ( ! secondary _demod ) return ;
var w = secondary _fft _size ;
//Add line to waterfall image
var oneline _image = secondary _demod _current _canvas _context . createImageData ( w , 1 ) ;
for ( x = 0 ; x < w ; x ++ )
{
2017-05-07 09:04:14 +00:00
var color = waterfall _mkcolor ( data [ x ] + secondary _demod _fft _offset _db ) ;
2017-05-06 19:51:03 +00:00
for ( i = 0 ; i < 4 ; i ++ ) oneline _image . data [ x * 4 + i ] = ( ( color >>> 0 ) >> ( ( 3 - i ) * 8 ) ) & 0xff ;
}
//Draw image
secondary _demod _current _canvas _context . putImageData ( oneline _image , 0 , secondary _demod _current _canvas _actual _line -- ) ;
secondary _demod _canvases . map ( ( x ) => { x . openwebrx _top += 1 ; } ) ;
secondary _demod _canvases _update _top ( ) ;
if ( secondary _demod _current _canvas _actual _line < 0 ) secondary _demod _swap _canvases ( ) ;
2017-05-03 14:32:47 +00:00
}
2017-05-02 22:32:08 +00:00
2017-05-06 19:51:03 +00:00
var secondary _demod _canvases _initialized = false ;
2017-05-03 14:32:47 +00:00
function secondary _demod _waterfall _dequeue ( )
{
2017-05-06 19:51:03 +00:00
if ( ! secondary _demod || ! secondary _demod _canvases _initialized ) return ;
if ( secondary _demod _waterfall _queue . length ) secondary _demod _waterfall _add ( secondary _demod _waterfall _queue . shift ( ) ) ;
2017-05-03 14:32:47 +00:00
if ( secondary _demod _waterfall _queue . length > Math . max ( fft _fps / 2 , 20 ) ) //in case of fft overflow
{
console . log ( "secondary waterfall overflow, queue length:" , secondary _demod _waterfall _queue . length ) ;
while ( secondary _demod _waterfall _queue . length ) secondary _demod _waterfall _add ( secondary _demod _waterfall _queue . shift ( ) ) ;
}
}
secondary _demod _listbox _updating = false ;
function secondary _demod _listbox _changed ( )
{
if ( secondary _demod _listbox _updating ) return ;
switch ( $ ( "#openwebrx-secondary-demod-listbox" ) [ 0 ] . value )
{
case "none" :
demodulator _analog _replace _last ( ) ;
break ;
case "bpsk31" :
demodulator _digital _replace ( 'bpsk31' ) ;
break ;
case "rtty" :
demodulator _digital _replace ( 'rtty' ) ;
break ;
}
}
function secondary _demod _listbox _update ( )
2017-05-02 22:32:08 +00:00
{
2017-05-03 14:32:47 +00:00
secondary _demod _listbox _updating = true ;
$ ( "#openwebrx-secondary-demod-listbox" ) . val ( ( secondary _demod ) ? secondary _demod : "none" ) ;
console . log ( "update" ) ;
secondary _demod _listbox _updating = false ;
2017-05-02 22:32:08 +00:00
}
2017-05-07 14:30:41 +00:00
secondary _demod _channel _freq = 1000 ;
function secondary _demod _update _marker ( )
{
2017-05-23 09:16:57 +00:00
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 ;
2017-05-07 16:12:43 +00:00
//console.log("sdum", width, left);
2017-05-07 14:30:41 +00:00
$ ( "#openwebrx-digimode-select-channel" ) . width ( width ) . css ( "left" , left + "px" )
}
2017-05-07 16:12:43 +00:00
secondary _demod _waiting _for _set = false ;
2017-05-07 14:30:41 +00:00
function secondary _demod _update _channel _freq _from _event ( evt )
{
2017-05-07 16:12:43 +00:00
if ( typeof evt !== "undefined" )
{
var relativeX = ( evt . offsetX ) ? evt . offsetX : evt . layerX ;
2017-05-25 10:07:10 +00:00
secondary _demod _channel _freq = secondary _demod _low _cut +
( relativeX / $ ( secondary _demod _canvas _container ) . width ( ) ) * ( secondary _demod _high _cut - secondary _demod _low _cut ) ;
2017-05-07 16:12:43 +00:00
}
//console.log("toset:", secondary_demod_channel_freq);
if ( ! secondary _demod _waiting _for _set )
{
secondary _demod _waiting _for _set = true ;
window . setTimeout ( ( ) => {
2019-05-05 18:36:50 +00:00
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : { "secondary_offset_freq" : Math . floor ( secondary _demod _channel _freq ) } } ) ) ;
2017-05-07 16:12:43 +00:00
//console.log("doneset:", secondary_demod_channel_freq);
secondary _demod _waiting _for _set = false ;
} , 50 ) ;
}
2017-05-07 14:30:41 +00:00
secondary _demod _update _marker ( ) ;
}
secondary _demod _mousedown = false ;
function secondary _demod _canvas _container _mousein ( )
{
2017-05-07 16:12:43 +00:00
$ ( "#openwebrx-digimode-select-channel" ) . css ( "opacity" , "0.7" ) ; //.css("border-width", "1px");
2017-05-07 14:30:41 +00:00
}
function secondary _demod _canvas _container _mouseout ( )
{
$ ( "#openwebrx-digimode-select-channel" ) . css ( "opacity" , "0" ) ;
}
function secondary _demod _canvas _container _mousemove ( evt )
{
if ( secondary _demod _mousedown ) secondary _demod _update _channel _freq _from _event ( evt ) ;
}
function secondary _demod _canvas _container _mousedown ( evt )
{
if ( evt . which == 1 ) secondary _demod _mousedown = true ;
}
function secondary _demod _canvas _container _mouseup ( evt )
{
if ( evt . which == 1 ) secondary _demod _mousedown = false ;
secondary _demod _update _channel _freq _from _event ( evt ) ;
}
2017-05-22 06:37:14 +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 )
{
2017-05-30 20:44:07 +00:00
var hctmp = high _cut ;
var lctmp = low _cut ;
low _cut = - hctmp ;
low _cut = - lctmp ;
2017-05-22 06:37:14 +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 ;
}
2017-05-23 09:16:57 +00:00
secondary _demod _low _cut = low _cut ;
secondary _demod _high _cut = high _cut ;
2017-05-22 06:37:14 +00:00
var shown _bw = high _cut - low _cut ;
2017-05-23 09:16:57 +00:00
secondary _demod _canvas _width = $ ( secondary _demod _canvas _container ) . width ( ) * ( if _samp _rate / 2 ) / shown _bw ;
secondary _demod _canvas _left = - secondary _demod _canvas _width * ( low _cut / ( if _samp _rate / 2 ) ) ;
//console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
secondary _demod _canvases . map ( ( x ) => { $ ( x ) . css ( "left" , secondary _demod _canvas _left + "px" ) . css ( "width" , secondary _demod _canvas _width + "px" ) ; } ) ;
secondary _demod _update _channel _freq _from _event ( ) ;
2017-05-22 06:37:14 +00:00
}