function DemodulatorPanel(el) { var self = this; self.el = el; self.demodulator = null; self.mode = null; self.squelchMargin = 10; self.initialParams = {}; var displayEl = el.find('.webrx-actual-freq') this.tuneableFrequencyDisplay = displayEl.tuneableFrequencyDisplay(); displayEl.on('frequencychange', function(event, freq) { self.getDemodulator().set_offset_frequency(freq - self.center_freq); }); this.mouseFrequencyDisplay = el.find('.webrx-mouse-freq').frequencyDisplay(); Modes.registerModePanel(this); el.on('click', '.openwebrx-demodulator-button', function() { var modulation = $(this).data('modulation'); if (modulation) { self.setMode(modulation); } else { self.disableDigiMode(); } }); el.on('change', '.openwebrx-secondary-demod-listbox', function() { var value = $(this).val(); if (value === 'none') { self.disableDigiMode(); } else { self.setMode(value); } }); el.on('click', '.openwebrx-squelch-auto', function() { if (!self.squelchAvailable()) return; el.find('.openwebrx-squelch-slider').val(getLogSmeterValue(smeter_level) + self.getSquelchMargin()); self.updateSquelch(); }); el.on('change', '.openwebrx-squelch-slider', function() { self.updateSquelch(); }); window.addEventListener('hashchange', function() { self.onHashChange(); }); }; DemodulatorPanel.prototype.render = function() { var available = Modes.getModes().filter(function(m){ return m.isAvailable(); }); var normalModes = available.filter(function(m){ return m.type === 'analog'; }); var digiModes = available.filter(function(m){ return m.type === 'digimode'; }); var html = [] var buttons = normalModes.map(function(m){ return $( '
' + m.name + '
' ); }); var $modegrid = $('
'); $modegrid.append.apply($modegrid, buttons); html.push($modegrid); html.push($( '
' + '
DIG
' + '' + '
' )); this.el.find(".openwebrx-modes").html(html); }; DemodulatorPanel.prototype.setMode = function(requestedModulation) { var mode = Modes.findByModulation(requestedModulation); if (!mode) { return; } if (this.mode === mode) { return; } if (!mode.isAvailable()) { divlog('Modulation "' + mode.name + '" not supported. Please check the feature report', true); return; } if (mode.type === 'digimode') { modulation = mode.underlying[0]; } else { if (this.mode && this.mode.type === 'digimode' && this.mode.underlying.indexOf(requestedModulation) >= 0) { // keep the mode, just switch underlying modulation mode = this.mode; modulation = requestedModulation; } else { modulation = mode.modulation; } } var current = this.collectParams(); if (this.demodulator) { current.offset_frequency = this.demodulator.get_offset_frequency(); current.squelch_level = this.demodulator.getSquelch(); } this.stopDemodulator(); this.demodulator = new Demodulator(current.offset_frequency, modulation); this.demodulator.setSquelch(current.squelch_level); var self = this; var updateFrequency = function(freq) { self.tuneableFrequencyDisplay.setFrequency(self.center_freq + freq); self.updateHash(); }; this.demodulator.on("frequencychange", updateFrequency); updateFrequency(this.demodulator.get_offset_frequency()); var updateSquelch = function(squelch) { self.el.find('.openwebrx-squelch-slider') .val(squelch) .attr('title', 'Squelch (' + squelch + ' dB)'); self.updateHash(); }; this.demodulator.on('squelchchange', updateSquelch); updateSquelch(this.demodulator.getSquelch()); if (mode.type === 'digimode') { this.demodulator.set_secondary_demod(mode.modulation); if (mode.bandpass) { this.demodulator.setBandpass(mode.bandpass); } } else { this.demodulator.set_secondary_demod(false); } this.demodulator.start(); this.mode = mode; this.updateButtons(); this.updatePanels(); this.updateHash(); }; DemodulatorPanel.prototype.disableDigiMode = function() { // just a little trick to get out of the digimode delete this.mode; this.setMode(this.getDemodulator().get_modulation()); }; DemodulatorPanel.prototype.updatePanels = function() { var modulation = this.getDemodulator().get_secondary_demod(); $('#openwebrx-panel-digimodes').attr('data-mode', modulation); toggle_panel("openwebrx-panel-digimodes", !!modulation); toggle_panel("openwebrx-panel-wsjt-message", ['ft8', 'wspr', 'jt65', 'jt9', 'ft4', 'fst4', 'fst4w', "q65"].indexOf(modulation) >= 0); toggle_panel("openwebrx-panel-js8-message", modulation == "js8"); toggle_panel("openwebrx-panel-packet-message", modulation === "packet"); toggle_panel("openwebrx-panel-pocsag-message", modulation === "pocsag"); modulation = this.getDemodulator().get_modulation(); var showing = 'openwebrx-panel-metadata-' + modulation; var metaPanels = $(".openwebrx-meta-panel"); metaPanels.each(function (_, p) { toggle_panel(p.id, p.id === showing); }); metaPanels.metaPanel().each(function() { this.clear(); }); }; DemodulatorPanel.prototype.getDemodulator = function() { return this.demodulator; }; DemodulatorPanel.prototype.collectParams = function() { var defaults = { offset_frequency: 0, squelch_level: -150, mod: 'nfm' } return $.extend(new Object(), defaults, this.validateInitialParams(this.initialParams), this.transformHashParams(this.parseHash())); }; DemodulatorPanel.prototype.startDemodulator = function() { if (!Modes.initComplete() || !this.center_freq) return; var params = this.collectParams(); this._apply(params); }; DemodulatorPanel.prototype.stopDemodulator = function() { if (!this.demodulator) { return; } this.demodulator.stop(); this.demodulator = null; this.mode = null; } DemodulatorPanel.prototype._apply = function(params) { this.setMode(params.mod); this.getDemodulator().set_offset_frequency(params.offset_frequency); this.getDemodulator().setSquelch(params.squelch_level); this.updateButtons(); }; DemodulatorPanel.prototype.setInitialParams = function(params) { $.extend(this.initialParams, params); }; DemodulatorPanel.prototype.resetInitialParams = function() { this.initialParams = {}; }; DemodulatorPanel.prototype.onHashChange = function() { this._apply(this.transformHashParams(this.parseHash())); }; DemodulatorPanel.prototype.transformHashParams = function(params) { var ret = { mod: params.secondary_mod || params.mod }; if (typeof(params.offset_frequency) !== 'undefined') ret.offset_frequency = params.offset_frequency; if (typeof(params.sql) !== 'undefined') ret.squelch_level = parseInt(params.sql); return ret; }; DemodulatorPanel.prototype.squelchAvailable = function () { return this.mode && this.mode.squelch; } DemodulatorPanel.prototype.updateButtons = function() { var $buttons = this.el.find(".openwebrx-demodulator-button"); $buttons.removeClass("highlighted").removeClass('same-mod'); var demod = this.getDemodulator() if (!demod) return; this.el.find('[data-modulation=' + demod.get_modulation() + ']').addClass("highlighted"); var secondary_demod = demod.get_secondary_demod() if (secondary_demod) { this.el.find(".openwebrx-button-dig").addClass("highlighted"); this.el.find('.openwebrx-secondary-demod-listbox').val(secondary_demod); var mode = Modes.findByModulation(secondary_demod); if (mode) { var self = this; mode.underlying.filter(function(m) { return m !== demod.get_modulation(); }).forEach(function(m) { self.el.find('[data-modulation=' + m + ']').addClass('same-mod') }); } } else { this.el.find('.openwebrx-secondary-demod-listbox').val('none'); } var squelch_disabled = !this.squelchAvailable(); this.el.find('.openwebrx-squelch-slider').prop('disabled', squelch_disabled); this.el.find('.openwebrx-squelch-auto')[squelch_disabled ? 'addClass' : 'removeClass']('disabled'); } DemodulatorPanel.prototype.setCenterFrequency = function(center_freq) { var me = this; if (me.centerFreqTimeout) { clearTimeout(me.centerFreqTimeout); me.centerFreqTimeout = false; } this.centerFreqTimeout = setTimeout(function() { me.stopDemodulator(); me.center_freq = center_freq; me.startDemodulator(); me.centerFreqTimeout = false; }, 50); }; DemodulatorPanel.prototype.parseHash = function() { if (!window.location.hash) { return {}; } var params = window.location.hash.substring(1).split(",").map(function(x) { var harr = x.split('='); return [harr[0], harr.slice(1).join('=')]; }).reduce(function(params, p){ params[p[0]] = p[1]; return params; }, {}); return this.validateHash(params); }; DemodulatorPanel.prototype.validateHash = function(params) { var self = this; params = Object.keys(params).filter(function(key) { if (key == 'freq' || key == 'mod' || key == 'secondary_mod' || key == 'sql') { return params.freq && Math.abs(params.freq - self.center_freq) <= bandwidth / 2; } return true; }).reduce(function(p, key) { p[key] = params[key]; return p; }, {}); if (params['freq']) { params['offset_frequency'] = params['freq'] - self.center_freq; delete params['freq']; } return params; }; DemodulatorPanel.prototype.validateInitialParams = function(params) { return Object.fromEntries( Object.entries(params).filter(function(a) { if (a[0] == "offset_frequency") { return Math.abs(a[1]) <= bandwidth / 2; } return true; }) ); }; DemodulatorPanel.prototype.updateHash = function() { var demod = this.getDemodulator(); if (!demod) return; var self = this; window.location.hash = $.map({ freq: demod.get_offset_frequency() + self.center_freq, mod: demod.get_modulation(), secondary_mod: demod.get_secondary_demod(), sql: demod.getSquelch(), }, function(value, key){ if (typeof(value) === 'undefined' || value === false) return undefined; return key + '=' + value; }).filter(function(v) { return !!v; }).join(','); }; DemodulatorPanel.prototype.updateSquelch = function() { var sliderValue = parseInt(this.el.find(".openwebrx-squelch-slider").val()); var demod = this.getDemodulator(); if (demod) demod.setSquelch(sliderValue); }; DemodulatorPanel.prototype.setSquelchMargin = function(margin) { if (typeof(margin) === 'undefined' || this.squelchMargin == margin) return; this.squelchMargin = margin; }; DemodulatorPanel.prototype.getSquelchMargin = function() { return this.squelchMargin; }; DemodulatorPanel.prototype.setMouseFrequency = function(freq) { this.mouseFrequencyDisplay.setFrequency(freq); }; DemodulatorPanel.prototype.setTuningPrecision = function(precision) { this.tuneableFrequencyDisplay.setTuningPrecision(precision); this.mouseFrequencyDisplay.setTuningPrecision(precision); }; $.fn.demodulatorPanel = function(){ if (!this.data('panel')) { this.data('panel', new DemodulatorPanel(this)); }; return this.data('panel'); };