diff --git a/htdocs/index.html b/htdocs/index.html
index a07726bc..c8eb32bc 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -257,21 +257,21 @@
-
▴ Settings
diff --git a/htdocs/lib/UI.js b/htdocs/lib/UI.js
index 6c21e18c..6228fbad 100644
--- a/htdocs/lib/UI.js
+++ b/htdocs/lib/UI.js
@@ -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],
diff --git a/htdocs/lib/Waterfall.js b/htdocs/lib/Waterfall.js
new file mode 100644
index 00000000..acaf2096
--- /dev/null
+++ b/htdocs/lib/Waterfall.js
@@ -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);
+ }
+};
diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js
index 5ee0c306..47574c66 100644
--- a/htdocs/openwebrx.js
+++ b/htdocs/openwebrx.js
@@ -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();
}
diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py
index abd4a038..45785245 100644
--- a/owrx/controllers/assets.py
+++ b/owrx/controllers/assets.py
@@ -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",