openwebrx-clone/htdocs/openwebrx.js

2864 lines
110 KiB
JavaScript
Raw Normal View History

2014-11-29 00:07:10 +00:00
/*
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
2014-11-29 00:07:10 +00:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
2014-11-29 00:07:10 +00:00
This program is distributed in the hope that it will be useful,
2014-11-29 00:07:10 +00:00
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
2014-11-29 00:07:10 +00:00
"""
2014-11-29 00:07:10 +00:00
*/
2019-10-16 11:17:47 +00:00
is_firefox = navigator.userAgent.indexOf("Firefox") >= 0;
2014-11-29 00:07:10 +00:00
function arrayBufferToString(buf) {
2019-10-16 11:17:47 +00:00
//http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
return String.fromCharCode.apply(null, new Uint8Array(buf));
2014-11-29 00:07:10 +00:00
}
var bandwidth;
var center_freq;
2019-10-16 11:17:47 +00:00
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;
2014-11-29 00:07:10 +00:00
var fft_size;
var fft_fps;
2019-10-16 11:17:47 +00:00
var fft_compression = "none";
var fft_codec = new sdrjs.ImaAdpcm();
var audio_compression = "none";
var waterfall_setup_done = 0;
var secondary_fft_size;
var audio_allowed;
2014-11-29 00:07:10 +00:00
/*function fade(something,from,to,time_ms,fps)
{
something.style.opacity=from;
something.fade_i=0;
n_of_iters=time_ms/(1000/fps);
change=(to-from)/(n_of_iters-1);
2014-11-29 00:07:10 +00:00
something.fade_timer=window.setInterval(
function(){
if(something.fade_i++<n_of_iters)
something.style.opacity=parseFloat(something.style.opacity)+change;
else
2014-11-29 00:07:10 +00:00
{something.style.opacity=to; window.clearInterval(something.fade_timer); }
},1000/fps);
}*/
2019-10-16 11:17:47 +00:00
var rx_photo_state = 1;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function e(what) {
return document.getElementById(what);
}
2014-11-29 00:07:10 +00:00
ios = /iPad|iPod|iPhone|Chrome/.test(navigator.userAgent);
is_chrome = /Chrome/.test(navigator.userAgent);
2016-03-26 23:47:26 +00:00
2019-10-16 11:17:47 +00:00
var rx_photo_height;
function init_rx_photo() {
2019-05-04 14:56:23 +00:00
var clip = e("webrx-top-photo-clip");
2019-10-16 11:17:47 +00:00
rx_photo_height = clip.clientHeight;
clip.style.maxHeight = rx_photo_height + "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);
$('#webrx-top-container').find('.openwebrx-photo-trigger').click(toggle_rx_photo);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var dont_toggle_rx_photo_flag = 0;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function dont_toggle_rx_photo() {
dont_toggle_rx_photo_flag = 1;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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()
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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";
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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";
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function updateVolume() {
volume = parseFloat(e("openwebrx-panel-volume").value) / 100;
}
2019-10-16 11:17:47 +00:00
function toggleMute() {
if (mute) {
mute = false;
e("openwebrx-mute-on").id = "openwebrx-mute-off";
e("openwebrx-mute-img").src = "static/gfx/openwebrx-speaker.png";
e("openwebrx-panel-volume").disabled = false;
e("openwebrx-panel-volume").style.opacity = 1.0;
e("openwebrx-panel-volume").value = volumeBeforeMute;
} else {
mute = true;
e("openwebrx-mute-off").id = "openwebrx-mute-on";
e("openwebrx-mute-img").src = "static/gfx/openwebrx-speaker-muted.png";
e("openwebrx-panel-volume").disabled = true;
e("openwebrx-panel-volume").style.opacity = 0.5;
volumeBeforeMute = e("openwebrx-panel-volume").value;
e("openwebrx-panel-volume").value = 0;
}
updateVolume();
2016-03-21 08:10:41 +00:00
}
2019-10-16 11:17:47 +00:00
function zoomInOneStep() {
zoom_set(zoom_level + 1);
}
2019-10-16 11:17:47 +00:00
function zoomOutOneStep() {
zoom_set(zoom_level - 1);
}
2019-10-16 11:17:47 +00:00
function zoomInTotal() {
zoom_set(zoom_levels.length - 1);
2016-03-20 15:06:10 +00:00
}
2019-10-16 11:17:47 +00:00
function zoomOutTotal() {
zoom_set(0);
2016-03-21 08:10:41 +00:00
}
2019-10-16 11:17:47 +00:00
function setSquelchToAuto() {
e("openwebrx-panel-squelch").value = (getLogSmeterValue(smeter_level) + 10).toString();
updateSquelch();
2016-03-20 15:06:10 +00:00
}
2019-10-16 11:17:47 +00:00
function updateSquelch() {
var sliderValue = parseInt(e("openwebrx-panel-squelch").value);
var outputValue = (sliderValue === parseInt(e("openwebrx-panel-squelch").min)) ? 0 : getLinearSmeterValue(sliderValue);
ws.send(JSON.stringify({"type": "dspcontrol", "params": {"squelch_level": outputValue}}));
}
function updateWaterfallColors(which) {
wfmax = e("openwebrx-waterfall-color-max");
wfmin = e("openwebrx-waterfall-color-min");
if (parseInt(wfmin.value) >= parseInt(wfmax.value)) {
if (!which) wfmin.value = (parseInt(wfmax.value) - 1).toString();
else wfmax.value = (parseInt(wfmin.value) + 1).toString();
}
waterfall_min_level = parseInt(wfmin.value);
waterfall_max_level = parseInt(wfmax.value);
}
function waterfallColorsDefault() {
waterfall_min_level = waterfall_min_level_default;
waterfall_max_level = waterfall_max_level_default;
e("openwebrx-waterfall-color-min").value = waterfall_min_level.toString();
e("openwebrx-waterfall-color-max").value = waterfall_max_level.toString();
}
function waterfallColorsAuto() {
e("openwebrx-waterfall-color-min").value = (waterfall_measure_minmax_min - waterfall_auto_level_margin[0]).toString();
e("openwebrx-waterfall-color-max").value = (waterfall_measure_minmax_max + waterfall_auto_level_margin[1]).toString();
updateWaterfallColors(0);
}
function setSmeterRelativeValue(value) {
if (value < 0) value = 0;
if (value > 1.0) value = 1.0;
var bar = e("openwebrx-smeter-bar");
var outer = e("openwebrx-smeter-outer");
bar.style.width = (outer.offsetWidth * value).toString() + "px";
bgRed = "linear-gradient(to top, #ff5939 , #961700)";
bgGreen = "linear-gradient(to top, #22ff2f , #008908)";
bgYellow = "linear-gradient(to top, #fff720 , #a49f00)";
bar.style.background = (value > 0.9) ? bgRed : ((value > 0.7) ? bgYellow : bgGreen);
}
function getLogSmeterValue(value) {
return 10 * Math.log10(value);
}
function getLinearSmeterValue(db_value) {
return Math.pow(10, db_value / 10);
}
function setSmeterAbsoluteValue(value) //the value that comes from `csdr squelch_and_smeter_cc`
2016-03-21 09:09:06 +00:00
{
2019-10-16 11:17:47 +00:00
var logValue = getLogSmeterValue(value);
var lowLevel = waterfall_min_level - 20;
var highLevel = waterfall_max_level + 20;
var percent = (logValue - lowLevel) / (highLevel - lowLevel);
setSmeterRelativeValue(percent);
e("openwebrx-smeter-db").innerHTML = logValue.toFixed(1) + " dB";
2016-03-21 09:09:06 +00:00
}
2019-10-16 11:17:47 +00:00
function typeInAnimation(element, timeout, what, onFinish) {
if (!what) {
onFinish();
return;
}
element.innerHTML += what[0];
window.setTimeout(function () {
typeInAnimation(element, timeout, what.substring(1), onFinish);
}, timeout);
}
2016-03-21 09:09:06 +00:00
2014-11-29 00:07:10 +00:00
// ========================================================
// ================= ANIMATION ROUTINES =================
// ========================================================
2019-10-16 11:17:47 +00:00
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);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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);
2014-11-29 00:07:10 +00:00
}
// ========================================================
// ================ DEMODULATOR ROUTINES ================
// ========================================================
2019-10-16 11:17:47 +00:00
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;
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
/*from_px-=env_bounding_line_w/2;
2014-11-29 00:07:10 +00:00
to_px+=env_bounding_line_w/2;*/
2019-10-16 11:17:47 +00:00
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
2014-11-29 00:07:10 +00:00
}
//******* class demodulator *******
// this can be used as a base class for ANY demodulator
2019-10-16 11:17:47 +00:00
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 () {
};
2014-11-29 00:07:10 +00:00
}
//ranges on filter envelope that can be dragged:
2019-10-16 11:17:47 +00:00
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()
2014-11-29 00:07:10 +00:00
//******* 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
2019-10-16 11:17:47 +00:00
demodulator_response_time = 50;
2014-11-29 00:07:10 +00:00
//in ms; if we don't limit the number of SETs sent to the server, audio will underrun (possibly output buffer is cleared on SETs in GNU Radio
2019-10-16 11:17:47 +00:00
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_server_output_rate / 2) - 1, //audio_context.sampleRate/2,
low_cut_limit: (-audio_server_output_rate / 2) + 1 //-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 === "dmr" || subtype === "ysf") {
this.low_cut = -4000;
this.high_cut = 4000;
}
else if (subtype === "dstar" || subtype === "nxdn") {
this.low_cut = -3250;
this.high_cut = 3250;
}
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
params = {
"low_cut": this.low_cut,
"high_cut": this.high_cut,
"offset_freq": this.offset_frequency
};
if (first_time) params.mod = this.server_mod;
ws.send(JSON.stringify({"type": "dspcontrol", "params": params}));
};
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);
};
this.envelope.dragged_range = demodulator.draggable_ranges.none;
// 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)
2014-11-29 00:07:10 +00:00
{
//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;*/
2019-10-16 11:17:47 +00:00
//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;
};
2019-10-16 11:17:47 +00:00
this.envelope.drag_end = function (x) { //in this demodulator we've already changed values in the drag_move() function so we shouldn't do too much here.
demodulator_buttons_update();
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;
};
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
demodulator_default_analog.prototype = new demodulator();
2014-11-29 00:07:10 +00:00
function mkenvelopes(visible_range) //called from mkscale
{
2019-10-16 11:17:47 +00:00
scale_ctx.clearRect(0, 0, scale_ctx.canvas.width, 22); //clear the upper part of the canvas (where filter envelopes reside)
for (var i = 0; i < demodulators.length; i++) {
demodulators[i].envelope.draw(visible_range);
}
if (demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function demodulator_remove(which) {
demodulators[which].stop();
demodulators.splice(which, 1);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function demodulator_add(what) {
demodulators.push(what);
mkenvelopes(get_visible_freq_range());
2014-11-29 00:07:10 +00:00
}
last_analog_demodulator_subtype = 'nfm';
last_digital_demodulator_subtype = 'bpsk31';
2019-10-16 11:17:47 +00:00
function demodulator_analog_replace(subtype, for_digital) { //this function should only exist until the multi-demodulator capability is added
if (!(typeof for_digital !== "undefined" && for_digital && secondary_demod)) {
secondary_demod_close_window();
secondary_demod_listbox_update();
}
last_analog_demodulator_subtype = subtype;
2019-10-16 11:17:47 +00:00
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));
demodulator_buttons_update();
update_digitalvoice_panels("openwebrx-panel-metadata-" + 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());
2019-09-28 00:21:29 +00:00
$("#webrx-actual-freq").html(format_frequency("{x} MHz", center_freq + to_what, 1e6, 4));
2014-11-29 00:07:10 +00:00
}
// ========================================================
// =================== SCALE ROUTINES ===================
// ========================================================
var scale_ctx;
var scale_canvas;
2019-10-16 11:17:47 +00:00
function scale_setup() {
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 frequency_container = e("openwebrx-frequency-container");
frequency_container.addEventListener("mousemove", frequency_container_mousemove, false);
}
var scale_canvas_drag_params = {
mouse_down: false,
drag: false,
start_x: 0,
key_modifiers: {shiftKey: false, altKey: false, ctrlKey: false}
2014-11-29 00:07:10 +00:00
};
2019-10-16 11:17:47 +00:00
function scale_canvas_mousedown(evt) {
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();
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale_offset_freq_from_px(x, visible_range) {
if (typeof visible_range === "undefined") visible_range = get_visible_freq_range();
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));
}
2014-11-29 00:07:10 +00:00
}
function frequency_container_mousemove(evt) {
2019-10-16 11:17:47 +00:00
var frequency = center_freq + scale_offset_freq_from_px(evt.pageX);
e("webrx-mouse-freq").innerHTML = format_frequency("{x} MHz", frequency, 1e6, 4);
}
2019-10-16 11:17:47 +00:00
function scale_canvas_end_drag(x) {
scale_canvas.style.cursor = "default";
scale_canvas_drag_params.drag = false;
scale_canvas_drag_params.mouse_down = false;
var event_handled = false;
for (var i = 0; i < demodulators.length; i++) event_handled |= demodulators[i].envelope.drag_end(x);
if (!event_handled) demodulator_set_offset_frequency(0, scale_offset_freq_from_px(x));
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale_canvas_mouseup(evt) {
scale_canvas_end_drag(evt.pageX);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function scale_px_from_freq(f, range) {
return Math.round(((f - range.start) / range.bw) * canvas_container.clientWidth);
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
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
}
2014-11-29 00:07:10 +00:00
];
2019-10-16 11:17:47 +00:00
var scale_min_space_bw_texts = 50;
var scale_min_space_bw_small_markers = 7;
function get_scale_mark_spacing(range) {
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) && (x < text_measured.width / 2)) { //if this is the first overall marker when zoomed out... and if it would be clipped off the screen...
if (scale_px_from_freq(marker_hz + spacing.smallbw * spacing.ratio, range) - text_measured.width >= scale_min_space_bw_texts) { //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "left";
scale_ctx.fillText(text_to_draw, 0, text_h_pos);
}
}
else if (zoom_level === 0 && (range.end - spacing.smallbw * spacing.ratio < marker_hz) && (x > window.innerWidth - text_measured.width / 2)) { // if this is the last overall marker when zoomed out... and if it would be clipped off the screen...
if (window.innerWidth - text_measured.width - scale_px_from_freq(marker_hz - spacing.smallbw * spacing.ratio, range) >= scale_min_space_bw_texts) { //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "right";
scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
}
}
else scale_ctx.fillText(text_to_draw, x, text_h_pos); //draw text normally
}
else { //small marker
scale_ctx.lineWidth = 2;
scale_ctx.lineTo(x, 22 + 8);
}
marker_hz += spacing.smallbw;
scale_ctx.stroke();
}
if (zoom_level !== 0) { // if zoomed, we don't want the texts to disappear because their markers can't be seen
// on the left side
scale_ctx.textAlign = "center";
var f = first_large - spacing.smallbw * spacing.ratio;
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);
}
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function resize_scale() {
2019-09-28 05:36:28 +00:00
ratio = window.devicePixelRatio || 1;
var w = window.innerWidth;
var h = 47;
scale_canvas.style.width = w + "px";
scale_canvas.style.height = h + "px";
w *= ratio;
h *= ratio;
2019-10-16 11:17:47 +00:00
scale_canvas.width = w;
2019-09-28 05:36:28 +00:00
scale_canvas.height = h;
scale_ctx.scale(ratio, ratio);
2019-10-16 11:17:47 +00:00
mkscale();
bookmarks.position();
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas_get_freq_offset(relativeX) {
rel = (relativeX / canvases[0].clientWidth);
return Math.round((bandwidth * rel) - (bandwidth / 2));
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas_get_frequency(relativeX) {
return center_freq + canvas_get_freq_offset(relativeX);
2014-11-29 00:07:10 +00:00
}
/*function canvas_format_frequency(relativeX)
{
return (canvas_get_frequency(relativeX)/1e6).toFixed(3)+" MHz";
}*/
2019-10-16 11:17:47 +00:00
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;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas_mousemove(evt) {
if (!waterfall_setup_done) return;
relativeX = get_relative_x(evt);
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;
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;
}
resize_canvases(false);
canvas_drag_last_x = evt.pageX;
canvas_drag_last_y = evt.pageY;
mkscale();
bookmarks.position();
}
}
else e("webrx-mouse-freq").innerHTML = format_frequency("{x} MHz", canvas_get_frequency(relativeX), 1e6, 4);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas_container_mouseleave(evt) {
canvas_end_drag();
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function canvas_mouseup(evt) {
if (!waterfall_setup_done) return;
relativeX = get_relative_x(evt);
if (!canvas_drag) {
demodulator_set_offset_frequency(0, canvas_get_freq_offset(relativeX));
}
else {
canvas_end_drag();
}
canvas_mouse_down = false;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function canvas_end_drag() {
canvas_container.style.cursor = "crosshair";
canvas_mouse_down = false;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function zoom_center_where_calc(screenposX) {
//return (screenposX-(window.innerWidth-canvas_container.clientWidth))/canvas_container.clientWidth;
return screenposX / canvas_container.clientWidth;
2014-11-29 00:07:10 +00:00
}
function get_relative_x(evt) {
2019-10-16 11:17:47 +00:00
var relativeX = (evt.offsetX) ? evt.offsetX : evt.layerX;
if ($(evt.target).closest(canvas_container).length) return relativeX;
2019-10-16 11:17:47 +00:00
// compensate for the frequency scale, since that is not resized by the browser.
return relativeX - zoom_offset_px;
}
2019-10-16 11:17:47 +00:00
function canvas_mousewheel(evt) {
if (!waterfall_setup_done) return;
var relativeX = get_relative_x(evt);
var dir = (evt.deltaY / Math.abs(evt.deltaY)) > 0;
zoom_step(dir, relativeX, zoom_center_where_calc(evt.pageX));
evt.preventDefault();
}
zoom_max_level_hps = 33; //Hz/pixel
zoom_levels_count = 14;
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;
smeter_level = 0;
function mkzoomlevels() {
zoom_levels = [1];
maxc = get_zoom_coeff_from_hps(zoom_max_level_hps);
if (maxc < 1) return;
// logarithmic interpolation
zoom_ratio = Math.pow(maxc, 1 / zoom_levels_count);
for (i = 1; i < zoom_levels_count; i++)
zoom_levels.push(Math.pow(zoom_ratio, i));
}
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;
//console.log(zoom_center_where, zoom_center_rel, where);
resize_canvases(true);
mkscale();
bookmarks.position();
}
function zoom_set(level) {
if (!(level >= 0 && level <= zoom_levels.length - 1)) return;
level = parseInt(level);
zoom_level = level;
//zoom_center_rel=canvas_get_freq_offset(-canvases[0].offsetLeft+canvas_container.clientWidth/2); //zoom to screen center instead of demod envelope
zoom_center_rel = demodulators[0].offset_frequency;
zoom_center_where = 0.5 + (zoom_center_rel / bandwidth); //this is a kind of hack
console.log(zoom_center_where, zoom_center_rel, -canvases[0].offsetLeft + canvas_container.clientWidth / 2);
resize_canvases(true);
mkscale();
bookmarks.position();
}
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;
var numHeight;
mathbox_container.style.height = canvas_container.style.height = (numHeight = window.innerHeight - e("webrx-top-container").clientHeight - e("openwebrx-scale-container").clientHeight).toString() + "px";
if (mathbox) {
//mathbox.three.camera.aspect = document.body.offsetWidth / numHeight;
//mathbox.three.camera.updateProjectionMatrix();
mathbox.three.renderer.setSize(document.body.offsetWidth, numHeight);
console.log(document.body.offsetWidth, numHeight);
}
2016-03-21 08:10:41 +00:00
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
audio_server_output_rate = 11025;
audio_client_resampling_factor = 4;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function audio_calculate_resampling(targetRate) { //both at the server and the client
output_range_max = 12000;
output_range_min = 8000;
i = 1;
while (true) {
audio_server_output_rate = Math.floor(targetRate / i);
if (audio_server_output_rate < output_range_min) {
audio_client_resampling_factor = audio_server_output_rate = 0;
divlog("Your audio card sampling rate (" + targetRate.toString() + ") is not supported.<br />Please change your operating system default settings in order to fix this.", 1);
}
if (audio_server_output_rate >= output_range_min && audio_server_output_rate <= output_range_max) break; //okay, we're done
i++;
}
audio_client_resampling_factor = i;
console.log("audio_calculate_resampling() :: " + audio_client_resampling_factor.toString() + ", " + audio_server_output_rate.toString());
2015-09-30 14:06:30 +00:00
}
2019-10-16 11:17:47 +00:00
debug_ws_data_received = 0;
debug_ws_time_start = 0;
2019-10-16 11:17:47 +00:00
max_clients_num = 0;
2019-09-21 13:19:10 +00:00
client_num = 0;
2019-10-04 20:01:07 +00:00
var currentprofile;
2019-10-16 11:17:47 +00:00
var COMPRESS_FFT_PAD_N = 10; //should be the same as in csdr.c
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function on_ws_recv(evt) {
if (typeof evt.data === 'string') {
2019-05-04 14:56:23 +00:00
// text messages
2019-05-07 18:20:12 +00:00
debug_ws_data_received += evt.data.length / 1000;
2019-10-16 11:17:47 +00:00
if (evt.data.substr(0, 16) === "CLIENT DE SERVER") {
divlog("Server acknowledged WebSocket connection.");
} else {
try {
json = JSON.parse(evt.data);
switch (json.type) {
2019-05-04 14:56:23 +00:00
case "config":
config = json.value;
window.waterfall_colors = config.waterfall_colors;
window.waterfall_min_level_default = config.waterfall_min_level;
window.waterfall_max_level_default = config.waterfall_max_level;
window.waterfall_auto_level_margin = config.waterfall_auto_level_margin;
waterfallColorsDefault();
2019-10-16 11:17:47 +00:00
window.starting_mod = config.start_mod;
window.starting_offset_frequency = config.start_offset_freq;
2019-05-04 21:11:13 +00:00
window.audio_buffering_fill_to = config.client_audio_buffer_size;
2019-05-04 14:56:23 +00:00
bandwidth = config.samp_rate;
center_freq = config.center_freq + config.lfo_offset;
2019-05-04 14:56:23 +00:00
fft_size = config.fft_size;
2019-10-16 11:17:47 +00:00
fft_fps = config.fft_fps;
audio_compression = config.audio_compression;
divlog("Audio stream is " + ((audio_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
fft_compression = config.fft_compression;
divlog("FFT stream is " + ((fft_compression === "adpcm") ? "compressed" : "uncompressed") + ".");
max_clients_num = config.max_clients;
progressbar_set(e("openwebrx-bar-clients"), client_num / max_clients_num, "Clients [" + client_num + "]", client_num > max_clients_num * 0.85);
2019-05-15 17:51:50 +00:00
mathbox_waterfall_colors = config.mathbox_waterfall_colors;
2019-10-16 11:17:47 +00:00
mathbox_waterfall_frequency_resolution = config.mathbox_waterfall_frequency_resolution;
mathbox_waterfall_history_length = config.mathbox_waterfall_history_length;
waterfall_init();
audio_preinit();
bookmarks.loadLocalBookmarks();
if (audio_allowed) {
if (audio_initialized) {
initialize_demodulator();
} else {
audio_init();
}
}
waterfall_clear();
2019-10-04 20:01:07 +00:00
currentprofile = config.profile_id;
2019-10-16 11:17:47 +00:00
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
break;
case "secondary_config":
window.secondary_fft_size = json.value.secondary_fft_size;
window.secondary_bw = json.value.secondary_bw;
window.if_samp_rate = json.value.if_samp_rate;
secondary_demod_init_canvases();
2019-10-16 11:17:47 +00:00
break;
2019-05-05 15:52:26 +00:00
case "receiver_details":
var r = json.value;
e('webrx-rx-title').innerHTML = r.receiver_name;
e('webrx-rx-desc').innerHTML = r.receiver_location + ' | Loc: ' + r.locator + ', ASL: ' + r.receiver_asl + ' m, <a href="https://www.google.hu/maps/place/' + r.receiver_gps[0] + ',' + r.receiver_gps[1] + '" target="_blank" onclick="dont_toggle_rx_photo();">[maps]</a>';
2019-05-05 15:52:26 +00:00
e('webrx-rx-photo-title').innerHTML = r.photo_title;
e('webrx-rx-photo-desc').innerHTML = r.photo_desc;
2019-10-16 11:17:47 +00:00
break;
2019-05-05 14:17:55 +00:00
case "smeter":
2019-05-12 14:02:49 +00:00
smeter_level = json.value;
setSmeterAbsoluteValue(smeter_level);
2019-10-16 11:17:47 +00:00
break;
2019-05-05 15:34:40 +00:00
case "cpuusage":
2019-10-16 11:17:47 +00:00
var server_cpu_usage = json.value;
progressbar_set(e("openwebrx-bar-server-cpu"), server_cpu_usage, "Server CPU [" + Math.round(server_cpu_usage * 100) + "%]", server_cpu_usage > 85);
break;
2019-05-10 20:47:40 +00:00
case "clients":
2019-05-15 17:51:50 +00:00
client_num = json.value;
2019-10-16 11:17:47 +00:00
progressbar_set(e("openwebrx-bar-clients"), client_num / max_clients_num, "Clients [" + client_num + "]", client_num > max_clients_num * 0.85);
break;
2019-05-10 14:14:16 +00:00
case "profiles":
var listbox = e("openwebrx-sdr-profiles-listbox");
2019-10-16 11:17:47 +00:00
listbox.innerHTML = json.value.map(function (profile) {
2019-05-10 14:14:16 +00:00
return '<option value="' + profile.id + '">' + profile.name + "</option>";
}).join("");
2019-10-04 20:01:07 +00:00
if (currentprofile) {
2019-10-16 11:17:47 +00:00
$('#openwebrx-sdr-profiles-listbox').val(currentprofile);
}
break;
case "features":
for (var feature in json.value) {
$('[data-feature="' + feature + '"')[json.value[feature] ? "show" : "hide"]();
}
break;
case "metadata":
update_metadata(json.value);
break;
case "wsjt_message":
update_wsjt_panel(json.value);
break;
case "dial_frequencies":
var as_bookmarks = json.value.map(function (d) {
return {
name: d.mode.toUpperCase(),
digital_modulation: d.mode,
frequency: d.frequency
};
});
bookmarks.replace_bookmarks(as_bookmarks, 'dial_frequencies');
break;
case "aprs_data":
update_packet_panel(json.value);
break;
case "bookmarks":
bookmarks.replace_bookmarks(json.value, "server");
break;
case "sdr_error":
divlog(json.value, true);
break;
2019-05-04 14:56:23 +00:00
default:
2019-05-05 15:34:40 +00:00
console.warn('received message of unknown type: ' + json.type);
2019-10-16 11:17:47 +00:00
}
} catch (e) {
// don't lose exception
console.error(e)
}
}
2019-05-04 14:56:23 +00:00
} else if (evt.data instanceof ArrayBuffer) {
// binary messages
2019-05-07 18:20:12 +00:00
debug_ws_data_received += evt.data.byteLength / 1000;
2019-10-16 11:17:47 +00:00
type = new Uint8Array(evt.data, 0, 1)[0];
data = evt.data.slice(1);
2019-05-04 18:26:11 +00:00
switch (type) {
case 1:
2019-05-04 21:11:13 +00:00
// FFT data
2019-10-16 11:17:47 +00:00
if (fft_compression === "none") {
2019-10-05 18:38:58 +00:00
waterfall_add(new Float32Array(data));
2019-10-16 11:17:47 +00:00
} else if (fft_compression === "adpcm") {
2019-05-04 18:26:11 +00:00
fft_codec.reset();
2019-10-16 11:17:47 +00:00
var waterfall_i16 = fft_codec.decode(new Uint8Array(data));
var waterfall_f32 = new Float32Array(waterfall_i16.length - COMPRESS_FFT_PAD_N);
for (var i = 0; i < waterfall_i16.length; i++) waterfall_f32[i] = waterfall_i16[i + COMPRESS_FFT_PAD_N] / 100;
2019-10-05 18:38:58 +00:00
waterfall_add(waterfall_f32);
}
2019-10-16 11:17:47 +00:00
break;
2019-05-04 21:11:13 +00:00
case 2:
// audio data
var audio_data;
2019-10-16 11:17:47 +00:00
if (audio_compression === "adpcm") {
2019-05-04 21:11:13 +00:00
audio_data = new Uint8Array(data);
} else {
audio_data = new Int16Array(data);
2017-01-20 12:26:09 +00:00
}
2019-05-04 21:11:13 +00:00
audio_prepare(audio_data);
audio_buffer_current_size_debug += audio_data.length;
audio_buffer_all_size_debug += audio_data.length;
2019-10-16 11:17:47 +00:00
if (!(ios || is_chrome) && (audio_initialized === 0 && audio_prepared_buffers.length > audio_buffering_fill_to)) audio_init()
break;
case 3:
// secondary FFT
2019-10-16 11:17:47 +00:00
if (fft_compression === "none") {
2019-10-05 18:38:58 +00:00
secondary_demod_waterfall_add(new Float32Array(data));
2019-10-16 11:17:47 +00:00
} else if (fft_compression === "adpcm") {
fft_codec.reset();
2019-10-16 11:17:47 +00:00
var waterfall_i16 = fft_codec.decode(new Uint8Array(data));
var waterfall_f32 = new Float32Array(waterfall_i16.length - COMPRESS_FFT_PAD_N);
for (var i = 0; i < waterfall_i16.length; i++) waterfall_f32[i] = waterfall_i16[i + COMPRESS_FFT_PAD_N] / 100;
2019-10-05 18:38:58 +00:00
secondary_demod_waterfall_add(waterfall_f32); //TODO digimodes
}
2019-10-16 11:17:47 +00:00
break;
case 4:
// secondary demod
secondary_demod_push_data(arrayBufferToString(data));
2019-10-16 11:17:47 +00:00
break;
2019-05-04 18:26:11 +00:00
default:
console.warn('unknown type of binary message: ' + type)
}
}
2014-11-29 00:07:10 +00:00
}
2019-05-30 14:12:13 +00:00
function update_metadata(meta) {
if (meta.protocol) switch (meta.protocol) {
case 'DMR':
if (meta.slot) {
2019-10-16 11:17:47 +00:00
var el = $("#openwebrx-panel-metadata-dmr").find(".openwebrx-dmr-timeslot-panel").get(meta.slot);
2019-06-09 13:15:27 +00:00
var id = "";
var name = "";
2019-06-09 15:39:15 +00:00
var target = "";
2019-06-09 20:27:35 +00:00
var group = false;
2019-06-15 10:30:04 +00:00
$(el)[meta.sync ? "addClass" : "removeClass"]("sync");
2019-10-16 11:17:47 +00:00
if (meta.sync && meta.sync === "voice") {
2019-06-09 13:15:27 +00:00
id = (meta.additional && meta.additional.callsign) || meta.source || "";
name = (meta.additional && meta.additional.fname) || "";
2019-10-16 11:17:47 +00:00
if (meta.type === "group") {
2019-06-09 20:27:35 +00:00
target = "Talkgroup: ";
group = true;
2019-05-30 15:19:46 +00:00
}
2019-10-16 11:17:47 +00:00
if (meta.type === "direct") target = "Direct: ";
2019-06-09 15:39:15 +00:00
target += meta.target || "";
2019-06-09 13:15:27 +00:00
$(el).addClass("active");
} else {
$(el).removeClass("active");
2019-05-30 15:19:46 +00:00
}
2019-06-09 13:15:27 +00:00
$(el).find(".openwebrx-dmr-id").text(id);
$(el).find(".openwebrx-dmr-name").text(name);
2019-06-09 15:39:15 +00:00
$(el).find(".openwebrx-dmr-target").text(target);
2019-06-09 20:27:35 +00:00
$(el).find(".openwebrx-meta-user-image")[group ? "addClass" : "removeClass"]("group");
2019-06-10 19:30:46 +00:00
} else {
2019-06-15 17:10:33 +00:00
clear_metadata();
}
break;
case 'YSF':
2019-06-09 15:39:15 +00:00
var el = $("#openwebrx-panel-metadata-ysf");
var mode = " "
var source = "";
var up = "";
var down = "";
2019-10-16 11:17:47 +00:00
if (meta.mode && meta.mode !== "") {
2019-06-09 15:39:15 +00:00
mode = "Mode: " + meta.mode;
source = meta.source || "";
2019-07-06 13:04:39 +00:00
if (meta.lat && meta.lon && meta.source) {
source = "<a class=\"openwebrx-maps-pin\" href=\"/map?callsign=" + meta.source + "\" target=\"_blank\"></a>" + source;
}
2019-06-09 15:39:15 +00:00
up = meta.up ? "Up: " + meta.up : "";
down = meta.down ? "Down: " + meta.down : "";
$(el).find(".openwebrx-meta-slot").addClass("active");
} else {
$(el).find(".openwebrx-meta-slot").removeClass("active");
}
2019-06-09 15:39:15 +00:00
$(el).find(".openwebrx-ysf-mode").text(mode);
$(el).find(".openwebrx-ysf-source").html(source);
2019-06-09 15:39:15 +00:00
$(el).find(".openwebrx-ysf-up").text(up);
$(el).find(".openwebrx-ysf-down").text(down);
break;
2019-06-09 17:12:37 +00:00
} else {
2019-06-15 17:10:33 +00:00
clear_metadata();
}
}
2019-07-14 12:33:30 +00:00
function html_escape(input) {
return $('<div/>').text(input).html()
}
function update_wsjt_panel(msg) {
2019-10-16 11:17:47 +00:00
var $b = $('#openwebrx-panel-wsjt-message').find('tbody');
var t = new Date(msg['timestamp']);
2019-10-16 11:17:47 +00:00
var pad = function (i) {
return ('' + i).padStart(2, "0");
};
var linkedmsg = msg['msg'];
2019-07-20 11:38:25 +00:00
if (['FT8', 'JT65', 'JT9', 'FT4'].indexOf(msg['mode']) >= 0) {
2019-07-14 12:33:30 +00:00
var matches = linkedmsg.match(/(.*\s[A-Z0-9]+\s)([A-R]{2}[0-9]{2})$/);
2019-10-16 11:17:47 +00:00
if (matches && matches[2] !== 'RR73') {
2019-07-14 12:33:30 +00:00
linkedmsg = html_escape(matches[1]) + '<a href="/map?locator=' + matches[2] + '" target="_blank">' + matches[2] + '</a>';
} else {
linkedmsg = html_escape(linkedmsg);
}
2019-10-16 11:17:47 +00:00
} else if (msg['mode'] === 'WSPR') {
2019-07-14 12:33:30 +00:00
var matches = linkedmsg.match(/([A-Z0-9]*\s)([A-R]{2}[0-9]{2})(\s[0-9]+)/);
if (matches) {
linkedmsg = html_escape(matches[1]) + '<a href="/map?locator=' + matches[2] + '" target="_blank">' + matches[2] + '</a>' + html_escape(matches[3]);
} else {
linkedmsg = html_escape(linkedmsg);
}
}
$b.append($(
'<tr data-timestamp="' + msg['timestamp'] + '">' +
2019-10-16 11:17:47 +00:00
'<td>' + pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds()) + '</td>' +
'<td class="decimal">' + msg['db'] + '</td>' +
'<td class="decimal">' + msg['dt'] + '</td>' +
'<td class="decimal freq">' + msg['freq'] + '</td>' +
'<td class="message">' + linkedmsg + '</td>' +
'</tr>'
));
$b.scrollTop($b[0].scrollHeight);
}
var digital_removal_interval;
// remove old wsjt messages in fixed intervals
function init_digital_removal_timer() {
if (digital_removal_interval) clearInterval(digital_removal_interval);
2019-10-16 11:17:47 +00:00
digital_removal_interval = setInterval(function () {
['#openwebrx-panel-wsjt-message', '#openwebrx-panel-packet-message'].forEach(function (root) {
var $elements = $(root + ' tbody tr');
// limit to 1000 entries in the list since browsers get laggy at some point
var toRemove = $elements.length - 1000;
if (toRemove <= 0) return;
$elements.slice(0, toRemove).remove();
});
}, 15000);
}
function update_packet_panel(msg) {
2019-10-16 11:17:47 +00:00
var $b = $('#openwebrx-panel-packet-message').find('tbody');
var pad = function (i) {
return ('' + i).padStart(2, "0");
};
2019-10-16 11:17:47 +00:00
if (msg.type && msg.type === 'thirdparty' && msg.data) {
msg = msg.data;
}
var source = msg.source;
if (msg.type) {
2019-10-16 11:17:47 +00:00
if (msg.type === 'item') {
source = msg.item;
}
2019-10-16 11:17:47 +00:00
if (msg.type === 'object') {
source = msg.object;
}
}
var timestamp = '';
if (msg.timestamp) {
var t = new Date(msg.timestamp);
timestamp = pad(t.getUTCHours()) + pad(t.getUTCMinutes()) + pad(t.getUTCSeconds())
}
var link = '';
var classes = [];
var styles = {};
var overlay = '';
2019-10-16 11:17:47 +00:00
var stylesToString = function (s) {
return $.map(s, function (value, key) {
return key + ':' + value + ';'
}).join('')
};
if (msg.symbol) {
classes.push('aprs-symbol');
2019-10-16 11:17:47 +00:00
classes.push('aprs-symboltable-' + (msg.symbol.table === '/' ? 'normal' : 'alternate'));
styles['background-position-x'] = -(msg.symbol.index % 16) * 15 + 'px';
styles['background-position-y'] = -Math.floor(msg.symbol.index / 16) * 15 + 'px';
2019-10-16 11:17:47 +00:00
if (msg.symbol.table !== '/' && msg.symbol.table !== '\\') {
s = {}
s['background-position-x'] = -(msg.symbol.tableindex % 16) * 15 + 'px';
s['background-position-y'] = -Math.floor(msg.symbol.tableindex / 16) * 15 + 'px';
2019-10-16 11:17:47 +00:00
overlay = '<div class="aprs-symbol aprs-symboltable-overlay" style="' + stylesToString(s) + '"></div>';
}
} else if (msg.lat && msg.lon) {
classes.push('openwebrx-maps-pin');
}
var attrs = [
'class="' + classes.join(' ') + '"',
2019-10-16 11:17:47 +00:00
'style="' + stylesToString(styles) + '"'
].join(' ');
if (msg.lat && msg.lon) {
link = '<a ' + attrs + ' href="/map?callsign=' + source + '" target="_blank">' + overlay + '</a>';
} else {
link = '<div ' + attrs + '>' + overlay + '</div>'
}
$b.append($(
'<tr>' +
2019-10-16 11:17:47 +00:00
'<td>' + timestamp + '</td>' +
'<td class="callsign">' + source + '</td>' +
'<td class="coord">' + link + '</td>' +
'<td class="message">' + (msg.comment || msg.message || '') + '</td>' +
'</tr>'
));
$b.scrollTop($b[0].scrollHeight);
}
function update_digitalvoice_panels(showing) {
2019-10-16 11:17:47 +00:00
$(".openwebrx-meta-panel").each(function (_, p) {
toggle_panel(p.id, p.id === showing);
2019-06-09 15:39:15 +00:00
});
2019-06-15 17:10:33 +00:00
clear_metadata();
}
function clear_metadata() {
2019-06-15 17:10:33 +00:00
$(".openwebrx-meta-panel .openwebrx-meta-autoclear").text("");
$(".openwebrx-meta-slot").removeClass("active").removeClass("sync");
$(".openwebrx-dmr-timeslot-panel").removeClass("muted");
}
2019-10-16 11:17:47 +00:00
function add_problem(what) {
problems_span = e("openwebrx-problems");
for (var i = 0; i < problems_span.children.length; i++) if (problems_span.children[i].innerHTML === what) return;
new_span = document.createElement("span");
new_span.innerHTML = what;
problems_span.appendChild(new_span);
window.setTimeout(function (ps, ns) {
ps.removeChild(ns);
}, 1000, problems_span, new_span);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
waterfall_measure_minmax = false;
waterfall_measure_minmax_now = false;
waterfall_measure_minmax_min = 1e100;
waterfall_measure_minmax_max = -1e100;
2019-10-16 11:17:47 +00:00
function waterfall_measure_minmax_do(what) {
waterfall_measure_minmax_min = Math.min(waterfall_measure_minmax_min, Math.min.apply(Math, what));
waterfall_measure_minmax_max = Math.max(waterfall_measure_minmax_max, Math.max.apply(Math, what));
}
2019-10-16 11:17:47 +00:00
function waterfall_measure_minmax_print() {
console.log("Waterfall | min = " + waterfall_measure_minmax_min.toString() + " dB | max = " + waterfall_measure_minmax_max.toString() + " dB");
}
2019-10-16 11:17:47 +00:00
function on_ws_opened() {
ws.send("SERVER DE CLIENT client=openwebrx.js type=receiver");
divlog("WebSocket opened to " + ws_url);
debug_ws_data_received = 0;
debug_ws_time_start = new Date().getTime();
2019-10-16 11:17:47 +00:00
reconnect_timeout = false;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var was_error = 0;
2019-10-16 11:17:47 +00:00
function divlog(what, is_error) {
is_error = !!is_error;
was_error |= is_error;
if (is_error) {
what = "<span class=\"webrx-error\">" + what + "</span>";
if (e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
}
e("openwebrx-debugdiv").innerHTML += what + "<br />";
//var wls=e("openwebrx-log-scroll");
//wls.scrollTop=wls.scrollHeight; //scroll to bottom
$(".nano").nanoScroller();
2019-10-16 11:17:47 +00:00
$(".nano").nanoScroller({scroll: 'bottom'});
2014-11-29 00:07:10 +00:00
}
var audio_context;
2019-10-16 11:17:47 +00:00
var audio_initialized = 0;
var volume = 1.0;
var volumeBeforeMute = 100.0;
var mute = false;
2014-11-29 00:07:10 +00:00
var audio_received = Array();
var audio_buffer_index = 0;
2015-09-30 14:06:30 +00:00
var audio_resampler;
2019-10-16 11:17:47 +00:00
var audio_codec = new sdrjs.ImaAdpcm();
2014-11-29 00:07:10 +00:00
var audio_node;
//var audio_received_sample_rate = 48000;
var audio_input_buffer_size;
// Optimalise these if audio lags or is choppy:
var audio_buffer_size;
2019-10-16 11:17:47 +00:00
var audio_buffer_maximal_length_sec = 3; //actual number of samples are calculated from sample rate
var audio_buffer_decrease_to_on_overrun_sec = 2.2;
var audio_flush_interval_ms = 500; //the interval in which audio_flush() is called
2014-11-29 00:07:10 +00:00
var audio_prepared_buffers = Array();
var audio_rebuffer;
var audio_last_output_buffer;
2014-11-29 00:07:10 +00:00
var audio_last_output_offset = 0;
var audio_buffering = false;
//var audio_buffering_fill_to=4; //on audio underrun we wait until this n*audio_buffer_size samples are present
2019-10-16 11:17:47 +00:00
//tnx to the hint from HA3FLT, now we have about half the response time! (original value: 10)
2019-10-16 11:17:47 +00:00
function gain_ff(gain_value, data) //great! solved clicking! will have to move to sdr.js
{
2019-10-16 11:17:47 +00:00
for (var i = 0; i < data.length; i++)
data[i] *= gain_value;
return data;
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function audio_prepare(data) {
2019-10-16 11:17:47 +00:00
//audio_rebuffer.push(sdrjs.ConvertI16_F(data));//no resampling
//audio_rebuffer.push(audio_resampler.process(sdrjs.ConvertI16_F(data)));//resampling without ADPCM
if (audio_compression === "none")
audio_rebuffer.push(audio_resampler.process(gain_ff(volume, sdrjs.ConvertI16_F(data))));//resampling without ADPCM
else if (audio_compression === "adpcm")
audio_rebuffer.push(audio_resampler.process(gain_ff(volume, sdrjs.ConvertI16_F(audio_codec.decode(data))))); //resampling & ADPCM
else return;
2019-10-16 11:17:47 +00:00
//console.log("prepare",data.length,audio_rebuffer.remaining());
while (audio_rebuffer.remaining()) {
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if (audio_buffering && audio_prepared_buffers.length > audio_buffering_fill_to) {
console.log("buffers now: " + audio_prepared_buffers.length.toString());
audio_buffering = false;
}
}
2019-10-16 11:17:47 +00:00
function audio_prepare_without_resampler(data) {
audio_rebuffer.push(sdrjs.ConvertI16_F(data));
console.log("prepare", data.length, audio_rebuffer.remaining());
while (audio_rebuffer.remaining()) {
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if (audio_buffering && audio_prepared_buffers.length > audio_buffering_fill_to) audio_buffering = false;
}
2019-10-16 11:17:47 +00:00
function audio_prepare_old(data) {
//console.log("audio_prepare :: "+data.length.toString());
//console.log("data.len = "+data.length.toString());
var dopush = function () {
console.log(audio_last_output_buffer);
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++;
};
var original_data_length = data.length;
var f32data = new Float32Array(data.length);
for (var i = 0; i < data.length; i++) f32data[i] = data[i] / 32768; //convert_i16_f
data = audio_resampler.process(f32data);
console.log(data, data.length, original_data_length);
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];
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];
}
}
var silence = new Float32Array(4096);
function audio_onprocess(e) {
if (audio_buffering) {
2016-10-16 19:40:03 +00:00
e.outputBuffer.copyToChannel(silence, 0);
return;
}
2019-10-16 11:17:47 +00:00
if (audio_prepared_buffers.length === 0) {
2016-10-16 19:40:03 +00:00
audio_buffer_progressbar_update();
/*add_problem("audio underrun");*/
2019-10-16 11:17:47 +00:00
audio_buffering = true;
2016-10-16 19:40:03 +00:00
e.outputBuffer.copyToChannel(silence, 0);
} else {
var buf = audio_prepared_buffers.shift();
2019-10-16 11:17:47 +00:00
e.outputBuffer.copyToChannel(buf, 0);
2016-10-16 19:40:03 +00:00
}
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var audio_buffer_progressbar_update_disabled = false;
2019-10-16 11:17:47 +00:00
var audio_buffer_total_average_level = 0;
var audio_buffer_total_average_level_length = 0;
2015-09-30 14:06:30 +00:00
var audio_overrun_cnt = 0;
var audio_underrun_cnt = 0;
2019-10-16 11:17:47 +00:00
function audio_buffer_progressbar_update() {
if (audio_buffer_progressbar_update_disabled) return;
var audio_buffer_value = (audio_prepared_buffers.length * audio_buffer_size) / audio_context.sampleRate;
audio_buffer_total_average_level_length++;
audio_buffer_total_average_level = (audio_buffer_total_average_level * ((audio_buffer_total_average_level_length - 1) / audio_buffer_total_average_level_length)) + (audio_buffer_value / audio_buffer_total_average_level_length);
var overrun = audio_buffer_value > audio_buffer_maximal_length_sec;
var underrun = audio_prepared_buffers.length === 0;
var text = "buffer";
if (overrun) {
text = "overrun";
console.log("audio overrun, " + (++audio_overrun_cnt).toString());
}
if (underrun) {
text = "underrun";
console.log("audio underrun, " + (++audio_underrun_cnt).toString());
}
if (overrun || underrun) {
audio_buffer_progressbar_update_disabled = true;
window.setTimeout(function () {
audio_buffer_progressbar_update_disabled = false;
audio_buffer_progressbar_update();
}, 1000);
}
progressbar_set(e("openwebrx-bar-audio-buffer"), (underrun) ? 1 : audio_buffer_value / 1.5, "Audio " + text + " [" + (audio_buffer_value).toFixed(1) + " s]", overrun || underrun || audio_buffer_value < 0.25);
}
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function audio_flush() {
flushed = false;
we_have_more_than = function (sec) {
return sec * audio_context.sampleRate < audio_prepared_buffers.length * audio_buffer_size;
};
if (we_have_more_than(audio_buffer_maximal_length_sec)) while (we_have_more_than(audio_buffer_decrease_to_on_overrun_sec)) {
if (!flushed) audio_buffer_progressbar_update();
flushed = true;
audio_prepared_buffers.shift();
}
//if(flushed) add_problem("audio overrun");
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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;
/*if (audio_received.length>audio_buffer_maximal_length)
2014-11-29 00:07:10 +00:00
{
add_problem("audio overrun");
audio_received.splice(0,audio_received.length-audio_buffer_maximal_length);
}
else*/
2019-10-16 11:17:47 +00:00
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);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
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);
}
}
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function webrx_set_param(what, value) {
2019-05-04 21:11:13 +00:00
params = {};
params[what] = value;
2019-10-16 11:17:47 +00:00
ws.send(JSON.stringify({"type": "dspcontrol", "params": params}));
2014-11-29 00:07:10 +00:00
}
var starting_mute = false;
2019-10-16 11:17:47 +00:00
function parsehash() {
if (h = window.location.hash) {
h.substring(1).split(",").forEach(function (x) {
harr = x.split("=");
//console.log(harr);
if (harr[0] === "mute") toggleMute();
else if (harr[0] === "mod") starting_mod = harr[1];
else if (harr[0] === "sql") {
e("openwebrx-panel-squelch").value = harr[1];
updateSquelch();
}
else if (harr[0] === "freq") {
console.log(parseInt(harr[1]));
console.log(center_freq);
starting_offset_frequency = parseInt(harr[1]) - center_freq;
}
});
2019-10-16 11:17:47 +00:00
}
2015-09-30 14:06:30 +00:00
}
2014-12-12 12:55:10 +00:00
2019-10-16 11:17:47 +00:00
function audio_preinit() {
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);
return;
}
if (audio_context.sampleRate < 44100 * 2)
audio_buffer_size = 4096;
else if (audio_context.sampleRate >= 44100 * 2 && audio_context.sampleRate < 44100 * 4)
audio_buffer_size = 4096 * 2;
else if (audio_context.sampleRate > 44100 * 4)
audio_buffer_size = 4096 * 4;
if (!audio_rebuffer) {
2019-10-16 11:17:47 +00:00
audio_rebuffer = new sdrjs.Rebuffer(audio_buffer_size, sdrjs.REBUFFER_FIXED);
audio_last_output_buffer = new Float32Array(audio_buffer_size);
//we send our setup packet
parsehash();
2015-09-30 14:06:30 +00:00
audio_calculate_resampling(audio_context.sampleRate);
2019-10-16 11:17:47 +00:00
audio_resampler = new sdrjs.RationalResamplerFF(audio_client_resampling_factor, 1);
}
2015-09-30 14:06:30 +00:00
2019-10-16 11:17:47 +00:00
ws.send(JSON.stringify({
"type": "dspcontrol",
"action": "start",
"params": {"output_rate": audio_server_output_rate}
}));
}
function audio_init() {
if (is_chrome) audio_context.resume();
if (starting_mute) toggleMute();
if (audio_client_resampling_factor === 0) return; //if failed to find a valid resampling factor...
audio_debug_time_start = (new Date()).getTime();
audio_debug_time_last_start = audio_debug_time_start;
audio_buffer_current_count_debug = 0;
//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
//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 //TODO remove this
window.setInterval(audio_flush, audio_flush_interval_ms);
divlog('Web Audio API succesfully initialized, sample rate: ' + audio_context.sampleRate.toString() + " sps");
initialize_demodulator();
//hide log panel in a second (if user has not hidden it yet)
window.setTimeout(function () {
if (typeof e("openwebrx-panel-log").openwebrxHidden === "undefined" && !was_error) {
toggle_panel("openwebrx-panel-log");
//animate(e("openwebrx-panel-log"),"opacity","",1,0,0.9,1000,60);
//window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
}
}, 2000);
2014-11-29 00:07:10 +00:00
}
function initialize_demodulator() {
2019-10-16 11:17:47 +00:00
demodulator_analog_replace(starting_mod);
if (starting_offset_frequency) {
demodulators[0].offset_frequency = starting_offset_frequency;
e("webrx-actual-freq").innerHTML = format_frequency("{x} MHz", center_freq + starting_offset_frequency, 1e6, 4);
demodulators[0].set();
mkscale();
}
}
2019-07-13 19:40:48 +00:00
var reconnect_timeout = false;
2019-10-16 11:17:47 +00:00
function on_ws_closed() {
try {
audio_node.disconnect();
}
catch (dont_care) {
}
audio_initialized = 0;
if (reconnect_timeout) {
// max value: roundabout 8 and a half minutes
reconnect_timeout = Math.min(reconnect_timeout * 2, 512000);
} else {
// initial value: 1s
reconnect_timeout = 1000;
}
divlog("WebSocket has closed unexpectedly. Attempting to reconnect in " + reconnect_timeout / 1000 + " seconds...", 1);
setTimeout(open_websocket, reconnect_timeout);
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function on_ws_error(event) {
divlog("WebSocket error.", 1);
}
2019-10-16 11:17:47 +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
function open_websocket() {
var protocol = 'ws';
if (window.location.toString().startsWith('https://')) {
protocol = 'wss';
}
ws_url = protocol + "://" + (window.location.origin.split("://")[1]) + "/ws/"; //guess automatically -> now default behaviour
if (!("WebSocket" in window))
divlog("Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.");
ws = new WebSocket(ws_url);
ws.onopen = on_ws_opened;
ws.onmessage = on_ws_recv;
ws.onclose = on_ws_closed;
ws.binaryType = "arraybuffer";
window.onbeforeunload = function () { //http://stackoverflow.com/questions/4812686/closing-websocket-correctly-html5-javascript
ws.onclose = function () {
};
ws.close();
};
ws.onerror = on_ws_error;
}
function waterfall_mkcolor(db_value, waterfall_colors_arg) {
if (typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors;
if (db_value < waterfall_min_level) db_value = waterfall_min_level;
if (db_value > waterfall_max_level) db_value = waterfall_max_level;
full_scale = waterfall_max_level - waterfall_min_level;
relative_value = db_value - waterfall_min_level;
value_percent = relative_value / full_scale;
percent_for_one_color = 1 / (waterfall_colors_arg.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(waterfall_colors_arg[index + 1], waterfall_colors_arg[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;
2014-11-29 00:07:10 +00:00
}
var canvas_context;
var canvases = [];
var canvas_default_height = 200;
var canvas_container;
var canvas_phantom;
2019-10-16 11:17:47 +00:00
function add_canvas() {
var new_canvas = document.createElement("canvas");
new_canvas.width = fft_size;
new_canvas.height = canvas_default_height;
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);
canvases.push(new_canvas);
while (canvas_container && canvas_container.clientHeight + canvas_default_height * 2 < canvases.length * canvas_default_height) {
var c = canvases.shift();
if (!c) break;
canvas_container.removeChild(c);
}
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function init_canvas_container() {
canvas_container = e("webrx-canvas-container");
mathbox_container = e("openwebrx-mathbox-container");
canvas_container.addEventListener("mouseleave", canvas_container_mouseleave, false);
canvas_container.addEventListener("mousemove", canvas_mousemove, false);
canvas_container.addEventListener("mouseup", canvas_mouseup, false);
canvas_container.addEventListener("mousedown", canvas_mousedown, false);
canvas_container.addEventListener("wheel", canvas_mousewheel, false);
var frequency_container = e("openwebrx-frequency-container");
frequency_container.addEventListener("wheel", canvas_mousewheel, false);
add_canvas();
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
canvas_maxshift = 0;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function shift_canvases() {
canvases.forEach(function (p) {
p.style.top = (p.openwebrx_top++).toString() + "px";
});
canvas_maxshift++;
}
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;
});
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function waterfall_init() {
init_canvas_container();
resize_waterfall_container(false);
/* then */
resize_canvases();
scale_setup();
mkzoomlevels();
waterfall_setup_done = 1;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
var waterfall_dont_scale = 0;
2019-10-16 11:17:47 +00:00
var mathbox_shift = function () {
if (mathbox_data_current_depth < mathbox_data_max_depth) mathbox_data_current_depth++;
if (mathbox_data_index + 1 >= mathbox_data_max_depth) mathbox_data_index = 0;
else mathbox_data_index++;
mathbox_data_global_index++;
};
2019-10-16 11:17:47 +00:00
var mathbox_clear_data = function () {
mathbox_data_index = 50;
mathbox_data_current_depth = 0;
};
2019-10-16 11:17:47 +00:00
var mathbox_get_data_line = function (x) {
return (mathbox_data_max_depth + mathbox_data_index + x - 1) % mathbox_data_max_depth;
};
2019-10-16 11:17:47 +00:00
var mathbox_data_index_valid = function (x) {
return x > mathbox_data_max_depth - mathbox_data_current_depth;
};
2019-10-16 11:17:47 +00:00
function waterfall_add(data) {
if (!waterfall_setup_done) return;
var w = fft_size;
2019-10-16 11:17:47 +00:00
if (waterfall_measure_minmax) waterfall_measure_minmax_do(data);
if (waterfall_measure_minmax_now) {
waterfall_measure_minmax_do(data);
waterfall_measure_minmax_now = false;
waterfallColorsAuto();
}
2019-10-16 11:17:47 +00:00
if (mathbox_mode === MATHBOX_MODES.WATERFALL) {
//Handle mathbox
for (var i = 0; i < fft_size; i++) mathbox_data[i + mathbox_data_index * fft_size] = data[i];
mathbox_shift();
} else {
2019-05-04 18:26:11 +00:00
//Add line to waterfall image
2019-10-16 11:17:47 +00:00
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;
2019-05-04 18:26:11 +00:00
}
2014-11-29 00:07:10 +00:00
2019-05-04 18:26:11 +00:00
//Draw image
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
shift_canvases();
2019-10-16 11:17:47 +00:00
if (canvas_actual_line < 0) add_canvas();
}
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function check_top_bar_congestion() {
var rmf = function (x) {
return x.offsetLeft + x.offsetWidth;
};
var wet = e("webrx-rx-title");
var wed = e("webrx-rx-desc");
var rightmost = Math.max(rmf(wet), rmf(wed));
var tl = e("openwebrx-main-buttons");
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
[wet, wed].map(function (what) {
if (rmf(what) > tl.offsetLeft - 20) what.style.opacity = what.style.opacity = "0";
else wet.style.opacity = wed.style.opacity = "1";
});
2014-11-29 00:07:10 +00:00
}
var MATHBOX_MODES =
2019-10-16 11:17:47 +00:00
{
UNINITIALIZED: 0,
NONE: 1,
WATERFALL: 2,
CONSTELLATION: 3
};
var mathbox_mode = MATHBOX_MODES.UNINITIALIZED;
var mathbox;
var mathbox_element;
2019-10-16 11:17:47 +00:00
function mathbox_init() {
//mathbox_waterfall_history_length is defined in the config
mathbox_data_max_depth = fft_fps * mathbox_waterfall_history_length; //how many lines can the buffer store
mathbox_data_current_depth = 0; //how many lines are in the buffer currently
mathbox_data_index = 0; //the index of the last empty line / the line to be overwritten
mathbox_data = new Float32Array(fft_size * mathbox_data_max_depth);
mathbox_data_global_index = 0;
mathbox_correction_for_z = 0;
mathbox = mathBox({
plugins: ['core', 'controls', 'cursor', 'stats'],
controls: {klass: THREE.OrbitControls}
});
three = mathbox.three;
2019-10-16 11:17:47 +00:00
if (typeof three === "undefined") divlog("3D waterfall cannot be initialized because WebGL is not supported in your browser.", true);
three.renderer.setClearColor(new THREE.Color(0x808080), 1.0);
2019-10-16 11:17:47 +00:00
mathbox_container.appendChild((mathbox_element = three.renderer.domElement));
view = mathbox
2019-10-16 11:17:47 +00:00
.set({
scale: 1080,
focus: 3
})
.camera({
proxy: true,
position: [-2, 1, 3]
})
.cartesian({
range: [[-1, 1], [0, 1], [0, 1]],
scale: [2, 2 / 3, 1]
});
view.axis({
2019-10-16 11:17:47 +00:00
axis: 1,
width: 3,
color: "#fff"
});
view.axis({
2019-10-16 11:17:47 +00:00
axis: 2,
width: 3,
color: "#fff"
//offset: [0, 0, 0],
});
view.axis({
2019-10-16 11:17:47 +00:00
axis: 3,
width: 3,
color: "#fff"
});
view.grid({
2019-10-16 11:17:47 +00:00
width: 2,
opacity: 0.5,
axes: [1, 3],
zOrder: 1,
color: "#fff"
});
2019-10-16 11:17:47 +00:00
var remap = function (x, z, t) {
var currentTimePos = mathbox_data_global_index / (fft_fps * 1.0);
var realZAdd = (-(t - currentTimePos) / mathbox_waterfall_history_length);
var zAdd = realZAdd - mathbox_correction_for_z;
if (zAdd < -0.2 || zAdd > 0.2) {
mathbox_correction_for_z = realZAdd;
}
var xIndex = Math.trunc(((x + 1) / 2.0) * fft_size); //x: frequency
var zIndex = Math.trunc(z * (mathbox_data_max_depth - 1)); //z: time
var realZIndex = mathbox_get_data_line(zIndex);
if (!mathbox_data_index_valid(zIndex)) return {y: undefined, dBValue: undefined, zAdd: 0};
var index = Math.trunc(xIndex + realZIndex * fft_size);
var dBValue = mathbox_data[index];
if (dBValue > waterfall_max_level) y = 1;
else if (dBValue < waterfall_min_level) y = 0;
else y = (dBValue - waterfall_min_level) / (waterfall_max_level - waterfall_min_level);
mathbox_dbg = {dbv: dBValue, indexval: index, mbd: mathbox_data.length, yval: y};
if (!y) y = 0;
return {y: y, dBValue: dBValue, zAdd: zAdd};
};
var points = view.area({
2019-10-16 11:17:47 +00:00
expr: function (emit, x, z, i, j, t) {
var y;
remapResult = remap(x, z, t);
if ((y = remapResult.y) === undefined) return;
emit(x, y, z + remapResult.zAdd);
},
width: mathbox_waterfall_frequency_resolution,
height: mathbox_data_max_depth - 1,
channels: 3,
axes: [1, 3]
});
var colors = view.area({
2019-10-16 11:17:47 +00:00
expr: function (emit, x, z, i, j, t) {
var dBValue;
if ((dBValue = remap(x, z, t).dBValue) === undefined) return;
var color = waterfall_mkcolor(dBValue, mathbox_waterfall_colors);
var b = (color & 0xff) / 255.0;
var g = ((color & 0xff00) >> 8) / 255.0;
var r = ((color & 0xff0000) >> 16) / 255.0;
emit(r, g, b, 1.0);
},
width: mathbox_waterfall_frequency_resolution,
height: mathbox_data_max_depth - 1,
channels: 4,
axes: [1, 3]
});
view.surface({
2019-10-16 11:17:47 +00:00
shaded: true,
points: '<<',
colors: '<',
color: 0xFFFFFF
});
view.surface({
2019-10-16 11:17:47 +00:00
fill: false,
lineX: false,
lineY: false,
points: '<<',
colors: '<',
color: 0xFFFFFF,
width: 2,
blending: 'add',
opacity: .25,
zBias: 5
});
2019-10-16 11:17:47 +00:00
mathbox_mode = MATHBOX_MODES.NONE;
}
2019-10-16 11:17:47 +00:00
function mathbox_toggle() {
2019-10-16 11:17:47 +00:00
if (mathbox_mode === MATHBOX_MODES.UNINITIALIZED) mathbox_init();
mathbox_mode = (mathbox_mode === MATHBOX_MODES.NONE) ? MATHBOX_MODES.WATERFALL : MATHBOX_MODES.NONE;
mathbox_container.style.display = (mathbox_mode === MATHBOX_MODES.WATERFALL) ? "block" : "none";
mathbox_clear_data();
waterfall_clear();
}
2019-10-16 11:17:47 +00:00
function waterfall_clear() {
while (canvases.length) //delete all canvases
{
var x = canvases.shift();
x.parentNode.removeChild(x);
delete x;
}
add_canvas();
}
2019-10-16 11:17:47 +00:00
function openwebrx_resize() {
resize_canvases();
resize_waterfall_container(true);
resize_scale();
check_top_bar_congestion();
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function init_header() {
$('#openwebrx-main-buttons').find('li[data-toggle-panel]').click(function () {
2019-07-11 17:37:00 +00:00
toggle_panel($(this).data('toggle-panel'));
});
}
2019-10-12 15:02:29 +00:00
var bookmarks;
2019-10-16 11:17:47 +00:00
function openwebrx_init() {
if (ios || is_chrome) e("openwebrx-big-grey").style.display = "table-cell";
(opb = e("openwebrx-play-button-text")).style.marginTop = (window.innerHeight / 2 - opb.clientHeight / 2).toString() + "px";
init_rx_photo();
open_websocket();
secondary_demod_init();
2019-06-15 17:10:33 +00:00
digimodes_init();
2019-10-16 11:17:47 +00:00
place_panels(first_show_panel);
window.setTimeout(function () {
window.setInterval(debug_audio, 1000);
}, 1000);
window.addEventListener("resize", openwebrx_resize);
check_top_bar_congestion();
init_header();
bookmarks = new BookmarkBar();
2019-10-16 11:17:47 +00:00
//Synchronise volume with slider
updateVolume();
2019-05-04 14:56:23 +00:00
2014-11-29 00:07:10 +00:00
}
2019-06-15 17:10:33 +00:00
function digimodes_init() {
2019-10-16 11:17:47 +00:00
$(".openwebrx-meta-panel").each(function (_, p) {
2019-07-08 18:31:34 +00:00
p.openwebrxHidden = true;
});
2019-06-15 17:10:33 +00:00
// initialze DMR timeslot muting
2019-10-16 11:17:47 +00:00
$('.openwebrx-dmr-timeslot-panel').click(function (e) {
2019-06-15 17:10:33 +00:00
$(e.currentTarget).toggleClass("muted");
update_dmr_timeslot_filtering();
});
}
function update_dmr_timeslot_filtering() {
2019-10-16 11:17:47 +00:00
var filter = $('.openwebrx-dmr-timeslot-panel').map(function (index, el) {
2019-06-15 17:10:33 +00:00
return (!$(el).hasClass("muted")) << index;
2019-10-16 11:17:47 +00:00
}).toArray().reduce(function (acc, v) {
2019-06-15 17:10:33 +00:00
return acc | v;
}, 0);
webrx_set_param("dmr_filter", filter);
}
2019-10-16 11:17:47 +00:00
function iosPlayButtonClick() {
//On iOS, we can only start audio from a click or touch event.
audio_init();
e("openwebrx-big-grey").style.opacity = 0;
window.setTimeout(function () {
e("openwebrx-big-grey").style.display = "none";
}, 1100);
audio_allowed = 1;
2016-03-26 23:47:26 +00:00
}
2019-10-16 11:17:47 +00:00
var rt = function (s, n) {
return s.replace(/[a-zA-Z]/g, function (c) {
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + n) ? c : c - 26);
});
};
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-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
var audio_debug_time_start = 0;
var audio_debug_time_last_start = 0;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
function debug_audio() {
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;
kbps_mult = (audio_compression === "adpcm") ? 8 : 16;
//e("openwebrx-audio-sps").innerHTML=
// ((audio_compression=="adpcm")?"ADPCM compressed":"uncompressed")+" audio downlink:<br/> "+(audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call).toFixed(0)+" kbps ("+
// (audio_buffer_all_size_debug*kbps_mult/audio_debug_time_taken).toFixed(1)+" kbps avg.), feed at "+
// ((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
var audio_speed_value = audio_buffer_current_size_debug * kbps_mult / audio_debug_time_since_last_call;
progressbar_set(e("openwebrx-bar-audio-speed"), audio_speed_value / 500000, "Audio stream [" + (audio_speed_value / 1000).toFixed(0) + " kbps]", false);
var audio_output_value = (audio_buffer_current_count_debug * audio_buffer_size) / audio_debug_time_taken;
var audio_max_rate = audio_context.sampleRate * 1.25;
var audio_min_rate = audio_context.sampleRate * .25;
progressbar_set(e("openwebrx-bar-audio-output"), audio_output_value / audio_max_rate, "Audio output [" + (audio_output_value / 1000).toFixed(1) + " ksps]", audio_output_value > audio_max_rate || audio_output_value < audio_min_rate);
audio_buffer_progressbar_update();
var debug_ws_time_taken = (time_now - debug_ws_time_start) / 1000;
2019-10-16 11:17:47 +00:00
var network_speed_value = debug_ws_data_received / debug_ws_time_taken;
progressbar_set(e("openwebrx-bar-network-speed"), network_speed_value * 8 / 2000, "Network usage [" + (network_speed_value * 8).toFixed(1) + " kbps]", false);
2019-10-16 11:17:47 +00:00
audio_buffer_current_size_debug = 0;
2019-10-16 11:17:47 +00:00
if (waterfall_measure_minmax) waterfall_measure_minmax_print();
2014-11-29 00:07:10 +00:00
}
// ========================================================
// ======================= PANELS =======================
// ========================================================
2019-10-16 11:17:47 +00:00
panel_margin = 5.9;
2014-11-29 00:07:10 +00:00
2019-10-16 11:17:47 +00:00
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;
2014-11-29 00:07:10 +00:00
}
2019-10-16 11:17:47 +00:00
function toggle_panel(what, on) {
var item = e(what);
2019-06-09 15:39:15 +00:00
if (!item) return;
2019-10-16 11:17:47 +00:00
if (typeof on !== "undefined") {
if (item.openwebrxHidden && !on) return;
if (!item.openwebrxHidden && on) return;
}
if (item.openwebrxDisableClick) return;
item.style.transitionDuration = "599ms";
item.style.transitionDelay = "0ms";
if (!item.openwebrxHidden) {
window.setTimeout(function () {
item.openwebrxHidden = !item.openwebrxHidden;
place_panels();
item.openwebrxDisableClick = false;
}, 700);
item.style.transform = "perspective( 599px ) rotateX( 90deg )";
}
else {
item.openwebrxHidden = !item.openwebrxHidden;
place_panels();
window.setTimeout(function () {
item.openwebrxDisableClick = false;
}, 700);
item.style.transform = "perspective( 599px ) rotateX( 0deg )";
}
item.style.transitionDuration = "0";
item.openwebrxDisableClick = true;
}
function first_show_panel(panel) {
panel.style.transitionDuration = 0;
panel.style.transitionDelay = 0;
rotx = (Math.random() > 0.5) ? -90 : 90;
roty = 0;
if (Math.random() > 0.5) {
rottemp = rotx;
rotx = roty;
roty = rottemp;
}
if (rotx !== 0 && Math.random() > 0.5) rotx = 270;
//console.log(rotx,roty);
transformString = "perspective( 599px ) rotateX( %1deg ) rotateY( %2deg )"
.replace("%1", rotx.toString()).replace("%2", roty.toString());
//console.log(transformString);
//console.log(panel);
panel.style.transform = transformString;
window.setTimeout(function () {
panel.style.transitionDuration = "599ms";
panel.style.transitionDelay = (Math.floor(Math.random() * 500)).toString() + "ms";
panel.style.transform = "perspective( 599px ) rotateX( 0deg ) rotateY( 0deg )";
//panel.style.transitionDuration="0ms";
//panel.style.transitionDelay="0";
}, 1);
}
function place_panels(function_apply) {
if (function_apply === undefined) function_apply = function (x) {
};
var hoffset = 0; //added this because the first panel should not have such great gap below
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.indexOf("openwebrx-panel") >= 0) {
if (c.openwebrxHidden) {
c.style.display = "none";
continue;
}
c.style.display = "block";
c.openwebrxPanelTransparent = (!!c.dataset.panelTransparent);
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";
if (!c.openwebrxPanelTransparent) c.style.margin = panel_margin.toString() + "px";
else c.style.marginLeft = panel_margin.toString() + "px";
c.openwebrxPanelWidth = parseInt(newSize[0]);
c.openwebrxPanelHeight = parseInt(newSize[1]);
}
}
y = hoffset; //was y=0 before hoffset
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 + ((p.openwebrxPanelTransparent) ? 0 : 3) * panel_margin;
if (function_apply) function_apply(p);
//console.log(p.id, y, p.openwebrxPanelTransparent);
2019-10-16 11:17:47 +00:00
}
y = hoffset;
while (right_col.length > 0) {
p = pop_bottommost_panel(right_col);
p.style.right = (e("webrx-canvas-container").offsetWidth - e("webrx-canvas-container").clientWidth).toString() + "px"; //get scrollbar width
p.style.bottom = y.toString() + "px";
p.style.visibility = "visible";
y += p.openwebrxPanelHeight + ((p.openwebrxPanelTransparent) ? 0 : 3) * panel_margin;
if (function_apply) function_apply(p);
}
}
function progressbar_set(obj, val, text, over) {
if (val < 0.05) val = 0;
if (val > 1) val = 1;
var innerBar = null;
var innerText = null;
for (var i = 0; i < obj.children.length; i++) {
if (obj.children[i].className === "openwebrx-progressbar-text") innerText = obj.children[i];
else if (obj.children[i].className === "openwebrx-progressbar-bar") innerBar = obj.children[i];
}
if (innerBar == null) return;
//.h: function animate(object,style_name,unit,from,to,accel,time_ms,fps,to_exec)
animate(innerBar, "width", "px", innerBar.clientWidth, val * obj.clientWidth, 0.7, 700, 60);
//innerBar.style.width=(val*100).toFixed(0)+"%";
innerBar.style.backgroundColor = (over) ? "#ff6262" : "#00aba6";
if (innerText == null) return;
innerText.innerHTML = text;
}
function demodulator_buttons_update() {
$(".openwebrx-demodulator-button").removeClass("highlighted");
if (secondary_demod) {
$("#openwebrx-button-dig").addClass("highlighted");
$('#openwebrx-secondary-demod-listbox').val(secondary_demod);
} else switch (demodulators[0].subtype) {
case "lsb":
case "usb":
case "cw":
if (demodulators[0].high_cut - demodulators[0].low_cut < 300)
$("#openwebrx-button-cw").addClass("highlighted");
else {
if (demodulators[0].high_cut < 0)
$("#openwebrx-button-lsb").addClass("highlighted");
else if (demodulators[0].low_cut > 0)
$("#openwebrx-button-usb").addClass("highlighted");
else $("#openwebrx-button-lsb, #openwebrx-button-usb").addClass("highlighted");
}
break;
default:
2019-05-14 21:36:37 +00:00
var mod = demodulators[0].subtype;
$("#openwebrx-button-" + mod).addClass("highlighted");
break;
2019-10-16 11:17:47 +00:00
}
}
function demodulator_analog_replace_last() {
demodulator_analog_replace(last_analog_demodulator_subtype);
}
/*
2019-10-16 11:17:47 +00:00
_____ _ _ _
| __ \(_) (_) | |
| | | |_ __ _ _ _ __ ___ ___ __| | ___ ___
| | | | |/ _` | | '_ ` _ \ / _ \ / _` |/ _ \/ __|
| |__| | | (_| | | | | | | | (_) | (_| | __/\__ \
|_____/|_|\__, |_|_| |_| |_|\___/ \__,_|\___||___/
2019-10-16 11:17:47 +00:00
__/ |
|___/
*/
secondary_demod = false;
secondary_demod_offset_freq = 0;
2019-10-16 11:17:47 +00:00
function demodulator_digital_replace_last() {
demodulator_digital_replace(last_digital_demodulator_subtype);
secondary_demod_listbox_update();
}
2019-10-16 11:17:47 +00:00
function demodulator_digital_replace(subtype) {
switch (subtype) {
case "bpsk31":
case "rtty":
case "ft8":
case "jt65":
case "jt9":
case "ft4":
secondary_demod_start(subtype);
demodulator_analog_replace('usb', true);
break;
case "wspr":
secondary_demod_start(subtype);
demodulator_analog_replace('usb', true);
// WSPR only samples between 1400 and 1600 Hz
demodulators[0].low_cut = 1350;
demodulators[0].high_cut = 1650;
demodulators[0].set();
break;
case "packet":
secondary_demod_start(subtype);
demodulator_analog_replace('nfm', true);
break;
}
demodulator_buttons_update();
2019-07-08 18:45:09 +00:00
$('#openwebrx-panel-digimodes').attr('data-mode', subtype);
toggle_panel("openwebrx-panel-digimodes", true);
2019-07-20 11:38:25 +00:00
toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4'].indexOf(subtype) >= 0);
2019-10-16 11:17:47 +00:00
toggle_panel("openwebrx-panel-packet-message", subtype === "packet");
}
2019-10-16 11:17:47 +00:00
function secondary_demod_create_canvas() {
var new_canvas = document.createElement("canvas");
new_canvas.width = secondary_fft_size;
new_canvas.height = $(secondary_demod_canvas_container).height();
new_canvas.style.width = $(secondary_demod_canvas_container).width() + "px";
new_canvas.style.height = $(secondary_demod_canvas_container).height() + "px";
console.log(new_canvas.width, new_canvas.height, new_canvas.style.width, new_canvas.style.height);
2019-10-16 11:17:47 +00:00
secondary_demod_current_canvas_actual_line = new_canvas.height - 1;
$(secondary_demod_canvas_container).children().last().before(new_canvas);
return new_canvas;
}
2019-10-16 11:17:47 +00:00
function secondary_demod_remove_canvases() {
$(secondary_demod_canvas_container).children("canvas").remove();
}
2019-10-16 11:17:47 +00:00
function secondary_demod_init_canvases() {
secondary_demod_remove_canvases();
2019-10-16 11:17:47 +00:00
secondary_demod_canvases = [];
secondary_demod_canvases.push(secondary_demod_create_canvas());
secondary_demod_canvases.push(secondary_demod_create_canvas());
2019-10-16 11:17:47 +00:00
secondary_demod_canvases[0].openwebrx_top = -$(secondary_demod_canvas_container).height();
secondary_demod_canvases[1].openwebrx_top = 0;
secondary_demod_canvases_update_top();
secondary_demod_current_canvas_context = secondary_demod_canvases[0].getContext("2d");
2019-10-16 11:17:47 +00:00
secondary_demod_current_canvas_actual_line = $(secondary_demod_canvas_container).height() - 1;
secondary_demod_current_canvas_index = 0;
secondary_demod_canvases_initialized = true;
//secondary_demod_update_channel_freq_from_event();
mkscale(); //so that the secondary waterfall zoom level will be initialized
}
2019-10-16 11:17:47 +00:00
function secondary_demod_canvases_update_top() {
for (var i = 0; i < 2; i++) secondary_demod_canvases[i].style.top = secondary_demod_canvases[i].openwebrx_top + "px";
}
2019-10-16 11:17:47 +00:00
function secondary_demod_swap_canvases() {
console.log("swap");
2019-10-16 11:17:47 +00:00
secondary_demod_canvases[0 + !secondary_demod_current_canvas_index].openwebrx_top -= $(secondary_demod_canvas_container).height() * 2;
secondary_demod_current_canvas_index = 0 + !secondary_demod_current_canvas_index;
secondary_demod_current_canvas_context = secondary_demod_canvases[secondary_demod_current_canvas_index].getContext("2d");
2019-10-16 11:17:47 +00:00
secondary_demod_current_canvas_actual_line = $(secondary_demod_canvas_container).height() - 1;
}
2019-10-16 11:17:47 +00:00
function secondary_demod_init() {
$("#openwebrx-panel-digimodes")[0].openwebrxHidden = true;
2019-07-08 18:31:34 +00:00
$("#openwebrx-panel-wsjt-message")[0].openwebrxHidden = true;
$("#openwebrx-panel-packet-message")[0].openwebrxHidden = true;
secondary_demod_canvas_container = $("#openwebrx-digimode-canvas-container")[0];
$(secondary_demod_canvas_container)
.mousemove(secondary_demod_canvas_container_mousemove)
.mouseup(secondary_demod_canvas_container_mouseup)
.mousedown(secondary_demod_canvas_container_mousedown)
.mouseenter(secondary_demod_canvas_container_mousein)
.mouseleave(secondary_demod_canvas_container_mouseleave);
init_digital_removal_timer();
}
2019-10-16 11:17:47 +00:00
function secondary_demod_start(subtype) {
secondary_demod_canvases_initialized = false;
2019-10-16 11:17:47 +00:00
ws.send(JSON.stringify({"type": "dspcontrol", "params": {"secondary_mod": subtype}}));
secondary_demod = subtype;
}
2019-10-16 11:17:47 +00:00
function secondary_demod_set() {
ws.send(JSON.stringify({"type": "dspcontrol", "params": {"secondary_offset_freq": secondary_demod_offset_freq}}));
}
2019-10-16 11:17:47 +00:00
function secondary_demod_stop() {
ws.send(JSON.stringify({"type": "dspcontrol", "params": {"secondary_mod": false}}));
secondary_demod = false;
}
2019-10-16 11:17:47 +00:00
function secondary_demod_push_binary_data(x) {
secondary_demod_push_data(Array.from(x).map(function (y) {
return y ? "1" : "0"
}).join("")
);
}
function secondary_demod_push_data(x) {
x = Array.from(x).filter(function (y) {
var c = y.charCodeAt(0);
return (c === 10 || (c >= 32 && c <= 126));
}).map(function (y) {
if (y === "&"
)
return "&amp;";
if (y === "<") return "&lt;";
if (y === ">") return "&gt;";
if (y === " ") return "&nbsp;";
return y;
2019-10-16 11:17:47 +00:00
}).map(function (y) {
if (y === "\n"
)
return "<br />";
return "<span class=\"part\">" + y + "</span>";
}).join("");
$("#openwebrx-cursor-blink").before(x);
}
2019-10-16 11:17:47 +00:00
function secondary_demod_data_clear() {
$("#openwebrx-cursor-blink").prevAll().remove();
}
2019-10-16 11:17:47 +00:00
function secondary_demod_close_window() {
secondary_demod_stop();
toggle_panel("openwebrx-panel-digimodes", false);
2019-07-08 18:31:34 +00:00
toggle_panel("openwebrx-panel-wsjt-message", false);
toggle_panel("openwebrx-panel-packet-message", false);
}
2019-10-16 11:17:47 +00:00
secondary_demod_fft_offset_db = 30; //need to calculate that later
2019-10-16 11:17:47 +00:00
function secondary_demod_waterfall_add(data) {
if (!secondary_demod) return;
var w = secondary_fft_size;
//Add line to waterfall image
var oneline_image = secondary_demod_current_canvas_context.createImageData(w, 1);
for (x = 0; x < w; x++) {
var color = waterfall_mkcolor(data[x] + secondary_demod_fft_offset_db);
for (i = 0; i < 4; i++) oneline_image.data[x * 4 + i] = ((color >>> 0) >> ((3 - i) * 8)) & 0xff;
}
//Draw image
secondary_demod_current_canvas_context.putImageData(oneline_image, 0, secondary_demod_current_canvas_actual_line--);
secondary_demod_canvases.map(function (x) {
x.openwebrx_top += 1;
})
;
secondary_demod_canvases_update_top();
2019-10-16 11:17:47 +00:00
if (secondary_demod_current_canvas_actual_line < 0) secondary_demod_swap_canvases();
}
var secondary_demod_canvases_initialized = false;
secondary_demod_listbox_updating = false;
2019-10-16 11:17:47 +00:00
function secondary_demod_listbox_changed() {
2019-07-13 21:16:25 +00:00
if (secondary_demod_listbox_updating) return;
var sdm = $("#openwebrx-secondary-demod-listbox")[0].value;
2019-10-16 11:17:47 +00:00
if (sdm === "none") {
demodulator_analog_replace_last();
} else {
demodulator_digital_replace(sdm);
}
update_dial_button();
}
2019-10-16 11:17:47 +00:00
function secondary_demod_listbox_update() {
secondary_demod_listbox_updating = true;
2019-10-16 11:17:47 +00:00
$("#openwebrx-secondary-demod-listbox").val((secondary_demod) ? secondary_demod : "none");
console.log("update");
secondary_demod_listbox_updating = false;
}
2019-10-16 11:17:47 +00:00
secondary_demod_channel_freq = 1000;
function secondary_demod_update_marker() {
var width = Math.max((secondary_bw / (if_samp_rate / 2)) * secondary_demod_canvas_width, 5);
var center_at = (secondary_demod_channel_freq / (if_samp_rate / 2)) * secondary_demod_canvas_width + secondary_demod_canvas_left;
var left = center_at - width / 2;
//console.log("sdum", width, left);
2019-10-16 11:17:47 +00:00
$("#openwebrx-digimode-select-channel").width(width).css("left", left + "px")
}
secondary_demod_waiting_for_set = false;
2019-10-16 11:17:47 +00:00
function secondary_demod_update_channel_freq_from_event(evt) {
if (typeof evt !== "undefined") {
var relativeX = (evt.offsetX) ? evt.offsetX : evt.layerX;
secondary_demod_channel_freq = secondary_demod_low_cut +
(relativeX / $(secondary_demod_canvas_container).width()) * (secondary_demod_high_cut - secondary_demod_low_cut);
}
//console.log("toset:", secondary_demod_channel_freq);
2019-10-16 11:17:47 +00:00
if (!secondary_demod_waiting_for_set) {
secondary_demod_waiting_for_set = true;
2019-10-16 11:17:47 +00:00
window.setTimeout(function () {
ws.send(JSON.stringify({
"type": "dspcontrol",
"params": {"secondary_offset_freq": Math.floor(secondary_demod_channel_freq)}
}));
//console.log("doneset:", secondary_demod_channel_freq);
secondary_demod_waiting_for_set = false;
},
50
)
;
}
secondary_demod_update_marker();
}
2019-10-16 11:17:47 +00:00
secondary_demod_mousedown = false;
function secondary_demod_canvas_container_mousein() {
$("#openwebrx-digimode-select-channel").css("opacity", "0.7"); //.css("border-width", "1px");
}
2019-10-16 11:17:47 +00:00
function secondary_demod_canvas_container_mouseleave() {
$("#openwebrx-digimode-select-channel").css("opacity", "0");
}
2019-10-16 11:17:47 +00:00
function secondary_demod_canvas_container_mousemove(evt) {
if (secondary_demod_mousedown) secondary_demod_update_channel_freq_from_event(evt);
}
2019-10-16 11:17:47 +00:00
function secondary_demod_canvas_container_mousedown(evt) {
if (evt.which === 1) secondary_demod_mousedown = true;
}
2019-10-16 11:17:47 +00:00
function secondary_demod_canvas_container_mouseup(evt) {
if (evt.which === 1) secondary_demod_mousedown = false;
secondary_demod_update_channel_freq_from_event(evt);
}
2019-10-16 11:17:47 +00:00
function secondary_demod_waterfall_set_zoom(low_cut, high_cut) {
if (!secondary_demod || !secondary_demod_canvases_initialized) return;
if (low_cut < 0 && high_cut < 0) {
var hctmp = high_cut;
var lctmp = low_cut;
low_cut = -hctmp;
low_cut = -lctmp;
}
2019-10-16 11:17:47 +00:00
else if (low_cut < 0 && high_cut > 0) {
high_cut = Math.max(Math.abs(high_cut), Math.abs(low_cut));
low_cut = 0;
}
secondary_demod_low_cut = low_cut;
secondary_demod_high_cut = high_cut;
2019-10-16 11:17:47 +00:00
var shown_bw = high_cut - low_cut;
secondary_demod_canvas_width = $(secondary_demod_canvas_container).width() * (if_samp_rate / 2) / shown_bw;
secondary_demod_canvas_left = -secondary_demod_canvas_width * (low_cut / (if_samp_rate / 2));
//console.log("setzoom", secondary_demod_canvas_width, secondary_demod_canvas_left, low_cut, high_cut);
2019-10-16 11:17:47 +00:00
secondary_demod_canvases.map(function (x) {
$(x).css("left", secondary_demod_canvas_left + "px").css("width", secondary_demod_canvas_width + "px");
})
;
secondary_demod_update_channel_freq_from_event();
}
2019-05-10 14:14:16 +00:00
function sdr_profile_changed() {
value = $('#openwebrx-sdr-profiles-listbox').val();
2019-10-16 11:17:47 +00:00
ws.send(JSON.stringify({type: "selectprofile", params: {profile: value}}));
2019-05-10 14:14:16 +00:00
}