Split waterfall functions from the openwebrx.js.

This commit is contained in:
Marat Fayzullin 2024-04-24 20:11:39 -04:00
parent 51913320d2
commit a5bf8e6e80
5 changed files with 228 additions and 179 deletions

View File

@ -257,21 +257,21 @@
<svg class="scanner" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#scanner-running"></use></svg>
</div>
<input title="Squelch" class="openwebrx-squelch-slider openwebrx-panel-slider" type="range" min="-150" max="0" value="-150" step="1">
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button openwebrx-slider-button">
<div title="Auto-adjust waterfall colors (right-click for continuous)" id="openwebrx-waterfall-colors-auto" class="openwebrx-button openwebrx-slider-button" onclick="Waterfall.setAutoRange();">
<svg class="auto" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-auto"></use></svg>
<svg class="continuous" viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-continuous"></use></svg>
</div>
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(0);">
<input title="Waterfall minimum level" id="openwebrx-waterfall-color-min" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="Waterfall.updateColors(0);">
</div>
<div class="openwebrx-panel-line">
<div title="Noise reduction on/off" class="openwebrx-nr-toggle openwebrx-button openwebrx-slider-button" onclick="UI.toggleNR();">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#noise-reduce"></use></svg>
</div>
<input title="Noise reduction level" disabled id="openwebrx-panel-nr" class="openwebrx-panel-slider" type="range" min="-10" max="10" value="0" step="1" onchange="UI.setNR(this.value)" oninput="UI.setNR(this.value)">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button openwebrx-slider-button" onclick="waterfallColorsDefault()">
<div title="Set waterfall colors to default" id="openwebrx-waterfall-colors-default" class="openwebrx-button openwebrx-slider-button" onclick="Waterfall.setDefaultRange();">
<svg viewBox="0 0 80 80"><use xlink:href="static/gfx/svg-defs.svg#waterfall-default"></use></svg>
</div>
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="updateWaterfallColors(1);">
<input title="Waterfall maximum level" id="openwebrx-waterfall-color-max" class="openwebrx-panel-slider" type="range" min="-200" max="100" value="50" step="1" onchange="Waterfall.updateColors(1);">
</div>
</div>
<div id="openwebrx-section-settings" class="openwebrx-section-divider" onclick="UI.toggleSection(this);">&blacktriangle;&nbsp;Settings</div>

View File

@ -263,20 +263,23 @@ UI.setWfTheme = function(theme) {
// Set selector
$('#openwebrx-wf-themes-listbox').val(theme);
// Generate new colors
waterfall_colors = chroma.scale(this.wfThemes[theme]).colors(256, 'rgb');
// Set new colors in the waterfall
Waterfall.setTheme(this.wfThemes[theme]);
};
// Set default waterfall color theme.
UI.setDefaultWfTheme = function(colors) {
// Update default waterfall theme with new colors
this.wfThemes['default'] = colors;
// If default theme currently used, update waterfall
if (this.wfTheme === 'default') {
this.wfTheme = null;
this.setWfTheme('default');
}
};
// Waterfall color schemes
// Waterfall color themes
UI.wfThemes = {
'default' : [0x000000, 0xFFFFFF],
'teejeez' : [0x000000, 0x0000FF, 0x00FFFF, 0x00FF00, 0xFFFF00, 0xFF0000, 0xFF00FF, 0xFFFFFF],

196
htdocs/lib/Waterfall.js Normal file
View File

@ -0,0 +1,196 @@
//
// Waterfall colors
//
function Waterfall() {}
Waterfall.colors = chroma.scale([0x000000, 0xFFFFFF]).colors(256, 'rgb');
Waterfall.levels = { min: -150, max: 0 };
Waterfall.fixed_levels = { min: -150, max: 0 };
Waterfall.auto_levels = { min: -150, max: 0 };
Waterfall.cont_levels = { min: -150, max: 0 };
Waterfall.auto_min_range = 0;
Waterfall.measure_minmax_now = false;
Waterfall.measure_minmax_continuous = false;
// Get current waterfall min/max levels range.
Waterfall.getRange = function() {
return this.levels;
};
// Set waterfall color theme, passed as an array of
// integer RGB values.
Waterfall.setTheme = function(theme) {
this.colors = chroma.scale(theme).colors(256, 'rgb');
};
// Configure waterfall parameters from the attributes
// sent by the server.
Waterfall.configure = function(config) {
if ('waterfall_levels' in config)
this.fixed_levels = config['waterfall_levels'];
if ('waterfall_auto_levels' in config)
this.auto_levels = config['waterfall_auto_levels'];
if ('waterfall_auto_min_range' in config)
this.auto_min_range = config['waterfall_auto_min_range'];
if ('waterfall_auto_level_default_mode' in config)
this.toggleContinuousRange(config['waterfall_auto_level_default_mode']);
};
// Use one-time automatic min/max level update.
Waterfall.setAutoRange = function() {
this.measure_minmax_now = true;
};
// Use default min/max levels.
Waterfall.setDefaultRange = function() {
this.levels.min = this.fixed_levels.min;
this.levels.max = this.fixed_levels.max;
this.updateSliders();
this.resetContinuousColors();
};
// Enable continuous min/max level updates.
Waterfall.toggleContinuousRange = function(on) {
// If no argument given, toggle continuous mode
on = typeof(on) === 'undefined'? !this.measure_minmax_continuous : on;
this.measure_minmax_continuous = on;
var autoButton = $('#openwebrx-waterfall-colors-auto');
autoButton[on ? 'addClass' : 'removeClass']('highlighted');
$('#openwebrx-waterfall-color-min, #openwebrx-waterfall-color-max').prop('disabled', on);
};
// Update waterfall min/max levels from sliders.
Waterfall.updateColors = function(which) {
var $wfmax = $("#openwebrx-waterfall-color-max");
var $wfmin = $("#openwebrx-waterfall-color-min");
this.levels.max = parseInt($wfmax.val());
this.levels.min = parseInt($wfmin.val());
if (this.levels.min >= this.levels.max) {
if (!which) {
this.levels.min = this.levels.max -1;
} else {
this.levels.max = this.levels.min + 1;
}
}
this.updateSliders();
};
// Update waterfall level sliders from min/max levels.
Waterfall.updateSliders = function() {
$('#openwebrx-waterfall-color-max')
.val(this.levels.max)
.attr('title', 'Waterfall maximum level (' + Math.round(this.levels.max) + ' dB)');
$('#openwebrx-waterfall-color-min')
.val(this.levels.min)
.attr('title', 'Waterfall minimum level (' + Math.round(this.levels.min) + ' dB)');
};
// Update automatic min/max levels.
Waterfall.updateAutoColors = function(levels) {
var min_level = levels.min - this.auto_levels.min;
var max_level = levels.max + this.auto_levels.max;
max_level = Math.max(min_level + (this.auto_min_range || 0), max_level);
this.levels.min = min_level;
this.levels.max = max_level;
this.updateSliders();
};
// Reset continuous min/max levels.
Waterfall.resetContinuousColors = function() {
this.cont_levels.min = this.levels.min;
this.cont_levels.max = this.levels.max;
};
// Update continous min/max levels.
Waterfall.updateContinuousColors = function(levels) {
if (levels.max > this.cont_levels.max + 1) {
this.cont_levels.max += 1;
} else if (levels.max < this.cont_levels.max - 1) {
this.cont_levels.max -= .1;
}
if (levels.min < this.cont_levels.min - 1) {
this.cont_levels.min -= 1;
} else if (levels.min > this.cont_levels.min + 1) {
this.cont_levels.min += .1;
}
this.updateAutoColors(this.cont_levels);
};
// Create a color based on the dB value and current color theme.
Waterfall.makeColor = function(db) {
var v = (db - this.levels.min) / (this.levels.max - this.levels.min);
v = Math.max(0, Math.min(1, v)) * (this.colors.length - 1);
var i = Math.floor(v);
v = v - i;
if (v == 0) {
return this.colors[i];
} else {
var c0 = this.colors[i];
var c1 = this.colors[i+1];
return [
c0[0] + v * (c1[0] - c0[0]),
c0[1] + v * (c1[1] - c0[1]),
c0[2] + v * (c1[2] - c0[2])
];
}
};
// Draw a single line of waterfall pixels based on the input data.
Waterfall.drawLine = function(out, data, offset = 0) {
var y = 0;
for (var x = 0; x < data.length; x++) {
var color = this.makeColor(data[x] + offset);
out[y++] = color[0];
out[y++] = color[1];
out[y++] = color[2];
out[y++] = 255;
}
}
// Measure min/max levels from the incoming data, if necessary.
Waterfall.measureRange = function(data) {
// Drop out unless we actually need to measure levels
if (!this.measure_minmax_now && !this.measure_minmax_continuous) return;
// Get visible range of frequencies
var range = get_visible_freq_range();
var start = center_freq - bandwidth / 2;
// This is based on an oversampling factor of about 1.25
range.start = Math.max(0.1, (range.start - start) / bandwidth);
range.end = Math.min(0.9, (range.end - start) / bandwidth);
// Align to the range edges, do not let things flip over
if (range.start >= 0.9)
range.start = range.end - range.bw / bandwidth;
else if (range.end <= 0.1)
range.end = range.start + range.bw / bandwidth;
// Find min/max levels within the range
data = data.slice(range.start * data.length, range.end * data.length);
var levels = {
min: Math.min.apply(Math, data),
max: Math.max.apply(Math, data)
};
if (this.measure_minmax_now) {
this.measure_minmax_now = false;
this.updateAutoColors(levels);
this.resetContinuousColors();
}
if (this.measure_minmax_continuous) {
this.updateContinuousColors(levels);
}
};

View File

@ -119,80 +119,6 @@ function jumpBySteps(steps) {
}
}
var waterfall_min_level;
var waterfall_max_level;
var waterfall_min_level_default;
var waterfall_max_level_default;
var waterfall_colors;
var waterfall_auto_levels;
var waterfall_auto_min_range;
var waterfall_measure_minmax_now = false;
var waterfall_measure_minmax_continuous = false;
function updateWaterfallColors(which) {
var $wfmax = $("#openwebrx-waterfall-color-max");
var $wfmin = $("#openwebrx-waterfall-color-min");
waterfall_max_level = parseInt($wfmax.val());
waterfall_min_level = parseInt($wfmin.val());
if (waterfall_min_level >= waterfall_max_level) {
if (!which) {
waterfall_min_level = waterfall_max_level -1;
} else {
waterfall_max_level = waterfall_min_level + 1;
}
}
updateWaterfallSliders();
}
function updateWaterfallSliders() {
$('#openwebrx-waterfall-color-max')
.val(waterfall_max_level)
.attr('title', 'Waterfall maximum level (' + Math.round(waterfall_max_level) + ' dB)');
$('#openwebrx-waterfall-color-min')
.val(waterfall_min_level)
.attr('title', 'Waterfall minimum level (' + Math.round(waterfall_min_level) + ' dB)');
}
function waterfallColorsDefault() {
waterfall_min_level = waterfall_min_level_default;
waterfall_max_level = waterfall_max_level_default;
updateWaterfallSliders();
waterfallColorsContinuousReset();
}
function waterfallColorsAuto(levels) {
var min_level = levels.min - waterfall_auto_levels.min;
var max_level = levels.max + waterfall_auto_levels.max;
max_level = Math.max(min_level + (waterfall_auto_min_range || 0), max_level);
waterfall_min_level = min_level;
waterfall_max_level = max_level;
updateWaterfallSliders();
}
var waterfall_continuous = {
min: -150,
max: 0
};
function waterfallColorsContinuousReset() {
waterfall_continuous.min = waterfall_min_level;
waterfall_continuous.max = waterfall_max_level;
}
function waterfallColorsContinuous(levels) {
if (levels.max > waterfall_continuous.max + 1) {
waterfall_continuous.max += 1;
} else if (levels.max < waterfall_continuous.max - 1) {
waterfall_continuous.max -= .1;
}
if (levels.min < waterfall_continuous.min - 1) {
waterfall_continuous.min -= 1;
} else if (levels.min > waterfall_continuous.min + 1) {
waterfall_continuous.min += .1;
}
waterfallColorsAuto(waterfall_continuous);
}
function setSmeterRelativeValue(value) {
if (value < 0) value = 0;
if (value > 1.0) value = 1.0;
@ -231,10 +157,9 @@ function getLogSmeterValue(value) {
function setSmeterAbsoluteValue(value) //the value that comes from `csdr squelch_and_smeter_cc`
{
var logValue = getLogSmeterValue(value);
var levels = Waterfall.getRange();
var percent = (logValue - (levels.min - 20)) / ((levels.max + 20) - (levels.min - 20));
setSquelchSliderBackground(logValue);
var lowLevel = waterfall_min_level - 20;
var highLevel = waterfall_max_level + 20;
var percent = (logValue - lowLevel) / (highLevel - lowLevel);
setSmeterRelativeValue(percent);
$("#openwebrx-smeter-db").html(logValue.toFixed(1) + " dB");
}
@ -976,22 +901,12 @@ function on_ws_recv(evt) {
switch (json.type) {
case "config":
var config = json['value'];
// Configure waterfall min/max levels, etc
Waterfall.configure(config);
if ('waterfall_colors' in config)
UI.setDefaultWfTheme(config['waterfall_colors']);
if ('waterfall_levels' in config) {
waterfall_min_level_default = config['waterfall_levels']['min'];
waterfall_max_level_default = config['waterfall_levels']['max'];
}
if ('waterfall_auto_levels' in config)
waterfall_auto_levels = config['waterfall_auto_levels'];
if ('waterfall_auto_min_range' in config)
waterfall_auto_min_range = config['waterfall_auto_min_range'];
if ('waterfall_auto_level_default_mode' in config)
waterfall_measure_minmax_continuous = config['waterfall_auto_level_default_mode'];
var waterfallAutoButton = $('#openwebrx-waterfall-colors-auto');
waterfallAutoButton[waterfall_measure_minmax_continuous ? 'addClass' : 'removeClass']('highlighted');
$('#openwebrx-waterfall-color-min, #openwebrx-waterfall-color-max').prop('disabled', waterfall_measure_minmax_continuous);
var initial_demodulator_params = {};
if ('start_mod' in config)
@ -1042,7 +957,7 @@ function on_ws_recv(evt) {
}
if ('sdr_id' in config || 'profile_id' in config || 'waterfall_levels' in config) {
waterfallColorsDefault();
Waterfall.setDefaultRange();
}
if ('tuning_precision' in config)
@ -1252,28 +1167,6 @@ function on_ws_recv(evt) {
}
}
function waterfall_measure_minmax_do(what) {
// Get visible range
var range = get_visible_freq_range();
var start = center_freq - bandwidth / 2;
// This is based on an oversampling factor of about 1,25
range.start = Math.max(0.1, (range.start - start) / bandwidth);
range.end = Math.min(0.9, (range.end - start) / bandwidth);
// Align to the range edges, do not let things flip over
if (range.start >= 0.9)
range.start = range.end - range.bw / bandwidth;
else if (range.end <= 0.1)
range.end = range.start + range.bw / bandwidth;
var data = what.slice(range.start * what.length, range.end * what.length);
return {
min: Math.min.apply(Math, data),
max: Math.max.apply(Math, data)
};
}
function on_ws_opened() {
$('#openwebrx-error-overlay').hide();
ws.send("SERVER DE CLIENT client=openwebrx.js type=receiver");
@ -1389,26 +1282,6 @@ function open_websocket() {
ws.onerror = on_ws_error;
}
function waterfall_mkcolor(db_value, waterfall_colors_arg) {
waterfall_colors_arg = waterfall_colors_arg || waterfall_colors;
var value_percent = (db_value - waterfall_min_level) / (waterfall_max_level - waterfall_min_level);
value_percent = Math.max(0, Math.min(1, value_percent));
var scaled = value_percent * (waterfall_colors_arg.length - 1);
var index = Math.floor(scaled);
var remain = scaled - index;
if (remain === 0) return waterfall_colors_arg[index];
return color_between(waterfall_colors_arg[index], waterfall_colors_arg[index + 1], remain);}
function color_between(first, second, percent) {
return [
first[0] + percent * (second[0] - first[0]),
first[1] + percent * (second[1] - first[1]),
first[2] + percent * (second[2] - first[2])
];
}
var canvas_context;
var canvases = [];
var canvas_default_height = 200;
@ -1478,30 +1351,17 @@ function waterfall_add(data) {
if (!waterfall_setup_done) return;
var w = fft_size;
if (waterfall_measure_minmax_now) {
var levels = waterfall_measure_minmax_do(data);
waterfall_measure_minmax_now = false;
waterfallColorsAuto(levels);
waterfallColorsContinuousReset();
}
if (waterfall_measure_minmax_continuous) {
var level = waterfall_measure_minmax_do(data);
waterfallColorsContinuous(level);
}
// measure waterfall min/max levels, if necessary
Waterfall.measureRange(data);
// create new canvas if the current one is full (or there isn't one)
if (canvas_actual_line <= 0) add_canvas();
//Add line to waterfall image
// add line to waterfall image
var oneline_image = canvas_context.createImageData(w, 1);
for (var x = 0; x < w; x++) {
var color = waterfall_mkcolor(data[x]);
for (i = 0; i < 3; i++) oneline_image.data[x * 4 + i] = color[i];
oneline_image.data[x * 4 + 3] = 255;
}
Waterfall.drawLine(oneline_image.data, data);
//Draw image
// draw image
canvas_context.putImageData(oneline_image, 0, --canvas_actual_line);
shift_canvases();
}
@ -1593,15 +1453,10 @@ function initSliders() {
$slider.trigger('change');
});
var waterfallAutoButton = $('#openwebrx-waterfall-colors-auto');
waterfallAutoButton.on('click', function() {
waterfall_measure_minmax_now=true;
}).on('contextmenu', function(){
waterfall_measure_minmax_continuous = !waterfall_measure_minmax_continuous;
waterfallColorsContinuousReset();
waterfallAutoButton[waterfall_measure_minmax_continuous ? 'addClass' : 'removeClass']('highlighted');
$('#openwebrx-waterfall-color-min, #openwebrx-waterfall-color-max').prop('disabled', waterfall_measure_minmax_continuous);
// Enable continuous waterfall color adjustment by pressing the
// right mouse button on AUTO
$('#openwebrx-waterfall-colors-auto').on('contextmenu', function() {
Waterfall.toggleContinuousRange();
return false;
});
@ -1867,20 +1722,14 @@ function secondary_demod_push_data(x) {
function secondary_demod_waterfall_add(data) {
var w = secondary_fft_size;
//Add line to waterfall image
// add line to waterfall image
var oneline_image = secondary_demod_current_canvas_context.createImageData(w, 1);
for (var x = 0; x < w; x++) {
var color = waterfall_mkcolor(data[x] + secondary_demod_fft_offset_db);
for (var i = 0; i < 3; i++) oneline_image.data[x * 4 + i] = color[i];
oneline_image.data[x * 4 + 3] = 255;
}
Waterfall.drawLine(oneline_image.data, data, secondary_demod_fft_offset_db);
//Draw image
// 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.map(function (x) { x.openwebrx_top += 1; });
secondary_demod_canvases_update_top();
if (secondary_demod_current_canvas_actual_line < 0) secondary_demod_swap_canvases();
}

View File

@ -137,6 +137,7 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController):
"lib/Js8Threads.js",
"lib/Modes.js",
"lib/MetaPanel.js",
"lib/Waterfall.js",
"lib/Spectrum.js",
"lib/Scanner.js",
"lib/Utils.js",