2014-11-29 00:07:10 +00:00
/ *
OpenWebRX ( c ) Copyright 2013 - 2014 Andras Retzler < randras @ sdr . hu >
This file is part of OpenWebRX .
OpenWebRX is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
OpenWebRX is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with OpenWebRX . If not , see < http : //www.gnu.org/licenses/>.
* /
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 ;
var waterfall _setup _done = 0 ;
var waterfall _queue = [ ] ;
var waterfall _timer ;
/ * 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 ) ;
something . fade _timer = window . setInterval (
function ( ) {
if ( something . fade _i ++ < n _of _iters )
something . style . opacity = parseFloat ( something . style . opacity ) + change ;
else
{ something . style . opacity = to ; window . clearInterval ( something . fade _timer ) ; }
} , 1000 / fps ) ;
} * /
var rx _photo _state = 1 ;
function e ( what ) { return document . getElementById ( what ) ; }
function init _rx _photo ( )
{
e ( "webrx-top-photo-clip" ) . style . maxHeight = rx _photo _height . toString ( ) + "px" ;
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 ] ;
else if ( window . getComputedStyle ) return document . defaultView . getComputedStyle ( of _what , null ) . getPropertyValue ( which ) ;
}
// ========================================================
// ================= 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 ;
else
{
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 ;
}
}
else
{ 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
env _bounding _line _w = 5 ; //
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 ; }
/ * 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 ) ;
to _px += ( env _att _w + env _bounding _line _w ) ;
// do drawing:
scale _ctx . lineWidth = 3 ;
scale _ctx . strokeStyle = color ;
scale _ctx . fillStyle = color ;
var drag _ranges = { envelope _on _screen : false , line _on _screen : false } ;
if ( ! ( to _px < 0 || from _px > window . innerWidth ) ) // out of screen?
{
drag _ranges . beginning = { x1 : from _px , x2 : from _px + env _bounding _line _w + env _att _w } ;
drag _ranges . ending = { x1 : to _px - env _bounding _line _w - env _att _w , x2 : to _px } ;
drag _ranges . whole _envelope = { x1 : from _px , x2 : to _px } ;
drag _ranges . envelope _on _screen = true ;
scale _ctx . beginPath ( ) ;
scale _ctx . moveTo ( from _px , env _h1 ) ;
scale _ctx . lineTo ( from _px + env _bounding _line _w , env _h1 ) ;
scale _ctx . lineTo ( from _px + env _bounding _line _w + env _att _w , env _h2 ) ;
scale _ctx . lineTo ( to _px - env _bounding _line _w - env _att _w , env _h2 ) ;
scale _ctx . lineTo ( to _px - env _bounding _line _w , env _h1 ) ;
scale _ctx . lineTo ( to _px , env _h1 ) ;
scale _ctx . globalAlpha = 0.3 ;
scale _ctx . fill ( ) ;
scale _ctx . globalAlpha = 1 ;
scale _ctx . stroke ( ) ;
}
if ( typeof line != "undefined" ) // out of screen?
{
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 )
{
// For low and high cut:
if ( in _range ( x , drag _ranges . beginning ) ) return dr . beginning ;
if ( in _range ( x , drag _ranges . ending ) ) return dr . ending ;
// Last priority: having clicked anything else on the envelope, without holding the shift key
if ( in _range ( x , drag _ranges . whole _envelope ) ) return dr . anything _else ;
}
return dr . none ; //User doesn't drag the envelope for this demodulator
}
//******* 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
demodulator _response _time = 100 ;
//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 ,
high _cut _limit : audio _context . sampleRate / 2 ,
low _cut _limit : - audio _context . sampleRate / 2
} ;
//Subtypes only define some filter parameters and the mod string sent to server,
//so you may set these parameters in your custom child class.
//Why? As of demodulation is done on the server, difference is mainly on the server side.
this . server _mod = subtype ;
if ( subtype == "lsb" )
{
this . low _cut = - 3000 ;
this . high _cut = - 300 ;
this . server _mod = "ssb" ;
}
else if ( subtype == "usb" )
{
this . low _cut = 300 ;
this . high _cut = 3000 ;
this . server _mod = "ssb" ;
}
else if ( subtype == "cw" )
{
this . low _cut = 700 ;
this . high _cut = 900 ;
this . server _mod = "ssb" ;
}
else if ( subtype == "nfm" )
{
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
}
else if ( subtype == "am" )
{
this . low _cut = - 4000 ;
this . high _cut = 4000 ;
}
this . wait _for _timer = false ;
this . set _after = false ;
this . set = function ( )
{ //set() is a wrapper to call doset(), but it ensures that doset won't execute more frequently than demodulator_response_time.
if ( ! this . wait _for _timer )
{
this . doset ( false ) ;
this . set _after = false ;
this . wait _for _timer = true ;
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
ws . send ( "SET" + ( ( first _time ) ? " mod=" + this . server _mod : "" ) +
" low_cut=" + this . low _cut . toString ( ) + " high_cut=" + this . high _cut . toString ( ) +
" offset_freq=" + this . offset _frequency . toString ( ) ) ;
}
this . doset ( true ) ; //we set parameters on object creation
//******* envelope object *******
// for drawing the filter envelope above scale
this . envelope . parent = this ;
this . envelope . draw = function ( visible _range )
{
this . visible _range = visible _range ;
this . drag _ranges = demod _envelope _draw ( range ,
center _freq + this . parent . offset _frequency + this . parent . low _cut ,
center _freq + this . parent . offset _frequency + this . parent . high _cut ,
this . color , center _freq + this . parent . offset _frequency ) ;
} ;
// 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.
if ( this . dragged _range == dr . beginning || this . dragged _range == dr . bfo || this . dragged _range == dr . pbs )
{
//we don't let low_cut go beyond its limits
if ( ( new _value = this . drag _origin . low _cut + minus * freq _change ) < this . parent . filter . low _cut _limit ) return true ;
//nor the filter passband be too small
if ( this . parent . high _cut - new _value < this . parent . filter . min _passband ) return true ;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if ( new _value >= this . parent . high _cut ) return true ;
this . parent . low _cut = new _value ;
}
if ( this . dragged _range == dr . ending || this . dragged _range == dr . bfo || this . dragged _range == dr . pbs )
{
//we don't let high_cut go beyond its limits
if ( ( new _value = this . drag _origin . high _cut + minus * freq _change ) > this . parent . filter . high _cut _limit ) return true ;
//nor the filter passband be too small
if ( new _value - this . parent . low _cut < this . parent . filter . min _passband ) return true ;
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
if ( new _value <= this . parent . low _cut ) return true ;
this . parent . high _cut = new _value ;
}
if ( this . dragged _range == dr . anything _else || this . dragged _range == dr . bfo )
{
//when any other part of the envelope is dragged, the offset frequency is changed (whole passband also moves with it)
new _value = this . drag _origin . offset _frequency + freq _change ;
if ( new _value > bandwidth / 2 || new _value < - bandwidth / 2 ) return true ; //we don't allow tuning above Nyquist frequency :-)
this . parent . offset _frequency = new _value ;
}
//now do the actual modifications:
mkenvelopes ( this . visible _range ) ;
this . parent . set ( ) ;
//will have to change this when changing to multi-demodulator mode:
e ( "webrx-actual-freq" ) . innerHTML = format _frequency ( "{x} MHz" , center _freq + this . parent . offset _frequency , 1e6 , 4 ) ;
return true ;
} ;
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.
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 ;
} ;
}
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 ) ;
}
}
function demodulator _remove ( which )
{
demodulators [ which ] . stop ( ) ;
demodulators . splice ( which , 1 ) ;
}
function demodulator _add ( what )
{
demodulators . push ( what ) ;
mkenvelopes ( get _visible _freq _range ( ) ) ;
}
function demodulator _analog _replace ( subtype )
{ //this function should only exist until the multi-demodulator capability is added
var temp _offset = 0 ;
if ( demodulators . length )
{
temp _offset = demodulators [ 0 ] . offset _frequency ;
demodulator _remove ( 0 ) ;
}
demodulator _add ( new demodulator _default _analog ( temp _offset , subtype ) ) ;
}
function demodulator _set _offset _frequency ( which , to _what )
{
if ( to _what > bandwidth / 2 || to _what < - bandwidth / 2 ) return ;
demodulators [ 0 ] . offset _frequency = Math . round ( to _what ) ;
demodulators [ 0 ] . set ( ) ;
mkenvelopes ( get _visible _freq _range ( ) ) ;
}
// ========================================================
// =================== 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 ) ;
scale _canvas = e ( "openwebrx-scale-canvas" ) ;
scale _ctx = scale _canvas . getContext ( "2d" ) ;
scale _canvas . addEventListener ( "mousedown" , scale _canvas _mousedown , false ) ;
scale _canvas . addEventListener ( "mousemove" , scale _canvas _mousemove , false ) ;
scale _canvas . addEventListener ( "mouseup" , scale _canvas _mouseup , false ) ;
resize _scale ( ) ;
}
var 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 ;
if ( scale _canvas _drag _params . mouse _down && ! scale _canvas _drag _params . drag && Math . abs ( evt . pageX - scale _canvas _drag _params . start _x ) > canvas _drag _min _delta )
//we can use the main drag_min_delta thing of the main canvas
{
scale _canvas _drag _params . drag = true ;
//call the drag_start for all demodulators (and they will decide if they're dragged, based on X coordinate)
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 ) ) ;
}
}
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 = { } ;
fcalc = function ( freq )
{
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
if ( out . small < scale _min _space _bw _small _markers ) return false ;
if ( out . small / 2 >= scale _min _space _bw _small _markers && freq . toString ( ) [ 0 ] != "5" ) { out . small /= 2 ; out . ratio *= 2 ; }
out . smallbw = freq / out . ratio ;
return true ;
}
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 ;
scale _ctx . beginPath ( ) ;
scale _ctx . moveTo ( x , 22 ) ;
if ( marker _hz % spacing . params . large _marker _per _hz == 0 )
{ //large marker
if ( typeof first _large == "undefined" ) var first _large = marker _hz ;
last _large = marker _hz ;
scale _ctx . lineWidth = 3.5 ;
scale _ctx . lineTo ( x , 22 + 11 ) ;
ftext ( marker _hz ) ;
var text _measured = scale _ctx . measureText ( text _to _draw ) ;
scale _ctx . textAlign = "center" ;
//advanced text drawing begins
if ( zoom _level == 0 && range . start + spacing . smallbw * spacing . ratio > marker _hz )
{ //if this is the first overall marker when zoomed out
if ( x < text _measured . width / 2 )
{ //and if it would be clipped off the screen
if ( scale _px _from _freq ( marker _hz + spacing . smallbw * spacing . ratio , range ) - text _measured . width >= scale _min _space _bw _texts )
{ //and if we have enough space to draw it correctly without clipping
scale _ctx . textAlign = "left" ;
scale _ctx . fillText ( text _to _draw , 0 , text _h _pos ) ;
}
}
}
else if ( zoom _level == 0 && range . end - spacing . smallbw * spacing . ratio < marker _hz )
{ //if this is the last overall marker when zoomed out
if ( x > window . innerWidth - text _measured . width / 2 )
{ //and if it would be clipped off the screen
if ( window . innerWidth - text _measured . width - scale _px _from _freq ( marker _hz - spacing . smallbw * spacing . ratio , range ) >= scale _min _space _bw _texts )
{ //and if we have enough space to draw it correctly without clipping
scale _ctx . textAlign = "right" ;
scale _ctx . fillText ( text _to _draw , window . innerWidth , text _h _pos ) ;
}
}
}
else scale _ctx . fillText ( text _to _draw , x , text _h _pos ) ; //draw text normally
}
else
{ //small marker
scale _ctx . lineWidth = 2 ;
scale _ctx . lineTo ( x , 22 + 8 ) ;
}
marker _hz += spacing . smallbw ;
scale _ctx . stroke ( ) ;
}
if ( zoom _level != 0 )
{ // if zoomed, we don't want the texts to disappear because their markers can't be seen
// on the left side
scale _ctx . textAlign = "center" ;
var f = first _large - spacing . smallbw * spacing . ratio ;
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 ;
//e("webrx-freq-show").style.visibility="visible";
}
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 )
{
if ( ! canvas _drag && Math . abs ( evt . pageX - canvas _drag _start _x ) > canvas _drag _min _delta )
{
canvas _drag = true ;
canvas _container . style . cursor = "move" ;
}
if ( canvas _drag )
{
var deltaX = canvas _drag _last _x - evt . pageX ;
var deltaY = canvas _drag _last _y - evt . pageY ;
//zoom_center_where=zoom_center_where_calc(evt.pageX);
var dpx = range . hps * deltaX ;
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 ;
if ( ! canvas _drag )
{
//ws.send("SET offset_freq="+canvas_get_freq_offset(relativeX).toString());
2014-12-21 22:38:45 +00:00
demodulator _set _offset _frequency ( 0 , canvas _get _freq _offset ( relativeX ) ) ;
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 ) ) ;
evt . preventDefault ( ) ;
//evt.returnValue = false; //disable scrollbar move
}
zoom _max _level _hps = 33 ; //Hz/pixel
zoom _levels _count = 5 ;
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 ;
function mkzoomlevels ( )
{
zoom _levels = [ 1 ] ;
maxc = get _zoom _coeff _from _hps ( zoom _max _level _hps ) ;
if ( maxc < 1 ) return ;
for ( i = 1 ; i < zoom _levels _count ; i ++ )
zoom _levels . push ( 1 + ( maxc - 1 ) * ( i / ( zoom _levels _count - 1 ) ) ) ;
}
function zoom _step ( out , where , onscreen )
{
if ( ( out && zoom _level == 0 ) || ( ! out && zoom _level >= zoom _levels _count - 1 ) ) return ;
if ( out ) -- zoom _level ;
else ++ zoom _level ;
zoom _center _rel = canvas _get _freq _offset ( where ) ;
//console.log("zoom_step || zlevel: "+zoom_level.toString()+" zlevel_val: "+zoom_levels[zoom_level].toString()+" zoom_center_rel: "+zoom_center_rel.toString());
zoom _center _where = onscreen ;
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 ;
if ( zoom _offset _px < winsize - canvases _new _width )
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 ;
canvas _container . style . height = ( window . innerHeight - e ( "webrx-top-container" ) . clientHeight - e ( "openwebrx-scale-container" ) . clientHeight ) . toString ( ) + "px" ;
}
function on _ws _recv ( evt )
{
if ( ! ( evt . data instanceof ArrayBuffer ) ) { divlog ( "on_ws_recv(): Not ArrayBuffer received..." , 1 ) ; return ; }
//
firstChars = getFirstChars ( evt . data , 3 ) ;
if ( firstChars == "CLI" )
{
var stringData = arrayBufferToString ( evt . data ) ;
if ( stringData . substring ( 0 , 16 ) == "CLIENT DE SERVER" ) divlog ( "Acknowledged WebSocket connection: " + stringData ) ;
}
if ( firstChars == "AUD" )
{
var audio _data = new Int16Array ( evt . data , 4 ) ;
audio _prepare ( audio _data ) ;
audio _buffer _current _size _debug += audio _data . length ;
audio _buffer _all _size _debug += audio _data . length ;
if ( audio _initialized == 0 && audio _prepared _buffers . length > audio _buffering _fill _to ) audio _init ( )
}
else if ( firstChars == "FFT" )
{
//alert("Yupee! Doing FFT");
var floatArray = new Float32Array ( evt . data , 4 ) ;
waterfall _add _queue ( floatArray ) ;
} else if ( firstChars == "MSG" )
{
/ * t r y
{ * /
var stringData = arrayBufferToString ( evt . data ) ;
params = stringData . substring ( 4 ) . split ( " " ) ;
for ( i = 0 ; i < params . length ; i ++ )
{
param = params [ i ] . split ( "=" ) ;
switch ( param [ 0 ] )
{
case "setup" :
waterfall _init ( ) ;
break ;
case "bandwidth" :
bandwidth = parseInt ( param [ 1 ] )
break ;
case "center_freq" :
center _freq = parseInt ( param [ 1 ] )
break ;
case "fft_size" :
fft _size = parseInt ( param [ 1 ] )
break ;
case "fft_fps" :
fft _fps = parseInt ( param [ 1 ] )
break ;
}
}
/ * }
catch ( err )
{
divlog ( "Received invalid message over WebSocket." ) ;
} * /
}
}
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 ) ;
}
function waterfall _add _queue ( what )
{
waterfall _queue . push ( what ) ;
}
function waterfall _dequeue ( )
{
if ( waterfall _queue . length ) waterfall _add ( waterfall _queue . shift ( ) ) ;
2014-12-12 12:55:10 +00:00
if ( waterfall _queue . length > Math . max ( fft _fps / 2 , 8 ) ) //in case of emergency
2014-11-29 00:07:10 +00:00
{
2014-12-12 12:55:10 +00:00
console . log ( 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 ) ;
}
function divlog ( what , is _error )
{
if ( typeof is _error !== undefined && is _error == 1 ) what = "<span class=\"webrx-error\">" + what + "</span>" ;
e ( "openwebrx-debugdiv" ) . innerHTML += what + "<br />" ;
}
var audio _context ;
var audio _initialized = 0 ;
var audio _received = Array ( ) ;
var audio _buffer _index = 0 ;
var audio _resampler ;
var audio _node ;
//var audio_received_sample_rate = 48000;
var audio _input _buffer _size ;
// Optimalise these if audio lags or is choppy:
var audio _buffer _size = 8192 ; //2048 was choppy
var audio _buffer _maximal _length _sec = 1.7 ; //actual number of samples are calculated from sample rate
var audio _flush _interval _ms = 250 ; //the interval in which audio_flush() is called
var audio _prepared _buffers = Array ( ) ;
var audio _last _output _buffer = new Float32Array ( audio _buffer _size ) ;
var audio _last _output _offset = 0 ;
var audio _buffering = false ;
var audio _buffering _fill _to = 10 ; //on audio underrun we wait until this n*audio_buffer_size samples are present
function audio _prepare ( data )
{
//console.log("audio_prepare :: "+data.length.toString());
//console.log("data.len = "+data.length.toString());
var dopush = function ( )
{
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 ++ ;
} ;
if ( data . length == 0 ) return ;
if ( audio _last _output _offset + data . length <= audio _buffer _size )
{ //array fits into output buffer
for ( var i = 0 ; i < data . length ; i ++ ) audio _last _output _buffer [ i + audio _last _output _offset ] = data [ i ] / 32768 ;
audio _last _output _offset += data . length ;
//console.log("fits into; offset="+audio_last_output_offset.toString());
if ( audio _last _output _offset == audio _buffer _size ) dopush ( ) ;
}
else
{ //array is larger than the remaining space in the output buffer
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
audio _last _output _buffer [ i + audio _last _output _offset ] = data [ i ] / 32768 ;
dopush ( ) ; //push the output buffer and create a new one
//console.log("larger than; copied half: "+copied.toString()+", now at: "+audio_last_output_offset.toString());
for ( var i = 0 ; i < remain ; i ++ ) //copy the remaining input samples to the new output buffer
audio _last _output _buffer [ i ] = data [ i + copied ] / 32768 ;
audio _last _output _offset += remain ;
//console.log("larger than; remained: "+remain.toString()+", now at: "+audio_last_output_offset.toString());
}
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 )
{
if ( audio _buffering ) return ;
if ( audio _prepared _buffers . length == 0 ) { add _problem ( "audio underrun" ) ; audio _buffering = true ; }
else e . outputBuffer . copyToChannel ( audio _prepared _buffers . shift ( ) , 0 ) ;
}
function audio _flush ( )
{
flushed = false ;
while ( audio _buffer _maximal _length _sec * audio _context . sampleRate < audio _prepared _buffers . length * audio _buffer _size )
{
flushed = true ;
audio _prepared _buffers . shift ( ) ;
}
if ( flushed ) add _problem ( "audio overrun" ) ;
}
function audio _onprocess _notused ( e )
{
//https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js
if ( audio _received . length == 0 )
{ 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 = ""
while ( 1 )
{
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
{
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";
audio _buffer _index = 0 ;
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 )
{
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 )
{
ws . send ( "SET " + what + "=" + value . toString ( ) ) ;
}
function audio _init ( )
{
2014-12-12 12:55:10 +00:00
audio _debug _time _start = ( new Date ( ) ) . getTime ( ) ;
audio _debug _time _last _start = audio _debug _time _start ;
2014-11-29 00:07:10 +00:00
//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
try
{
window . AudioContext = window . AudioContext || window . webkitAudioContext ;
audio _context = new AudioContext ( ) ;
}
catch ( e )
{
divlog ( 'Your browser does not support Web Audio API, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.' , 1 ) ;
}
//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 ) ;
// --- Resampling ---
//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);
webrx _set _param ( "audio_rate" , audio _context . sampleRate ) ; //Don't try to resample
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 ) ; * /
demodulator _analog _replace ( 'nfm' ) ; //needs audio_context.sampleRate to exist
}
function on _ws _closed ( )
{
try
{
audio _node . disconnect ( ) ;
}
catch ( dont _care ) { }
2014-12-11 19:04:24 +00:00
divlog ( "WebSocket has closed unexpectedly. Please reload the page." , 1 ) ;
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 ( )
{
2014-12-11 19:04:24 +00:00
if ( ws _url . startswith ( "ws://localhost:" ) && window . location . hostname != "127.0.0.1" && window . location . hostname != "localhost" )
{
divlog ( "Server administrator should set <em>server_hostname</em> correctly, because it is left as <em>\"localhost\"</em>. Now guessing hostname from page URL." , 1 ) ;
ws _url = "ws://" + ( window . location . origin . split ( "://" ) [ 1 ] ) + "/ws/" ; //guess automatically
}
2014-11-29 00:07:10 +00:00
if ( ! ( "WebSocket" in window ) )
divlog ( "Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser." ) ;
ws = new WebSocket ( ws _url + client _id ) ;
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 ;
}
//var color_scale=[0xFFFFFFFF, 0x000000FF];
//var color_scale=[0x000000FF, 0x000000FF, 0x3a0090ff, 0x10c400ff, 0xffef00ff, 0xff5656ff];
//var color_scale=[0x000000FF, 0x000000FF, 0x534b37ff, 0xcedffaff, 0x8899a9ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff];
//var color_scale=[ 0x000000FF, 0xff5656ff, 0xffffffff];
//2014-04-22
var color _scale = [ 0x2e6893ff , 0x69a5d0ff , 0x214b69ff , 0x9dc4e0ff , 0xfff775ff , 0xff8a8aff , 0xb20000ff ] ;
function waterfall _mkcolor ( db _value )
{
min _value = - 100 ; //in dB
max _value = 10
if ( db _value < min _value ) db _value = min _value
if ( db _value > max _value ) db _value = max _value
full _scale = max _value - min _value ;
relative _value = db _value - min _value ;
value _percent = relative _value / full _scale ;
percent _for _one _color = 1 / ( color _scale . length - 1 ) ;
index = Math . floor ( value _percent / percent _for _one _color ) ;
remain = ( value _percent - percent _for _one _color * index ) / percent _for _one _color ;
return color _between ( color _scale [ index + 1 ] , color _scale [ index ] , remain ) ;
}
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 ( )
{
new _canvas = document . createElement ( "canvas" ) ;
new _canvas . width = fft _size ;
new _canvas . height = canvas _default _height ;
canvas _actual _line = canvas _default _height - 1 ;
new _canvas . style . width = ( canvas _container . clientWidth * zoom _levels [ zoom _level ] ) . toString ( ) + "px" ;
new _canvas . style . left = zoom _offset _px . toString ( ) + "px" ;
new _canvas . style . height = canvas _default _height . toString ( ) + "px" ;
new _canvas . openwebrx _top = ( - canvas _default _height + 1 ) ;
new _canvas . style . top = new _canvas . openwebrx _top . toString ( ) + "px" ;
canvas _context = new _canvas . getContext ( "2d" ) ;
canvas _container . appendChild ( new _canvas ) ;
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 ) ;
}
function init _canvas _container ( )
{
canvas _container = e ( "webrx-canvas-container" ) ;
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 ( )
{
canvases . forEach ( function ( p )
{
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" ;
//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" ;
canvases . forEach ( function ( p )
{
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 ( ) ;
waterfall _timer = window . setInterval ( waterfall _dequeue , 900 / fft _fps ) ;
resize _waterfall _container ( false ) ; /* then */ resize _canvases ( ) ;
scale _setup ( ) ;
mkzoomlevels ( ) ;
waterfall _setup _done = 1 ;
}
var waterfall _dont _scale = 0 ;
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 ) ;
}
}
}
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 )
{
last _pixel += data [ i ] ;
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 ) ; //?
}
}
}
}
//Add line to waterfall image
base = ( h - 1 ) * w * 4 ;
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 ;
} * /
//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 ( ) ;
//divlog("Drawn FFT");
}
/ *
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 ( )
{
var wt = e ( "webrx-rx-title" ) ;
var tl = e ( "webrx-ha5kfu-top-logo" ) ;
if ( wt . offsetLeft + wt . offsetWidth > tl . offsetLeft - 20 ) tl . style . display = "none" ;
else tl . style . display = "block" ;
}
function openwebrx _resize ( )
{
resize _canvases ( ) ;
resize _waterfall _container ( true ) ;
resize _scale ( ) ;
check _top _bar _congestion ( ) ;
}
function openwebrx _init ( )
{
init _rx _photo ( ) ;
open _websocket ( ) ;
place _panels ( ) ;
window . setTimeout ( function ( ) { window . setInterval ( debug _audio , 1000 ) ; } , 1000 ) ;
window . addEventListener ( "resize" , openwebrx _resize ) ;
}
/ *
window . setInterval ( function ( ) {
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 ;
2014-11-29 00:07:10 +00:00
e ( "openwebrx-audio-sps" ) . innerHTML =
2014-12-12 12:55:10 +00:00
"audio recv. at " + ( audio _buffer _current _size _debug / audio _debug _time _since _last _call ) . toFixed ( 0 ) + " sps (" +
2014-11-29 00:07:10 +00:00
( audio _buffer _all _size _debug / audio _debug _time _taken ) . toFixed ( 1 ) + " sps avg.), feed at " +
( ( audio _buffer _current _count _debug * audio _buffer _size ) / audio _debug _time _taken ) . toFixed ( 1 ) + " sps output" ;
audio _buffer _current _size _debug = 0 ;
}
// ========================================================
// ======================= PANELS =======================
// ========================================================
panel _margin = 10 ;
function pop _bottommost _panel ( from )
{
min _order = parseInt ( from [ 0 ] . dataset . panelOrder ) ;
min _index = 0 ;
for ( i = 0 ; i < from . length ; i ++ )
{
actual _order = parseInt ( from [ i ] . dataset . panelOrder ) ;
if ( actual _order < min _order )
{
min _index = i ;
min _order = actual _order ;
}
}
to _return = from [ min _index ] ;
from . splice ( min _index , 1 ) ;
return to _return ;
}
function place _panels ( )
{
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" )
{
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" ;
c . style . height = newSize [ 1 ] + "px" ;
c . style . margin = panel _margin . toString ( ) + "px" ;
c . openwebrxPanelWidth = parseInt ( newSize [ 0 ] ) ;
c . openwebrxPanelHeight = parseInt ( newSize [ 1 ] ) ;
}
}
y = 0 ;
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" ;
y += p . openwebrxPanelHeight + 3 * panel _margin ;
}
y = 0 ;
while ( right _col . length > 0 )
{
p = pop _bottommost _panel ( right _col ) ;
p . style . right = "10px" ;
p . style . bottom = y . toString ( ) + "px" ;
p . style . visibility = "visible" ;
y += p . openwebrxPanelHeight + 3 * panel _margin ;
}
}