diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 7a075e9..624382f 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -31,6 +31,7 @@ var fft_compression = "none"; var fft_codec; var waterfall_setup_done = 0; var secondary_fft_size; +var tuning_step = 1; function updateVolume() { audioEngine.setVolume(parseFloat($("#openwebrx-panel-volume").val()) / 100); @@ -268,7 +269,9 @@ function scale_canvas_mousedown(evt) { 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 / waterfallWidth())) - center_freq; + + var f = (visible_range.start + visible_range.bw * (x / waterfallWidth())) - center_freq; + return tuning_step>0? Math.round(f / tuning_step) * tuning_step : f; } function scale_canvas_mousemove(evt) { @@ -513,11 +516,16 @@ function resize_scale() { function canvas_get_freq_offset(relativeX) { var rel = (relativeX / canvas_container.clientWidth); - return Math.round((bandwidth * rel) - (bandwidth / 2)); + var off = (bandwidth * rel) - (bandwidth / 2); + + return tuning_step>0? + Math.round(off / tuning_step) * tuning_step : Math.round(off); } function canvas_get_frequency(relativeX) { - return center_freq + canvas_get_freq_offset(relativeX); + var f = center_freq + canvas_get_freq_offset(relativeX); + + return tuning_step>0? Math.round(f / tuning_step) * tuning_step : f; } @@ -535,16 +543,23 @@ function format_frequency(format, freq_hz, pre_divide, decimals) { var canvas_drag = false; var canvas_drag_min_delta = 1; var canvas_mouse_down = false; +var canvas_mouse2_down = 0; var canvas_drag_last_x; var canvas_drag_last_y; var canvas_drag_start_x; var canvas_drag_start_y; 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; + if (evt.button > 0) + if (canvas_mouse2_down == 0) + canvas_mouse2_down = evt.button; + else { + 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 } @@ -581,16 +596,21 @@ function canvas_container_mouseleave() { } function canvas_mouseup(evt) { - if (!waterfall_setup_done) return; - var relativeX = get_relative_x(evt); + if (evt.button > 0) { + if (evt.button == canvas_mouse2_down) + canvas_mouse2_down = 0; + } else { + if (!waterfall_setup_done) return; + var relativeX = get_relative_x(evt); - if (!canvas_drag) { - $('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().set_offset_frequency(canvas_get_freq_offset(relativeX)); + if (!canvas_drag) { + $('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().set_offset_frequency(canvas_get_freq_offset(relativeX)); + } + else { + canvas_end_drag(); + } + canvas_mouse_down = false; } - else { - canvas_end_drag(); - } - canvas_mouse_down = false; } function canvas_end_drag() { @@ -616,14 +636,18 @@ function get_relative_x(evt) { function canvas_mousewheel(evt) { if (!waterfall_setup_done) return; - - var delta = -evt.deltaY; - // deltaMode 0 means pixels instead of lines - if ('deltaMode' in evt && evt.deltaMode === 0) { - delta /= 50; - } var relativeX = get_relative_x(evt); - zoom_step(delta, relativeX, zoom_center_where_calc(evt.pageX)); + var dir = (evt.deltaY / Math.abs(evt.deltaY)) > 0; + + // Zoom when mouse button down, tune otherwise + if (canvas_mouse2_down > 0) { + zoom_step(dir, relativeX, zoom_center_where_calc(evt.pageX)); + } else { + var f = $('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().get_offset_frequency(); + f += dir? -tuning_step : tuning_step; + $('#openwebrx-panel-receiver').demodulatorPanel().getDemodulator().set_offset_frequency(f); + } + evt.preventDefault(); } @@ -783,6 +807,9 @@ function on_ws_recv(evt) { if ('tuning_precision' in config) $('#openwebrx-panel-receiver').demodulatorPanel().setTuningPrecision(config['tuning_precision']); + if ('tuning_step' in config) + tuning_step = config['tuning_step']; + break; case "secondary_config": var s = json['value']; diff --git a/owrx/config/defaults.py b/owrx/config/defaults.py index f03dded..e155fe9 100644 --- a/owrx/config/defaults.py +++ b/owrx/config/defaults.py @@ -33,6 +33,7 @@ defaultConfig = PropertyLayer( samp_rate=2400000, start_freq=439275000, start_mod="nfm", + tuning_step="1000", ), "2m": PropertyLayer( name="2m", @@ -41,6 +42,7 @@ defaultConfig = PropertyLayer( samp_rate=2048000, start_freq=145725000, start_mod="nfm", + tuning_step="1000", ), } ), @@ -57,6 +59,7 @@ defaultConfig = PropertyLayer( samp_rate=384000, start_freq=14070000, start_mod="usb", + tuning_step="500", ), "30m": PropertyLayer( name="30m", @@ -64,6 +67,7 @@ defaultConfig = PropertyLayer( samp_rate=192000, start_freq=10142000, start_mod="usb", + tuning_step="500", ), "40m": PropertyLayer( name="40m", @@ -71,6 +75,7 @@ defaultConfig = PropertyLayer( samp_rate=256000, start_freq=7070000, start_mod="lsb", + tuning_step="500", ), "80m": PropertyLayer( name="80m", @@ -78,6 +83,7 @@ defaultConfig = PropertyLayer( samp_rate=384000, start_freq=3570000, start_mod="lsb", + tuning_step="500", ), "49m": PropertyLayer( name="49m Broadcast", @@ -85,6 +91,7 @@ defaultConfig = PropertyLayer( samp_rate=384000, start_freq=6070000, start_mod="am", + tuning_step="1000", ), } ), @@ -102,6 +109,7 @@ defaultConfig = PropertyLayer( samp_rate=500000, start_freq=14070000, start_mod="usb", + tuning_step="500", ), "30m": PropertyLayer( name="30m", @@ -110,6 +118,7 @@ defaultConfig = PropertyLayer( samp_rate=250000, start_freq=10142000, start_mod="usb", + tuning_step="500", ), "40m": PropertyLayer( name="40m", @@ -118,6 +127,7 @@ defaultConfig = PropertyLayer( samp_rate=500000, start_freq=7070000, start_mod="lsb", + tuning_step="500", ), "80m": PropertyLayer( name="80m", @@ -126,6 +136,7 @@ defaultConfig = PropertyLayer( samp_rate=500000, start_freq=3570000, start_mod="lsb", + tuning_step="500", ), "49m": PropertyLayer( name="49m Broadcast", @@ -134,6 +145,7 @@ defaultConfig = PropertyLayer( samp_rate=500000, start_freq=6070000, start_mod="am", + tuning_step="1000", ), } ), diff --git a/owrx/connection.py b/owrx/connection.py index 699da31..d198160 100644 --- a/owrx/connection.py +++ b/owrx/connection.py @@ -120,6 +120,7 @@ class OpenWebRxReceiverClient(OpenWebRxClient, SdrSourceEventClient): "start_mod", "start_freq", "center_freq", + "tuning_step", "initial_squelch_level", "sdr_id", "profile_id", diff --git a/owrx/source/__init__.py b/owrx/source/__init__.py index ae790be..b933eb5 100644 --- a/owrx/source/__init__.py +++ b/owrx/source/__init__.py @@ -12,8 +12,8 @@ from owrx.command import CommandMapper from owrx.socket import getAvailablePort from owrx.property import PropertyStack, PropertyLayer, PropertyFilter, PropertyCarousel, PropertyDeleted from owrx.property.filter import ByLambda -from owrx.form.input import Input, TextInput, NumberInput, CheckboxInput, ModesInput, ExponentialInput -from owrx.form.input.converter import OptionalConverter +from owrx.form.input import Input, TextInput, NumberInput, CheckboxInput, ModesInput, ExponentialInput, DropdownInput, Option +from owrx.form.input.converter import OptionalConverter, IntConverter from owrx.form.input.device import GainInput, SchedulerInput, WaterfallLevelsInput from owrx.form.input.validator import RequiredValidator from owrx.form.section import OptionalSection @@ -565,6 +565,12 @@ class SdrDeviceDescription(object): ExponentialInput("samp_rate", "Sample rate", "S/s"), ExponentialInput("start_freq", "Initial frequency", "Hz"), ModesInput("start_mod", "Initial modulation"), + DropdownInput( + "tuning_step", + "Tuning step", + options=[Option(str(i), "{} Hz".format(i)) for i in [1, 100, 500, 1000, 2500, 3000, 5000, 6000, 10000, 12000, 50000]], + converter=IntConverter(), + ), NumberInput("initial_squelch_level", "Initial squelch level", append="dBFS"), ] @@ -589,7 +595,7 @@ class SdrDeviceDescription(object): return keys def getProfileMandatoryKeys(self): - return ["name", "center_freq", "samp_rate", "start_freq", "start_mod"] + return ["name", "center_freq", "samp_rate", "start_freq", "start_mod", "tuning_step"] def getProfileOptionalKeys(self): return ["initial_squelch_level", "rf_gain", "lfo_offset", "waterfall_levels"]