From 782ad98474d47f559dd0db3a5c42a4792a532ccb Mon Sep 17 00:00:00 2001 From: ha7ilm Date: Sat, 29 Nov 2014 01:07:47 +0100 Subject: [PATCH] initial commit --- README.md | 8 +- htdocs/openwebrx.js~ | 1538 ------------------------------------------ 2 files changed, 7 insertions(+), 1539 deletions(-) delete mode 100755 htdocs/openwebrx.js~ diff --git a/README.md b/README.md index a799180..0caf420 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ It has the following features: OpenWebRX currently requires a Linux machine to run. First you will need to install the dependencies: + - libcsdr - rtl-sdr @@ -29,6 +30,7 @@ After cloning this repository and connecting an RTL-SDR dongle to your computer, You can now open the GUI at http://localhost:8073. Please note that it is also listening on the following ports (on localhost only): + - port 8888 for the I/Q source, - port 4951 for the multi-user I/Q server. @@ -39,6 +41,10 @@ I would like to maintain a list of online amateur radio receivers on 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_pxwindow.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)); - /*if(this.dragged_range==dr.beginning||this.dragged_range==dr.ending) - { - //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.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_cutbandwidth/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;ibandwidth/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=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; - 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=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, 22+10); - } - } - } - else if(zoom_level==0&&range.end-spacing.smallbw*spacing.ratiowindow.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, 22+10); - } - } - } - else scale_ctx.fillText(text_to_draw, x, 22+10); //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/23) - { - 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; - /*realX=(relativeX-element.clientWidth/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()); - //e("webrx-actual-freq").innerHTML=format_frequency("{x} MHz",canvas_get_frequency(relativeX),1e6,4); - demodulator_set_offset_frequency(0, canvas_get_freq_offset(relativeX)); - } - 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; - var relativeX=(evt.offsetX)?evt.offsetX:evt.layerX; - 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-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_px10) 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") - { - /*try - {*/ - params=stringData.substring(4).split(" "); - for(i=0;ifft_fps/2) //in case of emergency - { - 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=""+what+""; - e("openwebrx-debugdiv").innerHTML+=what+"
"; -} - -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=2; //actual number of samples are calculated from sample rate -var audio_flush_interval_ms=250; //the interval in which audio_flush() is called - -function audio_onprocess(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"+read_remain.toString()+" obi="+obi.toString()+"\n"; - audio_buffer_index+=read_remain; - break; - } - else - { - for (i=audio_buffer_index; iaudio_buffer_maximal_length) - { - 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() -{ - 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() -{ - //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); - } - audio_node = audio_context.createJavaScriptNode(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()); - /*audio_source=audio_context.createBufferSource(); - 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) {} - divlog("WebSocket has closed unexpectedly. Please reload the page.", 1); -} - -function on_ws_error(event) -{ - divlog(event.toString(),1); -} - -function open_websocket() -{ - 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]; - -//Nona: -var color_scale=[0x000000ff,0x000000ff, 0xffffffff, 0x0000ffff, 0x00ffffff]; - -function waterfall_mkcolor(db_value) -{ - min_value=-100; //in dB - max_value=10 - if(db_valuemax_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("mousewheel",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("mousewheel",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 ==== - /*if(waterfall_dont_scale) - { - scaled=data; - for(i=scaled.length;i1) - { - 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; i1) - { - 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>>0)>>((3-i)*8))&0xff; - }*/ - - //Add line to waterfall image - oneline_image = canvas_context.createImageData(w,1); - for(x=0;x>>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; ytl.offsetLeft-20) tl.style.display="none"; - else tl.style.display="block"; -} - -function webrx_resize() -{ - resize_canvases(); - resize_waterfall_container(true); - resize_scale(); - check_top_bar_congestion(); -} - -function webrx_init() -{ - init_rx_photo(); - open_websocket(); - place_panels(); - window.setInterval(debug_audio,1000); -} - -/* -window.setInterval(function(){ - sum=0; - for(i=0;i=(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); } - -function debug_audio() -{ - - e("openwebrx-audio-sps").innerHTML=audio_buffer_current_size_debug.toString(); - 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;i0) - { - 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; - } -} -