2020-05-01 23:25:23 +00:00
function Filter ( demodulator ) {
this . demodulator = demodulator ;
2020-04-30 20:07:19 +00:00
this . min _passband = 100 ;
}
Filter . prototype . getLimits = function ( ) {
var max _bw ;
2021-05-01 16:27:15 +00:00
if ( [ 'pocsag' , 'packet' ] . indexOf ( this . demodulator . get _secondary _demod ( ) ) >= 0 ) {
2020-04-30 20:07:19 +00:00
max _bw = 12500 ;
2021-05-01 16:27:15 +00:00
} else if ( [ 'dmr' , 'dstar' , 'nxdn' , 'ysf' , 'm17' ] . indexOf ( this . demodulator . get _modulation ( ) ) >= 0 ) {
max _bw = 6250 ;
2020-08-08 20:04:10 +00:00
} else if ( this . demodulator . get _modulation ( ) === 'wfm' ) {
2021-01-14 22:47:12 +00:00
max _bw = 100000 ;
2020-09-05 20:10:23 +00:00
} else if ( this . demodulator . get _modulation ( ) === 'drm' ) {
2021-05-01 16:27:15 +00:00
max _bw = 50000 ;
2021-09-13 14:58:45 +00:00
} else if ( this . demodulator . get _modulation ( ) === "freedv" ) {
max _bw = 4000 ;
2020-04-30 20:07:19 +00:00
} else {
max _bw = ( audioEngine . getOutputRate ( ) / 2 ) - 1 ;
}
return {
high : max _bw ,
low : - max _bw
} ;
} ;
2020-04-30 21:31:52 +00:00
function Envelope ( demodulator ) {
this . demodulator = demodulator ;
2020-04-30 20:07:19 +00:00
this . dragged _range = Demodulator . draggable _ranges . none ;
}
Envelope . prototype . draw = function ( visible _range ) {
this . visible _range = visible _range ;
2020-04-30 21:31:52 +00:00
var line = center _freq + this . demodulator . offset _frequency ;
2020-04-30 20:07:19 +00:00
2020-04-30 21:16:49 +00:00
// ____
// 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).
var env _bounding _line _w = 5 ; //
var env _att _w = 5 ; // _______ ___env_h2 in px ___|_____
var env _h1 = 17 ; // _/| \_ ___env_h1 in px _/ |_ \_
var env _h2 = 5 ; // |||env_att_line_w |_env_lineplus
var env _lineplus = 1 ; // ||env_bounding_line_w
var env _line _click _area = 6 ;
//range=get_visible_freq_range();
2020-04-30 21:31:52 +00:00
var from = center _freq + this . demodulator . offset _frequency + this . demodulator . low _cut ;
2020-04-30 21:16:49 +00:00
var from _px = scale _px _from _freq ( from , range ) ;
2020-04-30 21:31:52 +00:00
var to = center _freq + this . demodulator . offset _frequency + this . demodulator . high _cut ;
2020-04-30 21:16:49 +00:00
var to _px = scale _px _from _freq ( to , range ) ;
if ( to _px < from _px ) /* swap'em */ {
var temp _px = to _px ;
to _px = from _px ;
from _px = temp _px ;
}
from _px -= ( env _att _w + env _bounding _line _w ) ;
to _px += ( env _att _w + env _bounding _line _w ) ;
// do drawing:
scale _ctx . lineWidth = 3 ;
2020-04-30 21:31:52 +00:00
var color = this . color || '#ffff00' ; // yellow
2020-04-30 21:16:49 +00:00
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?
{
var 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 ( ) ;
}
}
2020-04-30 21:31:52 +00:00
this . drag _ranges = drag _ranges ;
2020-04-30 21:16:49 +00:00
} ;
2020-04-30 20:07:19 +00:00
Envelope . prototype . drag _start = function ( x , key _modifiers ) {
this . key _modifiers = key _modifiers ;
2020-04-30 21:16:49 +00:00
this . dragged _range = this . where _clicked ( x , this . drag _ranges , key _modifiers ) ;
2020-04-30 20:07:19 +00:00
this . drag _origin = {
x : x ,
2020-04-30 21:31:52 +00:00
low _cut : this . demodulator . low _cut ,
high _cut : this . demodulator . high _cut ,
offset _frequency : this . demodulator . offset _frequency
2020-04-30 20:07:19 +00:00
} ;
return this . dragged _range !== Demodulator . draggable _ranges . none ;
} ;
2020-04-30 21:20:56 +00:00
Envelope . prototype . where _clicked = function ( x , drag _ranges , key _modifiers ) { // Check exactly what the user has clicked based on ranges returned by envelope_draw().
2020-04-30 21:16:49 +00:00
var in _range = function ( x , range ) {
return range . x1 <= x && range . x2 >= x ;
} ;
var 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
} ;
2020-04-30 20:07:19 +00:00
Envelope . prototype . drag _move = function ( x ) {
var dr = Demodulator . draggable _ranges ;
var new _value ;
if ( this . dragged _range === dr . none ) return false ; // we return if user is not dragging (us) at all
var freq _change = Math . round ( this . visible _range . hps * ( x - this . drag _origin . x ) ) ;
//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.
var 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
2020-04-30 21:31:52 +00:00
if ( ( new _value = this . drag _origin . low _cut + minus * freq _change ) < this . demodulator . filter . getLimits ( ) . low ) return true ;
2020-04-30 20:07:19 +00:00
//nor the filter passband be too small
2020-04-30 21:31:52 +00:00
if ( this . demodulator . high _cut - new _value < this . demodulator . filter . min _passband ) return true ;
2020-04-30 20:07:19 +00:00
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
2020-04-30 21:31:52 +00:00
if ( new _value >= this . demodulator . high _cut ) return true ;
2020-05-01 23:25:23 +00:00
this . demodulator . setLowCut ( new _value ) ;
2020-04-30 20:07:19 +00:00
}
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
2020-04-30 21:31:52 +00:00
if ( ( new _value = this . drag _origin . high _cut + minus * freq _change ) > this . demodulator . filter . getLimits ( ) . high ) return true ;
2020-04-30 20:07:19 +00:00
//nor the filter passband be too small
2020-04-30 21:31:52 +00:00
if ( new _value - this . demodulator . low _cut < this . demodulator . filter . min _passband ) return true ;
2020-04-30 20:07:19 +00:00
//sanity check to prevent GNU Radio "firdes check failed: fa <= fb"
2020-04-30 21:31:52 +00:00
if ( new _value <= this . demodulator . low _cut ) return true ;
2020-05-01 23:25:23 +00:00
this . demodulator . setHighCut ( new _value ) ;
2020-04-30 20:07:19 +00:00
}
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 :-)
2020-05-01 22:05:20 +00:00
this . demodulator . set _offset _frequency ( new _value ) ;
2020-04-30 20:07:19 +00:00
}
//now do the actual modifications:
2020-05-01 22:05:20 +00:00
//mkenvelopes(this.visible_range);
//this.demodulator.set();
2020-04-30 20:07:19 +00:00
return true ;
} ;
Envelope . prototype . drag _end = function ( ) {
var to _return = this . dragged _range !== Demodulator . draggable _ranges . none ; //this part is required for cliking anywhere on the scale to set offset
this . dragged _range = Demodulator . draggable _ranges . none ;
return to _return ;
} ;
//******* 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
function Demodulator ( offset _frequency , modulation ) {
this . offset _frequency = offset _frequency ;
this . envelope = new Envelope ( this ) ;
this . color = Demodulator . get _next _color ( ) ;
this . modulation = modulation ;
2020-05-01 23:25:23 +00:00
this . filter = new Filter ( this ) ;
2020-04-30 20:07:19 +00:00
this . squelch _level = - 150 ;
this . dmr _filter = 3 ;
2020-05-03 11:10:25 +00:00
this . started = false ;
2020-04-30 20:07:19 +00:00
this . state = { } ;
2020-04-30 20:31:18 +00:00
this . secondary _demod = false ;
2020-04-30 20:07:19 +00:00
var mode = Modes . findByModulation ( modulation ) ;
if ( mode ) {
this . low _cut = mode . bandpass . low _cut ;
this . high _cut = mode . bandpass . high _cut ;
}
2020-05-01 22:05:20 +00:00
this . listeners = {
2020-05-03 17:55:48 +00:00
"frequencychange" : [ ] ,
"squelchchange" : [ ]
2020-05-01 22:05:20 +00:00
} ;
2020-04-30 20:07:19 +00:00
}
//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
2020-04-30 21:20:56 +00:00
} ; //to which parameter these correspond in envelope_draw()
2020-04-30 20:07:19 +00:00
Demodulator . color _index = 0 ;
Demodulator . colors = [ "#ffff00" , "#00ff00" , "#00ffff" , "#058cff" , "#ff9600" , "#a1ff39" , "#ff4e39" , "#ff5dbd" ] ;
Demodulator . get _next _color = function ( ) {
if ( this . color _index >= this . colors . length ) this . color _index = 0 ;
return ( this . colors [ this . color _index ++ ] ) ;
}
2020-05-01 22:05:20 +00:00
Demodulator . prototype . on = function ( event , handler ) {
this . listeners [ event ] . push ( handler ) ;
} ;
Demodulator . prototype . emit = function ( event , params ) {
this . listeners [ event ] . forEach ( function ( fn ) {
fn ( params ) ;
} ) ;
} ;
2020-04-30 20:07:19 +00:00
Demodulator . prototype . set _offset _frequency = function ( to _what ) {
2021-01-13 22:50:36 +00:00
if ( typeof ( to _what ) == 'undefined' || to _what > bandwidth / 2 || to _what < - bandwidth / 2 ) return ;
2020-05-01 22:05:20 +00:00
to _what = Math . round ( to _what ) ;
if ( this . offset _frequency === to _what ) {
return ;
}
this . offset _frequency = to _what ;
2020-04-30 20:07:19 +00:00
this . set ( ) ;
2020-05-01 22:05:20 +00:00
this . emit ( "frequencychange" , to _what ) ;
2020-04-30 20:07:19 +00:00
mkenvelopes ( get _visible _freq _range ( ) ) ;
} ;
Demodulator . prototype . get _offset _frequency = function ( ) {
return this . offset _frequency ;
} ;
Demodulator . prototype . get _modulation = function ( ) {
return this . modulation ;
} ;
Demodulator . prototype . start = function ( ) {
2020-05-03 11:10:25 +00:00
this . started = true ;
2020-04-30 20:07:19 +00:00
this . set ( ) ;
ws . send ( JSON . stringify ( {
"type" : "dspcontrol" ,
"action" : "start"
} ) ) ;
} ;
// TODO check if this is actually used
Demodulator . prototype . stop = function ( ) {
} ;
Demodulator . prototype . send = function ( params ) {
ws . send ( JSON . stringify ( { "type" : "dspcontrol" , "params" : params } ) ) ;
}
Demodulator . prototype . set = function ( ) { //this function sends demodulator parameters to the server
2020-05-03 11:10:25 +00:00
if ( ! this . started ) return ;
2020-04-30 20:07:19 +00:00
var params = {
"low_cut" : this . low _cut ,
"high_cut" : this . high _cut ,
"offset_freq" : this . offset _frequency ,
"mod" : this . modulation ,
"dmr_filter" : this . dmr _filter ,
2020-04-30 20:31:18 +00:00
"squelch_level" : this . squelch _level ,
"secondary_mod" : this . secondary _demod ,
"secondary_offset_freq" : this . secondary _offset _freq
2020-04-30 20:07:19 +00:00
} ;
var to _send = { } ;
for ( var key in params ) {
if ( ! ( key in this . state ) || params [ key ] !== this . state [ key ] ) {
to _send [ key ] = params [ key ] ;
}
}
if ( Object . keys ( to _send ) . length > 0 ) {
this . send ( to _send ) ;
for ( var key in to _send ) {
this . state [ key ] = to _send [ key ] ;
}
}
mkenvelopes ( get _visible _freq _range ( ) ) ;
} ;
Demodulator . prototype . setSquelch = function ( squelch ) {
2020-05-03 17:55:48 +00:00
if ( this . squelch _level == squelch ) {
return ;
}
2020-04-30 20:07:19 +00:00
this . squelch _level = squelch ;
this . set ( ) ;
2020-05-03 17:55:48 +00:00
this . emit ( "squelchchange" , squelch ) ;
} ;
Demodulator . prototype . getSquelch = function ( ) {
return this . squelch _level ;
2020-04-30 20:07:19 +00:00
} ;
Demodulator . prototype . setDmrFilter = function ( dmr _filter ) {
this . dmr _filter = dmr _filter ;
this . set ( ) ;
} ;
Demodulator . prototype . setBandpass = function ( bandpass ) {
this . bandpass = bandpass ;
this . low _cut = bandpass . low _cut ;
this . high _cut = bandpass . high _cut ;
this . set ( ) ;
} ;
2020-05-01 23:25:23 +00:00
Demodulator . prototype . setLowCut = function ( low _cut ) {
this . low _cut = low _cut ;
this . set ( ) ;
} ;
Demodulator . prototype . setHighCut = function ( high _cut ) {
this . high _cut = high _cut ;
this . set ( ) ;
} ;
2020-04-30 20:07:19 +00:00
Demodulator . prototype . getBandpass = function ( ) {
return {
low _cut : this . low _cut ,
high _cut : this . high _cut
} ;
} ;
2020-04-30 20:31:18 +00:00
Demodulator . prototype . set _secondary _demod = function ( secondary _demod ) {
2020-05-01 22:05:20 +00:00
if ( this . secondary _demod === secondary _demod ) {
return ;
}
2020-04-30 20:31:18 +00:00
this . secondary _demod = secondary _demod ;
this . set ( ) ;
} ;
2020-05-01 22:05:20 +00:00
Demodulator . prototype . get _secondary _demod = function ( ) {
return this . secondary _demod ;
} ;
2020-04-30 20:31:18 +00:00
Demodulator . prototype . set _secondary _offset _freq = function ( secondary _offset ) {
2020-05-01 22:05:20 +00:00
if ( this . secondary _offset _freq === secondary _offset ) {
return ;
}
2020-04-30 20:31:18 +00:00
this . secondary _offset _freq = secondary _offset ;
this . set ( ) ;
} ;