From f977744be56aa98e5256631c2c93332fae62b15c Mon Sep 17 00:00:00 2001 From: Vladisslav P Date: Thu, 17 Feb 2022 06:18:22 +0300 Subject: [PATCH] Initial multivfo implementation Move ddc into receiver_base_cf Move defines and modulation type to dedicated files Implement config v4 Implement vfo selection by clickin the plotter widget Implement stereo panning feature to improve listening experience Implement locking vfos to a frequency and automatically delete locked vfos when moved out of bandwidth Add global lock/unlock context menu Add global squelch auto/reset popup menu Implement settings loading from a bookbark by right-clicking bookmark tag on a plotter Implement new vfo creation from a bookbark by middle-clicking bookmark tag on a plotter Add bookmarks context menu actions 1. To tune current demod to a frequency 2. To tune current demod to a frequency and load params from the bookmark 3. To create a new demod with the bookmark params Prevent new vfo creation over already active bookmark Replace selected bookmark instead of adding new one at the same frequency Update plotter overlay on bookmark delete Update plotter overlay on bookmarks save Save bookmarks on tag delete Store all receiver parameters to a bookmark Add a button to copy recording settings to all VFOs Sort bookmarks by any column Validate and correct filter params on input Make Modulations class fully static Allow to choose bookmarks table visible columns Select a bookmark on exact freq match only to avoid a situation when 2 bookmarks have overlapping filter passbands and wrong bookmark with wider filter passband is activated. Automatically create demodulators when a bookmark, marked as 'auto' gets visible on the panadapter. Add a switch to enable/disable this function. --- resources/icons.qrc | 49 +- resources/icons/add.svg | 25 + resources/icons/lock.svg | 402 +++++++++ resources/icons/remove.svg | 133 +++ src/applications/gqrx/mainwindow.cpp | 1007 +++++++++++++++++---- src/applications/gqrx/mainwindow.h | 31 +- src/applications/gqrx/mainwindow.ui | 34 +- src/applications/gqrx/receiver.cpp | 1013 +++++++++++++++------- src/applications/gqrx/receiver.h | 111 ++- src/applications/gqrx/remote_control.cpp | 68 +- src/applications/gqrx/remote_control.h | 8 +- src/dsp/rx_agc_xx.cpp | 88 +- src/dsp/rx_agc_xx.h | 16 +- src/dsp/rx_demod_am.cpp | 8 - src/dsp/rx_filter.h | 3 +- src/interfaces/wav_sink.cpp | 12 +- src/interfaces/wav_sink.h | 1 + src/qtgui/agc_options.cpp | 41 + src/qtgui/agc_options.h | 8 + src/qtgui/agc_options.ui | 136 ++- src/qtgui/audio_options.cpp | 5 + src/qtgui/audio_options.h | 2 + src/qtgui/audio_options.ui | 32 +- src/qtgui/bookmarks.cpp | 168 +++- src/qtgui/bookmarks.h | 41 +- src/qtgui/bookmarkstablemodel.cpp | 512 +++++++++-- src/qtgui/bookmarkstablemodel.h | 62 +- src/qtgui/demod_options.cpp | 10 + src/qtgui/demod_options.h | 3 + src/qtgui/dockaudio.cpp | 108 +-- src/qtgui/dockaudio.h | 28 +- src/qtgui/dockbookmarks.cpp | 163 +++- src/qtgui/dockbookmarks.h | 13 +- src/qtgui/dockinputctl.cpp | 16 + src/qtgui/dockinputctl.h | 2 + src/qtgui/dockinputctl.ui | 14 +- src/qtgui/dockrds.cpp | 10 +- src/qtgui/dockrds.h | 2 +- src/qtgui/dockrxopt.cpp | 497 +++++------ src/qtgui/dockrxopt.h | 96 +- src/qtgui/dockrxopt.ui | 71 +- src/qtgui/nb_options.cpp | 7 + src/qtgui/nb_options.h | 1 + src/qtgui/plotter.cpp | 148 +++- src/qtgui/plotter.h | 23 +- src/receivers/CMakeLists.txt | 5 + src/receivers/defines.h | 47 + src/receivers/modulations.cpp | 295 +++++++ src/receivers/modulations.h | 87 ++ src/receivers/nbrx.cpp | 245 +++--- src/receivers/nbrx.h | 49 +- src/receivers/receiver_base.cpp | 164 ++-- src/receivers/receiver_base.h | 112 +-- src/receivers/vfo.cpp | 244 ++++++ src/receivers/vfo.h | 236 +++++ src/receivers/wfmrx.cpp | 58 +- src/receivers/wfmrx.h | 43 +- 57 files changed, 5278 insertions(+), 1535 deletions(-) create mode 100644 resources/icons/add.svg create mode 100644 resources/icons/lock.svg create mode 100644 resources/icons/remove.svg create mode 100644 src/receivers/defines.h create mode 100644 src/receivers/modulations.cpp create mode 100644 src/receivers/modulations.h create mode 100644 src/receivers/vfo.cpp create mode 100644 src/receivers/vfo.h diff --git a/resources/icons.qrc b/resources/icons.qrc index 8f535b0992..71d341c7e7 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -1,25 +1,28 @@ - - icons/audio-card.svg - icons/bookmark-new.svg - icons/clear.svg - icons/clock.svg - icons/close.svg - icons/document.svg - icons/flash.svg - icons/folder.svg - icons/fullscreen.svg - icons/floppy.svg - icons/gqrx.svg - icons/help.svg - icons/info.svg - icons/play.svg - icons/power-off.svg - icons/record.svg - icons/refresh.svg - icons/settings.svg - icons/signal.svg - icons/tangeo-network-idle.svg - icons/terminal.svg - + + icons/lock.svg + icons/remove.svg + icons/add.svg + icons/audio-card.svg + icons/bookmark-new.svg + icons/clear.svg + icons/clock.svg + icons/close.svg + icons/document.svg + icons/flash.svg + icons/folder.svg + icons/fullscreen.svg + icons/floppy.svg + icons/gqrx.svg + icons/help.svg + icons/info.svg + icons/play.svg + icons/power-off.svg + icons/record.svg + icons/refresh.svg + icons/settings.svg + icons/signal.svg + icons/tangeo-network-idle.svg + icons/terminal.svg + diff --git a/resources/icons/add.svg b/resources/icons/add.svg new file mode 100644 index 0000000000..de7746458f --- /dev/null +++ b/resources/icons/add.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/lock.svg b/resources/icons/lock.svg new file mode 100644 index 0000000000..b84551c887 --- /dev/null +++ b/resources/icons/lock.svg @@ -0,0 +1,402 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Luca Ferretti <elle.uca@libero.it> + + + + + + monitor + display + video + screen + LCD + CRT + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg new file mode 100644 index 0000000000..3280a35948 --- /dev/null +++ b/resources/icons/remove.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + image/svg+xml + + + List remove + August 2006 + + + Andreas Nilsson + + + http://www.gnome.org + + + remove + minus + + + + + + + + + + + + + + + + + + + diff --git a/src/applications/gqrx/mainwindow.cpp b/src/applications/gqrx/mainwindow.cpp index 2950ee5d5d..7345358089 100644 --- a/src/applications/gqrx/mainwindow.cpp +++ b/src/applications/gqrx/mainwindow.cpp @@ -67,6 +67,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) ui(new Ui::MainWindow), d_lnb_lo(0), d_hw_freq(0), + d_auto_bookmarks(false), d_fftAvg(0.25), d_have_audio(true), dec_afsk1200(nullptr) @@ -94,7 +95,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) ui->freqCtrl->setup(0, 0, 9999e6, 1, FCTL_UNIT_NONE); ui->freqCtrl->setFrequency(144500000); - d_filter_shape = receiver::FILTER_SHAPE_NORMAL; + d_filter_shape = Modulations::FILTER_SHAPE_NORMAL; /* create receiver object */ rx = new receiver("", "", 1); @@ -198,7 +199,14 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) ui->menu_View->addSeparator(); ui->menu_View->addAction(ui->actionFullScreen); + /* Setup demodulator switching SpinBox */ + rxSpinBox = new QSpinBox(ui->mainToolBar); + rxSpinBox->setMaximum(255); + rxSpinBox->setValue(0); + ui->mainToolBar->insertWidget(ui->actionAddDemodulator, rxSpinBox); + /* connect signals and slots */ + connect(rxSpinBox, SIGNAL(valueChanged(int)), this, SLOT(rxSpinBox_valueChanged(int))); connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), this, SLOT(setNewFrequency(qint64))); connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), remote, SLOT(setNewFrequency(qint64))); connect(ui->freqCtrl, SIGNAL(newFrequency(qint64)), uiDockAudio, SLOT(setRxFrequency(qint64))); @@ -216,11 +224,12 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockInputCtl, SIGNAL(antennaSelected(QString)), this, SLOT(setAntenna(QString))); connect(uiDockInputCtl, SIGNAL(freqCtrlResetChanged(bool)), this, SLOT(setFreqCtrlReset(bool))); connect(uiDockInputCtl, SIGNAL(invertScrollingChanged(bool)), this, SLOT(setInvertScrolling(bool))); - connect(uiDockRxOpt, SIGNAL(rxFreqChanged(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64))); + connect(uiDockInputCtl, SIGNAL(autoBookmarksChanged(bool)), this, SLOT(setAutoBookmarks(bool))); + connect(uiDockRxOpt, SIGNAL(rxFreqChanged(qint64)), this, SLOT(setNewFrequency(qint64))); connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), this, SLOT(setFilterOffset(qint64))); connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), remote, SLOT(setFilterOffset(qint64))); - connect(uiDockRxOpt, SIGNAL(demodSelected(int)), this, SLOT(selectDemod(int))); - connect(uiDockRxOpt, SIGNAL(demodSelected(int)), remote, SLOT(setMode(int))); + connect(uiDockRxOpt, SIGNAL(demodSelected(Modulations::idx)), this, SLOT(selectDemod(Modulations::idx))); + connect(uiDockRxOpt, SIGNAL(demodSelected(Modulations::idx)), remote, SLOT(setMode(Modulations::idx))); connect(uiDockRxOpt, SIGNAL(fmMaxdevSelected(float)), this, SLOT(setFmMaxdev(float))); connect(uiDockRxOpt, SIGNAL(fmEmphSelected(double)), this, SLOT(setFmEmph(double))); connect(uiDockRxOpt, SIGNAL(amDcrToggled(bool)), this, SLOT(setAmDcr(bool))); @@ -234,9 +243,13 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockRxOpt, SIGNAL(agcAttackChanged(int)), this, SLOT(setAgcAttack(int))); connect(uiDockRxOpt, SIGNAL(agcDecayChanged(int)), this, SLOT(setAgcDecay(int))); connect(uiDockRxOpt, SIGNAL(agcHangChanged(int)), this, SLOT(setAgcHang(int))); + connect(uiDockRxOpt, SIGNAL(agcPanningChanged(int)), this, SLOT(setAgcPanning(int))); + connect(uiDockRxOpt, SIGNAL(agcPanningAuto(bool)), this, SLOT(setAgcPanningAuto(bool))); connect(uiDockRxOpt, SIGNAL(noiseBlankerChanged(int,bool,float)), this, SLOT(setNoiseBlanker(int,bool,float))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), this, SLOT(setSqlLevel(double))); - connect(uiDockRxOpt, SIGNAL(sqlAutoClicked()), this, SLOT(setSqlLevelAuto())); + connect(uiDockRxOpt, SIGNAL(sqlAutoClicked(bool)), this, SLOT(setSqlLevelAuto(bool))); + connect(uiDockRxOpt, SIGNAL(sqlResetAllClicked()), this, SLOT(resetSqlLevelGlobal())); + connect(uiDockRxOpt, SIGNAL(freqLock(bool, bool)), this, SLOT(setFreqLock(bool, bool))); connect(uiDockAudio, SIGNAL(audioGainChanged(float)), this, SLOT(setAudioGain(float))); connect(uiDockAudio, SIGNAL(audioMuteChanged(bool)), this, SLOT(setAudioMute(bool))); connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool))); @@ -252,6 +265,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockAudio, SIGNAL(recMinTimeChanged(int)), this, SLOT(recMinTimeChanged(int))); connect(uiDockAudio, SIGNAL(recMaxGapChanged(int)), this, SLOT(recMaxGapChanged(int))); connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int))); + connect(uiDockAudio, SIGNAL(copyRecSettingsToAllVFOs()), this, SLOT(copyRecSettingsToAllVFOs())); connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int))); connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int))); connect(uiDockFft, SIGNAL(fftWindowChanged(int)), this, SLOT(setIqFftWindow(int))); @@ -275,6 +289,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(ui->plotter, SIGNAL(newZoomLevel(float)), uiDockFft, SLOT(setZoomLevel(float))); connect(ui->plotter, SIGNAL(newSize()), this, SLOT(setWfSize())); + connect(ui->plotter, SIGNAL(selectVfo(int)), this, SLOT(on_plotter_selectVfo(int))); connect(uiDockFft, SIGNAL(fftColorChanged(QColor)), this, SLOT(setFftColor(QColor))); connect(uiDockFft, SIGNAL(fftFillToggled(bool)), this, SLOT(setFftFill(bool))); @@ -283,8 +298,12 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockRDS, SIGNAL(rdsDecoderToggled(bool)), this, SLOT(setRdsDecoder(bool))); // Bookmarks - connect(uiDockBookmarks, SIGNAL(newBookmarkActivated(qint64, QString, int)), this, SLOT(onBookmarkActivated(qint64, QString, int))); + connect(uiDockBookmarks, SIGNAL(newBookmarkActivated(BookmarkInfo &)), this, SLOT(onBookmarkActivated(BookmarkInfo &))); + //FIXME: create a new slot that would avoid changing hw frequency if the bookmark is in the current bandwidth + connect(uiDockBookmarks, SIGNAL(newBookmarkActivated(qint64)), this, SLOT(setNewFrequency(qint64))); + connect(uiDockBookmarks, SIGNAL(newBookmarkActivatedAddDemod(BookmarkInfo &)), this, SLOT(onBookmarkActivatedAddDemod(BookmarkInfo &))); connect(uiDockBookmarks->actionAddBookmark, SIGNAL(triggered()), this, SLOT(on_actionAddBookmark_triggered())); + connect(&Bookmarks::Get(), SIGNAL(BookmarksChanged()), ui->plotter, SLOT(updateOverlay())); //DXC Spots connect(&DXCSpots::Get(), SIGNAL(dxcSpotsUpdated()), this, SLOT(updateClusterSpots())); @@ -303,8 +322,8 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(remote, SIGNAL(newFrequency(qint64)), ui->freqCtrl, SLOT(setFrequency(qint64))); connect(remote, SIGNAL(newLnbLo(double)), uiDockInputCtl, SLOT(setLnbLo(double))); connect(remote, SIGNAL(newLnbLo(double)), this, SLOT(setLnbLo(double))); - connect(remote, SIGNAL(newMode(int)), this, SLOT(selectDemod(int))); - connect(remote, SIGNAL(newMode(int)), uiDockRxOpt, SLOT(setCurrentDemod(int))); + connect(remote, SIGNAL(newMode(Modulations::idx)), this, SLOT(selectDemod(Modulations::idx))); + connect(remote, SIGNAL(newMode(Modulations::idx)), uiDockRxOpt, SLOT(setCurrentDemod(Modulations::idx))); connect(remote, SIGNAL(newSquelchLevel(double)), this, SLOT(setSqlLevel(double))); connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double))); @@ -418,6 +437,7 @@ MainWindow::~MainWindow() delete [] d_realFftData; delete [] d_iirFftData; delete qsvg_dummy; + delete rxSpinBox; } /** @@ -445,6 +465,8 @@ bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash, bool conf_ok = false; bool conv_ok; bool skip_loading_cfg = false; + int ver = 0; + qint64 hw_freq = 0; qDebug() << "Loading configuration from:" << cfgfile; @@ -496,6 +518,7 @@ bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash, // manual reconf (FIXME: check status) conv_ok = false; + ver = m_settings->value("configversion").toInt(&conv_ok); // hide toolbar bool_val = m_settings->value("gui/hide_toolbar", false).toBool(); if (bool_val) @@ -631,36 +654,36 @@ bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash, } uiDockInputCtl->readSettings(m_settings); // this will also update freq range - uiDockRxOpt->readSettings(m_settings); - uiDockFft->readSettings(m_settings); - uiDockAudio->readSettings(m_settings); - dxc_options->readSettings(m_settings); - { - int64_val = m_settings->value("input/frequency", 14236000).toLongLong(&conv_ok); + int64_val = m_settings->value("input/frequency", 14236000).toLongLong(&conv_ok); - // If frequency is out of range set frequency to the center of the range. - qint64 hw_freq = int64_val - d_lnb_lo - (qint64)(rx->get_filter_offset()); - if (hw_freq < d_hw_freq_start || hw_freq > d_hw_freq_stop) - { - int64_val = (d_hw_freq_stop - d_hw_freq_start) / 2 + - (qint64)(rx->get_filter_offset()) + d_lnb_lo; - } + // If frequency is out of range set frequency to the center of the range. + hw_freq = int64_val - d_lnb_lo; + if (hw_freq < d_hw_freq_start || hw_freq > d_hw_freq_stop) + { + hw_freq = (d_hw_freq_stop - d_hw_freq_start) / 2; + int64_val = hw_freq + d_lnb_lo; + } - ui->freqCtrl->setFrequency(int64_val); + rx->set_rf_freq(hw_freq); + if (ver >= 4) + { + ui->freqCtrl->setFrequency(int64_val + (qint64)(rx->get_filter_offset())); setNewFrequency(ui->freqCtrl->getFrequency()); // ensure all GUI and RF is updated } - + readRXSettings(ver); + if (ver < 4) { - int flo = m_settings->value("receiver/filter_low_cut", 0).toInt(&conv_ok); - int fhi = m_settings->value("receiver/filter_high_cut", 0).toInt(&conv_ok); - - if (conv_ok && uiDockRxOpt->currentDemod() != DockRxOpt::MODE_OFF && flo != fhi) - { - on_plotter_newFilterFreq(flo, fhi); - } + rx->set_rf_freq(hw_freq - rx->get_filter_offset()); + ui->freqCtrl->setFrequency(hw_freq + d_lnb_lo); + setNewFrequency(hw_freq + d_lnb_lo); } + uiDockFft->readSettings(m_settings); + uiDockBookmarks->readSettings(m_settings); + uiDockAudio->readSettings(m_settings); + dxc_options->readSettings(m_settings); + iq_tool->readSettings(m_settings); /* @@ -744,29 +767,329 @@ void MainWindow::storeSession() { if (m_settings) { - m_settings->setValue("input/frequency", ui->freqCtrl->getFrequency()); + int rx_count = rx->get_rx_count(); + m_settings->setValue("configversion", (rx_count <= 1) ? 3 : 4); + for (int i = 0; true; i++) + { + QString grp = QString("rx%1").arg(i); + QString offset = QString("rx%1/offset").arg(i); + if (m_settings->contains(offset)) + m_settings->remove(grp); + else + break; + } + m_settings->remove("audio"); + m_settings->remove("receiver"); + if (rx_count <= 1) + m_settings->setValue("input/frequency", qint64(rx->get_rf_freq() + d_lnb_lo + rx->get_filter_offset())); + else + m_settings->setValue("input/frequency", qint64(rx->get_rf_freq() + d_lnb_lo)); uiDockInputCtl->saveSettings(m_settings); - uiDockRxOpt->saveSettings(m_settings); uiDockFft->saveSettings(m_settings); uiDockAudio->saveSettings(m_settings); + uiDockBookmarks->saveSettings(m_settings); remote->saveSettings(m_settings); iq_tool->saveSettings(m_settings); dxc_options->saveSettings(m_settings); + int old_current = rx->get_current(); + int int_val; + for (int i = 0; i < rx_count; i++) { + if (rx_count <= 1) + m_settings->beginGroup("receiver"); + else + m_settings->beginGroup(QString("rx%1").arg(i)); + m_settings->remove(""); + rx->fake_select_rx(i); + + m_settings->setValue("demod", Modulations::GetStringForModulationIndex(rx->get_demod())); + + int cwofs = rx->get_cw_offset(); + if (cwofs == 700) + m_settings->remove("cwoffset"); + else + m_settings->setValue("cwoffset", cwofs); + + // currently we do not need the decimal + int_val = (int)rx->get_fm_maxdev(); + if (int_val == 2500) + m_settings->remove("fm_maxdev"); + else + m_settings->setValue("fm_maxdev", int_val); + + // save as usec + int_val = (int)(1.0e6 * rx->get_fm_deemph()); + if (int_val == 75) + m_settings->remove("fm_deemph"); + else + m_settings->setValue("fm_deemph", int_val); + + qint64 offs = rx->get_filter_offset(); + if (offs) + { + m_settings->setValue("offset", offs); + } + else + m_settings->remove("offset"); + + if (rx->get_freq_lock()) + m_settings->setValue("freq_locked", true); + else + m_settings->remove("freq_locked"); + + double sql_lvl = rx->get_sql_level(); + if (sql_lvl > -150.0) + m_settings->setValue("sql_level", sql_lvl); + else + m_settings->remove("sql_level"); + + // AGC settings + int_val = rx->get_agc_target_level(); + if (int_val != 0) + m_settings->setValue("agc_target_level", int_val); + else + m_settings->remove("agc_target_level"); + + int_val = rx->get_agc_attack(); + if (int_val != 20) + m_settings->setValue("agc_attack", int_val); + else + m_settings->remove("agc_decay"); + + int_val = rx->get_agc_decay(); + if (int_val != 500) + m_settings->setValue("agc_decay", int_val); + else + m_settings->remove("agc_decay"); + + int_val = rx->get_agc_hang(); + if (int_val != 0) + m_settings->setValue("agc_hang", int_val); + else + m_settings->remove("agc_hang"); + + int_val = rx->get_agc_panning(); + if (int_val != 0) + m_settings->setValue("agc_panning", int_val); + else + m_settings->remove("agc_panning"); + + if (rx->get_agc_panning_auto()) + m_settings->setValue("agc_panning_auto", true); + else + m_settings->remove("agc_panning_auto"); + + int_val = rx->get_agc_max_gain(); + if (int_val != 100) + m_settings->setValue("agc_maxgain", int_val); + else + m_settings->remove("agc_maxgain"); + + // AGC Off + if (!rx->get_agc_on()) + m_settings->setValue("agc_off", true); + else + m_settings->remove("agc_off"); + //filter int flo, fhi; - ui->plotter->getHiLowCutFrequencies(&flo, &fhi); + receiver::filter_shape fdw; + rx->get_filter(flo, fhi, fdw); if (flo != fhi) { - m_settings->setValue("receiver/filter_low_cut", flo); - m_settings->setValue("receiver/filter_high_cut", fhi); + m_settings->setValue("filter_low_cut", flo); + m_settings->setValue("filter_high_cut", fhi); + m_settings->setValue("filter_shape", fdw); } + + if (rx_count <= 1) + { + m_settings->endGroup(); + m_settings->beginGroup("audio"); + } + if (rx->get_audio_rec_dir() != QDir::homePath().toStdString()) + m_settings->setValue("rec_dir", rx->get_audio_rec_dir().data()); + else + m_settings->remove("rec_dir"); + + if (rx->get_audio_rec_sql_triggered() != false) + m_settings->setValue("squelch_triggered_recording", true); + else + m_settings->remove("squelch_triggered_recording"); + + int_val = rx->get_audio_rec_min_time(); + if (int_val != 0) + m_settings->setValue("rec_min_time", int_val); + else + m_settings->remove("rec_min_time"); + + int_val = rx->get_audio_rec_max_gap(); + if (int_val != 0) + m_settings->setValue("rec_max_gap", int_val); + else + m_settings->remove("rec_max_gap"); + + m_settings->endGroup(); + if (rx_count <= 1) + break; } + rx->fake_select_rx(old_current); + if (rx_count > 1) + m_settings->setValue("gui/current_rx", old_current); + else + m_settings->remove("gui/current_rx"); } } +void MainWindow::readRXSettings(int ver) +{ + bool conv_ok; + int int_val; + double dbl_val; + int i = 0; + rxSpinBox->setMaximum(0); + while (rx->get_rx_count() > 1) + rx->delete_rx(); + ui->plotter->setCurrentVfo(0); + ui->plotter->clearVfos(); + QString grp = (ver >= 4) ? QString("rx%1").arg(i) : "receiver"; + while (1) + { + m_settings->beginGroup(grp); + + qint64 offs = m_settings->value("offset", 0).toInt(&conv_ok); + if (conv_ok) + rx->set_filter_offset(offs); + + if (m_settings->value("freq_locked", false).toBool()) + rx->set_freq_lock(true); + else + rx->set_freq_lock(false); + + int_val = Modulations::MODE_AM; + if (m_settings->contains("demod")) { + if (ver >= 3) { + int_val = Modulations::GetEnumForModulationString(m_settings->value("demod").toString()); + } else { + int_val = Modulations::ConvertFromOld(m_settings->value("demod").toInt(&conv_ok)); + } + } + rx->set_demod(Modulations::idx(int_val)); + + int_val = m_settings->value("cwoffset", 700).toInt(&conv_ok); + if (conv_ok) + rx->set_cw_offset(int_val); + + int_val = m_settings->value("fm_maxdev", 2500).toInt(&conv_ok); + if (conv_ok) + rx->set_fm_maxdev(int_val); + + dbl_val = m_settings->value("fm_deemph", 75).toDouble(&conv_ok); + if (conv_ok && dbl_val >= 0) + rx->set_fm_deemph(1.0e-6 * dbl_val); // was stored as usec + + dbl_val = m_settings->value("sql_level", 1.0).toDouble(&conv_ok); + if (conv_ok && dbl_val < 1.0) + rx->set_sql_level(dbl_val); + + // AGC settings + int_val = m_settings->value("agc_target_level", 0).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_target_level(int_val); + + //TODO: store/restore the preset correctly + int_val = m_settings->value("agc_decay", 500).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_decay(int_val); + + int_val = m_settings->value("agc_attack", 20).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_attack(int_val); + + int_val = m_settings->value("agc_hang", 0).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_hang(int_val); + + int_val = m_settings->value("agc_panning", 0).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_panning(int_val); + + if (m_settings->value("agc_panning_auto", false).toBool()) + rx->set_agc_panning_auto(true); + else + rx->set_agc_panning_auto(false); + + int_val = m_settings->value("agc_maxgain", 100).toInt(&conv_ok); + if (conv_ok) + rx->set_agc_max_gain(int_val); + + if (m_settings->value("agc_off", false).toBool()) + rx->set_agc_on(false); + else + rx->set_agc_on(true); + + bool flo_ok = false; + bool fhi_ok = false; + int flo = m_settings->value("filter_low_cut", 0).toInt(&flo_ok); + int fhi = m_settings->value("filter_high_cut", 0).toInt(&fhi_ok); + int_val = m_settings->value("filter_shape", Modulations::FILTER_SHAPE_NORMAL).toInt(&conv_ok); + + if (flo != fhi) + rx->set_filter(flo, fhi, receiver::filter_shape(int_val)); + + if (ver < 4) + { + m_settings->endGroup(); + m_settings->beginGroup("audio"); + } + int_val = m_settings->value("gain", QVariant(-60)).toInt(&conv_ok); + if (conv_ok) + if (!rx->get_agc_on()) + rx->set_agc_manual_gain(int_val); + + QString rec_dir = m_settings->value("rec_dir", QDir::homePath()).toString(); + rx->set_audio_rec_dir(rec_dir.toStdString()); + + bool squelch_triggered = m_settings->value("squelch_triggered_recording", false).toBool(); + rx->set_audio_rec_sql_triggered(squelch_triggered); + + int_val = m_settings->value("rec_min_time", 0).toInt(&conv_ok); + if (!conv_ok) + int_val = 0; + rx->set_audio_rec_min_time(int_val); + + int_val = m_settings->value("rec_max_gap", 0).toInt(&conv_ok); + if (!conv_ok) + int_val = 0; + rx->set_audio_rec_max_gap(int_val); + + m_settings->endGroup(); + ui->plotter->addVfo(rx->get_current_vfo()); + i++; + if (ver < 4) + break; + grp = QString("rx%1").arg(i); + if (!m_settings->contains(grp + "/offset")) + break; + rx->add_rx(); + } + if (ver >= 4) + int_val = m_settings->value("gui/current_rx", 0).toInt(&conv_ok); + else + conv_ok = false; + if (!conv_ok) + int_val = 0; + rxSpinBox->setMaximum(rx->get_rx_count() - 1); + ui->plotter->removeVfo(rx->get_vfo(int_val)); + rx->select_rx(int_val); + ui->plotter->setCurrentVfo(int_val); + if (rxSpinBox->value() != int_val) + rxSpinBox->setValue(int_val); + loadRxToGUI(); +} + /** * @brief Update hardware RF frequency range. * @param ignore_limits Whether ignore the hardware specd and allow DC-to-light @@ -862,19 +1185,152 @@ void MainWindow::updateGainStages(bool read_from_device) */ void MainWindow::setNewFrequency(qint64 rx_freq) { - auto hw_freq = (double)(rx_freq - d_lnb_lo) - rx->get_filter_offset(); - auto center_freq = rx_freq - (qint64)rx->get_filter_offset(); - - d_hw_freq = (qint64)hw_freq; + auto new_offset = rx->get_filter_offset(); + auto hw_freq = (double)(rx_freq - d_lnb_lo) - new_offset; + auto center_freq = rx_freq - (qint64)new_offset; + auto delta_freq = d_hw_freq; + QList bml; // set receiver frequency rx->set_rf_freq(hw_freq); + d_hw_freq = d_ignore_limits ? hw_freq : (qint64)rx->get_rf_freq(); + if (rx->is_playing_iq() || (d_hw_freq != (qint64)hw_freq)) + { + new_offset = rx_freq - d_lnb_lo - d_hw_freq; + if (d_hw_freq != (qint64)hw_freq) + { + center_freq = d_hw_freq + d_lnb_lo; + // set RX filter + rx->set_filter_offset((double)new_offset); + + // update RF freq label and channel filter offset + rx_freq = center_freq + new_offset; + } + } + delta_freq -= d_hw_freq; // update widgets ui->plotter->setCenterFreq(center_freq); uiDockRxOpt->setHwFreq(d_hw_freq); ui->freqCtrl->setFrequency(rx_freq); uiDockBookmarks->setNewFrequency(rx_freq); + remote->setNewFrequency(rx_freq); + uiDockAudio->setRxFrequency(rx_freq); + if (rx->is_rds_decoder_active()) + rx->reset_rds_parser(); + if (delta_freq) + { + std::set del_list; + if (rx->get_rx_count() > 1) + { + std::vector locked_vfos; + int offset_lim = (int)(ui->plotter->getSampleRate() / 2); + ui->plotter->getLockedVfos(locked_vfos); + for (auto& cvfo : locked_vfos) + { + ui->plotter->removeVfo(cvfo); + int new_offset = cvfo->get_offset() + delta_freq; + if ((new_offset > offset_lim) || (new_offset < -offset_lim)) + del_list.insert(cvfo->get_index()); + else + { + rx->set_filter_offset(cvfo->get_index(), new_offset); + ui->plotter->addVfo(cvfo); + } + } + } + + if (d_auto_bookmarks) + { + //calculate frequency range to search for auto bookmarks + qint64 from = 0, to = 0; + qint64 sr = ui->plotter->getSampleRate(); + if (delta_freq > 0) + { + if (delta_freq > sr) + { + from = center_freq - sr / 2; + to = center_freq + sr / 2; + } + else + { + from = center_freq - sr / 2; + to = center_freq - sr / 2 + delta_freq; + } + } + else + { + if (-delta_freq > sr) + { + from = center_freq - sr / 2; + to = center_freq + sr / 2; + } + else + { + from = center_freq + sr / 2 + delta_freq; + to = center_freq + sr / 2; + } + } + bml = Bookmarks::Get().getBookmarksInRange(from, to, true); + } + + if ((del_list.size() > 0)||(bml.size() > 0)) + { + int current = rx->get_current(); + if (ui->actionDSP->isChecked()) + rx->stop(); + for (auto& bm : bml) + { + int n = rx->add_rx(); + if (n > 0) + { + rxSpinBox->setMaximum(rx->get_rx_count() - 1); + rx->set_demod(bm.get_demod()); + // preserve squelch level, force locked state + auto old_vfo = rx->get_current_vfo(); + auto old_sql = old_vfo->get_sql_level(); + old_vfo->restore_settings(bm, false); + old_vfo->set_sql_level(old_sql); + old_vfo->set_offset(bm.frequency - center_freq); + old_vfo->set_freq_lock(true); + ui->plotter->addVfo(old_vfo); + rx->select_rx(current); + } + } + if (del_list.size() > 0) + { + int lastCurrent = rx->get_current(); + for (auto i = del_list.rbegin(); i != del_list.rend(); ++i) + { + int last = rx->get_rx_count() - 1; + rx->select_rx(*i); + if (lastCurrent == last) + { + lastCurrent = *i; + last = -1; + } + else + if (*i != last) + { + ui->plotter->removeVfo(rx->get_vfo(last)); + last = *i; + } + else + last = -1; + rx->delete_rx(); + if (last != -1) + ui->plotter->addVfo(rx->get_vfo(last)); + } + rx->select_rx(lastCurrent); + ui->plotter->setCurrentVfo(lastCurrent); + rxSpinBox->setMaximum(rx->get_rx_count() - 1); + rxSpinBox->setValue(lastCurrent); + } + if (ui->actionDSP->isChecked()) + rx->start(); + ui->plotter->updateOverlay(); + } + } } /** @@ -1037,15 +1493,21 @@ void MainWindow::setInvertScrolling(bool enabled) uiDockAudio->setInvertScrolling(enabled); } +/** Invert scroll wheel direction */ +void MainWindow::setAutoBookmarks(bool enabled) +{ + d_auto_bookmarks = enabled; +} + /** * @brief Select new demodulator. * @param demod New demodulator. */ void MainWindow::selectDemod(const QString& strModulation) { - int iDemodIndex; + Modulations::idx iDemodIndex; - iDemodIndex = DockRxOpt::GetEnumForModulationString(strModulation); + iDemodIndex = Modulations::GetEnumForModulationString(strModulation); qDebug() << "selectDemod(str):" << strModulation << "-> IDX:" << iDemodIndex; return selectDemod(iDemodIndex); @@ -1059,134 +1521,177 @@ void MainWindow::selectDemod(const QString& strModulation) * and configures the default channel filter. * */ -void MainWindow::selectDemod(int mode_idx) +void MainWindow::selectDemod(Modulations::idx mode_idx) { - double cwofs = 0.0; int filter_preset = uiDockRxOpt->currentFilter(); - int flo=0, fhi=0, click_res=100; + int flo=0, fhi=0; + Modulations::filter_shape filter_shape; bool rds_enabled; // validate mode_idx - if (mode_idx < DockRxOpt::MODE_OFF || mode_idx >= DockRxOpt::MODE_LAST) + if (mode_idx < Modulations::MODE_OFF || mode_idx >= Modulations::MODE_LAST) { qDebug() << "Invalid mode index:" << mode_idx; - mode_idx = DockRxOpt::MODE_OFF; + mode_idx = Modulations::MODE_OFF; } qDebug() << "New mode index:" << mode_idx; - uiDockRxOpt->getFilterPreset(mode_idx, filter_preset, &flo, &fhi); d_filter_shape = (receiver::filter_shape)uiDockRxOpt->currentFilterShape(); + rx->get_filter(flo, fhi, filter_shape); + if (filter_preset == FILTER_PRESET_USER) + { + if (((rx->get_demod() == Modulations::MODE_USB) && + (mode_idx == Modulations::MODE_LSB)) + || + ((rx->get_demod() == Modulations::MODE_LSB) && + (mode_idx == Modulations::MODE_USB))) + { + std::swap(flo, fhi); + flo = -flo; + fhi = -fhi; + filter_preset = FILTER_PRESET_USER; + } + Modulations::UpdateFilterRange(mode_idx, flo, fhi); + } + if (filter_preset != FILTER_PRESET_USER) + { + Modulations::GetFilterPreset(mode_idx, filter_preset, flo, fhi); + } - rds_enabled = rx->is_rds_decoder_active(); - if (rds_enabled) - setRdsDecoder(false); - uiDockRDS->setDisabled(); + if (mode_idx != rx->get_demod()) + { + rds_enabled = rx->is_rds_decoder_active(); + if (rds_enabled) + setRdsDecoder(false); + uiDockRDS->setDisabled(); + + if ((mode_idx >=Modulations::MODE_OFF) && (mode_idx set_demod(mode_idx); + + switch (mode_idx) { + case Modulations::MODE_OFF: + /* Spectrum analyzer only */ + if (rx->is_recording_audio()) + { + stopAudioRec(); + uiDockAudio->setAudioRecButtonState(false); + } + break; + case Modulations::MODE_AM: + case Modulations::MODE_AM_SYNC: + case Modulations::MODE_USB: + case Modulations::MODE_LSB: + case Modulations::MODE_CWL: + case Modulations::MODE_CWU: + break; + + case Modulations::MODE_NFM: + rx->set_fm_maxdev(uiDockRxOpt->currentMaxdev()); + rx->set_fm_deemph(uiDockRxOpt->currentEmph()); + break; + + case Modulations::MODE_WFM_MONO: + case Modulations::MODE_WFM_STEREO: + case Modulations::MODE_WFM_STEREO_OIRT: + /* Broadcast FM */ + uiDockRDS->setEnabled(); + if (rds_enabled) + setRdsDecoder(true); + break; + + default: + qDebug() << "Unsupported mode selection (can't happen!): " << mode_idx; + flo = -5000; + fhi = 5000; + break; + } + } + rx->set_filter(flo, fhi, d_filter_shape); + updateDemodGUIRanges(); +} + +/** + * @brief Update GUI after demodulator selection. + * + * Update plotter demod ranges + * Update audio dock fft range + * Update plotter cut frequencies + * Update plotter click resolution + * Update plotter filter click resolution + * Update remote settings too + * + */ +void MainWindow::updateDemodGUIRanges() +{ + int click_res=100; + int flo=0, fhi=0, loMin, loMax, hiMin,hiMax; + Modulations::filter_shape filter_shape; + rx->get_filter(flo, fhi, filter_shape); + Modulations::idx mode_idx = rx->get_demod(); + Modulations::GetFilterRanges(mode_idx, loMin, loMax, hiMin, hiMax); + ui->plotter->setDemodRanges(loMin, loMax, hiMin, hiMax, hiMax == -loMin); switch (mode_idx) { - case DockRxOpt::MODE_OFF: + case Modulations::MODE_OFF: /* Spectrum analyzer only */ - if (rx->is_recording_audio()) - { - stopAudioRec(); - uiDockAudio->setAudioRecButtonState(false); - } - if (dec_afsk1200 != nullptr) - { - dec_afsk1200->close(); - } - rx->set_demod(receiver::RX_DEMOD_OFF); click_res = 1000; break; - case DockRxOpt::MODE_RAW: + case Modulations::MODE_RAW: /* Raw I/Q; max 96 ksps*/ - rx->set_demod(receiver::RX_DEMOD_NONE); - ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true); uiDockAudio->setFftRange(0,24000); click_res = 100; break; - case DockRxOpt::MODE_AM: - rx->set_demod(receiver::RX_DEMOD_AM); - ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true); + case Modulations::MODE_AM: uiDockAudio->setFftRange(0,6000); click_res = 100; break; - case DockRxOpt::MODE_AM_SYNC: - rx->set_demod(receiver::RX_DEMOD_AMSYNC); - ui->plotter->setDemodRanges(-40000, -200, 200, 40000, true); + case Modulations::MODE_AM_SYNC: uiDockAudio->setFftRange(0,6000); click_res = 100; break; - case DockRxOpt::MODE_NFM: - ui->plotter->setDemodRanges(-40000, -1000, 1000, 40000, true); + case Modulations::MODE_NFM: uiDockAudio->setFftRange(0, 5000); - rx->set_demod(receiver::RX_DEMOD_NFM); - rx->set_fm_maxdev(uiDockRxOpt->currentMaxdev()); - rx->set_fm_deemph(uiDockRxOpt->currentEmph()); click_res = 100; break; - case DockRxOpt::MODE_WFM_MONO: - case DockRxOpt::MODE_WFM_STEREO: - case DockRxOpt::MODE_WFM_STEREO_OIRT: + case Modulations::MODE_WFM_MONO: + case Modulations::MODE_WFM_STEREO: + case Modulations::MODE_WFM_STEREO_OIRT: /* Broadcast FM */ - ui->plotter->setDemodRanges(-120e3, -10000, 10000, 120e3, true); uiDockAudio->setFftRange(0,24000); /** FIXME: get audio rate from rx **/ click_res = 1000; - if (mode_idx == DockRxOpt::MODE_WFM_MONO) - rx->set_demod(receiver::RX_DEMOD_WFM_M); - else if (mode_idx == DockRxOpt::MODE_WFM_STEREO_OIRT) - rx->set_demod(receiver::RX_DEMOD_WFM_S_OIRT); - else - rx->set_demod(receiver::RX_DEMOD_WFM_S); - - uiDockRDS->setEnabled(); - if (rds_enabled) - setRdsDecoder(true); break; - case DockRxOpt::MODE_LSB: + case Modulations::MODE_LSB: /* LSB */ - rx->set_demod(receiver::RX_DEMOD_SSB); - ui->plotter->setDemodRanges(-40000, -100, -5000, 0, false); uiDockAudio->setFftRange(0,3000); click_res = 100; break; - case DockRxOpt::MODE_USB: + case Modulations::MODE_USB: /* USB */ - rx->set_demod(receiver::RX_DEMOD_SSB); - ui->plotter->setDemodRanges(0, 5000, 100, 40000, false); uiDockAudio->setFftRange(0,3000); click_res = 100; break; - case DockRxOpt::MODE_CWL: + case Modulations::MODE_CWL: /* CW-L */ - rx->set_demod(receiver::RX_DEMOD_SSB); - cwofs = -uiDockRxOpt->getCwOffset(); - ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true); uiDockAudio->setFftRange(0,1500); click_res = 10; break; - case DockRxOpt::MODE_CWU: + case Modulations::MODE_CWU: /* CW-U */ - rx->set_demod(receiver::RX_DEMOD_SSB); - cwofs = uiDockRxOpt->getCwOffset(); - ui->plotter->setDemodRanges(-5000, -100, 100, 5000, true); uiDockAudio->setFftRange(0,1500); click_res = 10; break; default: - qDebug() << "Unsupported mode selection (can't happen!): " << mode_idx; - flo = -5000; - fhi = 5000; click_res = 100; break; } @@ -1195,23 +1700,12 @@ void MainWindow::selectDemod(int mode_idx) ui->plotter->setHiLowCutFrequencies(flo, fhi); ui->plotter->setClickResolution(click_res); ui->plotter->setFilterClickResolution(click_res); - rx->set_filter((double)flo, (double)fhi, d_filter_shape); - rx->set_cw_offset(cwofs); - rx->set_sql_level(uiDockRxOpt->currentSquelchLevel()); - - //Call wrapper to update enable/disabled state - setAgcOn(uiDockRxOpt->getAgcOn()); - rx->set_agc_target_level(uiDockRxOpt->getAgcTargetLevel()); - rx->set_agc_manual_gain(uiDockAudio->audioGain() / 10.0); - rx->set_agc_max_gain(uiDockRxOpt->getAgcMaxGain()); - rx->set_agc_attack(uiDockRxOpt->getAgcAttack()); - rx->set_agc_decay(uiDockRxOpt->getAgcDecay()); - rx->set_agc_hang(uiDockRxOpt->getAgcHang()); + uiDockRxOpt->setFilterParam(flo, fhi); remote->setMode(mode_idx); remote->setPassband(flo, fhi); - d_have_audio = (mode_idx != DockRxOpt::MODE_OFF); + d_have_audio = (mode_idx != Modulations::MODE_OFF); uiDockRxOpt->setCurrentDemod(mode_idx); } @@ -1339,6 +1833,18 @@ void MainWindow::setAgcDecay(int msec) rx->set_agc_decay(msec); } +/** AGC panning changed. */ +void MainWindow::setAgcPanning(int panning) +{ + rx->set_agc_panning(panning); +} + +/** AGC panning auto changed. */ +void MainWindow::setAgcPanningAuto(bool panningAuto) +{ + rx->set_agc_panning_auto(panningAuto); +} + /** * @brief Noise blanker configuration changed. * @param nb1 Noise blanker 1 ON/OFF. @@ -1368,8 +1874,10 @@ void MainWindow::setSqlLevel(double level_db) * @brief Squelch level auto clicked. * @return The new squelch level. */ -double MainWindow::setSqlLevelAuto() +double MainWindow::setSqlLevelAuto(bool global) { + if (global) + rx->set_sql_level(3.0, true, true); double level = rx->get_signal_pwr() + 3.0; if (level > -10.0) // avoid 0 dBFS level = uiDockRxOpt->getSqlLevel(); @@ -1378,6 +1886,14 @@ double MainWindow::setSqlLevelAuto() return level; } +/** + * @brief Squelch level reset all clicked. + */ +void MainWindow::resetSqlLevelGlobal() +{ + rx->set_sql_level(-150.0, true, false); +} + /** Signal strength meter timeout. */ void MainWindow::meterTimeout() { @@ -1558,11 +2074,13 @@ void MainWindow::stopAudioRec() /** Audio recording is started or stopped. */ void MainWindow::audioRecEvent(const QString filename, bool is_running) { - if(is_running) + if (is_running) { ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename)); uiDockAudio->audioRecStarted(QString(filename)); - }else{ + } + else + { /* reset state of record button */ uiDockAudio->audioRecStopped(); ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000); @@ -1602,6 +2120,18 @@ void MainWindow::stopAudioPlayback() } } +void MainWindow::copyRecSettingsToAllVFOs() +{ + std::vector vfos = rx->get_vfos(); + for (auto& cvfo : vfos) + if (cvfo->get_index() != rx->get_current()) + { + cvfo->set_audio_rec_dir(rx->get_audio_rec_dir()); + cvfo->set_audio_rec_min_time(rx->get_audio_rec_min_time()); + cvfo->set_audio_rec_max_gap(rx->get_audio_rec_max_gap()); + } +} + /** Start streaming audio over UDP. */ void MainWindow::startAudioStream(const QString& udp_host, int udp_port, bool stereo) { @@ -1744,7 +2274,7 @@ void MainWindow::stopIqPlayback() qint64 oldOffset = m_settings->value("receiver/offset", 0).toLongLong(&offsetOK); if (centerOK && offsetOK) { - on_plotter_newDemodFreq(oldCenter, oldOffset); + on_plotter_newDemodFreq(oldCenter + oldOffset, oldOffset); } if (ui->actionDSP->isChecked()) @@ -2078,6 +2608,42 @@ void MainWindow::on_plotter_newDemodFreq(qint64 freq, qint64 delta) rx->reset_rds_parser(); } +/* CPlotter::NewDemodFreqLoad() is emitted */ +/* tune and load demodulator settings */ +void MainWindow::on_plotter_newDemodFreqLoad(qint64 freq, qint64 delta) +{ + // set RX filter + if (delta != qint64(rx->get_filter_offset())) + { + rx->set_filter_offset((double) delta); + updateFrequencyRange(); + } + + QList tags = + Bookmarks::Get().getBookmarksInRange(freq, freq); + if (tags.size() > 0) + { + onBookmarkActivated(tags.first()); + } + else + setNewFrequency(freq); +} + +/* CPlotter::NewDemodFreqLoad() is emitted */ +/* new demodulator here */ +void MainWindow::on_plotter_newDemodFreqAdd(qint64 freq, qint64 delta) +{ + vfo::sptr found = rx->find_vfo(freq - d_lnb_lo); + if (!found) + on_actionAddDemodulator_triggered(); + else + { + rxSpinBox->setValue(found->get_index()); + rxSpinBox_valueChanged(found->get_index()); + } + on_plotter_newDemodFreqLoad(freq, delta); +} + /* CPlotter::NewfilterFreq() is emitted or bookmark activated */ void MainWindow::on_plotter_newFilterFreq(int low, int high) { /* parameter correctness will be checked in receiver class */ @@ -2238,33 +2804,28 @@ void MainWindow::setRdsDecoder(bool checked) remote->setRDSstatus(checked); } -void MainWindow::onBookmarkActivated(qint64 freq, const QString& demod, int bandwidth) +void MainWindow::onBookmarkActivated(BookmarkInfo & bm) { - setNewFrequency(freq); - selectDemod(demod); - - /* Check if filter is symmetric or not by checking the presets */ - auto mode = uiDockRxOpt->currentDemod(); - auto preset = uiDockRxOpt->currentFilter(); - - int lo, hi; - uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi); + setNewFrequency(bm.frequency); + selectDemod(bm.get_demod()); + // preserve offset, squelch level, force locked state + auto old_vfo = rx->get_current_vfo(); + auto old_offset = old_vfo->get_offset(); + auto old_sql = old_vfo->get_sql_level(); + rx->get_current_vfo()->restore_settings(bm, false); + old_vfo->set_sql_level(old_sql); + old_vfo->set_offset(old_offset); + old_vfo->set_freq_lock(true); + loadRxToGUI(); +} - if(lo + hi == 0) - { - lo = -bandwidth / 2; - hi = bandwidth / 2; - } - else if(lo >= 0 && hi >= 0) - { - hi = lo + bandwidth; - } - else if(lo <= 0 && hi <= 0) +void MainWindow::onBookmarkActivatedAddDemod(BookmarkInfo & bm) +{ + if (!rx->find_vfo(bm.frequency - d_lnb_lo)) { - lo = hi - bandwidth; + on_actionAddDemodulator_triggered(); + onBookmarkActivated(bm); } - - on_plotter_newFilterFreq(lo, hi); } void MainWindow::setPassband(int bandwidth) @@ -2274,18 +2835,18 @@ void MainWindow::setPassband(int bandwidth) auto preset = uiDockRxOpt->currentFilter(); int lo, hi; - uiDockRxOpt->getFilterPreset(mode, preset, &lo, &hi); + Modulations::GetFilterPreset(mode, preset, lo, hi); - if(lo + hi == 0) + if (lo + hi == 0) { lo = -bandwidth / 2; hi = bandwidth / 2; } - else if(lo >= 0 && hi >= 0) + else if (lo >= 0 && hi >= 0) { hi = lo + bandwidth; } - else if(lo <= 0 && hi <= 0) + else if (lo <= 0 && hi <= 0) { lo = hi - bandwidth; } @@ -2295,6 +2856,11 @@ void MainWindow::setPassband(int bandwidth) on_plotter_newFilterFreq(lo, hi); } +void MainWindow::setFreqLock(bool lock, bool all) +{ + rx->set_freq_lock(lock, all); +} + /** Launch Gqrx google group website. */ void MainWindow::on_actionUserGroup_triggered() { @@ -2429,7 +2995,9 @@ void MainWindow::on_actionAddBookmark_triggered() bool ok=false; QString name; QStringList tags; + const qint64 freq = ui->freqCtrl->getFrequency(); + QList bookmarkFound = Bookmarks::Get().getBookmarksInRange(freq, freq); // Create and show the Dialog for a new Bookmark. // Write the result into variable 'name'. { @@ -2462,6 +3030,11 @@ void MainWindow::on_actionAddBookmark_triggered() mainLayout->addWidget(taglist); mainLayout->addWidget(buttonBox); + if (bookmarkFound.size()) + { + textfield->setText(bookmarkFound.first().name); + taglist->setSelectedTags(bookmarkFound.first().tags); + } ok = dialog.exec(); if (ok) { @@ -2477,13 +3050,13 @@ void MainWindow::on_actionAddBookmark_triggered() } // Add new Bookmark to Bookmarks. - if(ok) + if (ok) { int i; BookmarkInfo info; + info.restore_settings(*rx->get_current_vfo().get()); info.frequency = ui->freqCtrl->getFrequency(); - info.bandwidth = ui->plotter->getFilterBw(); info.modulation = uiDockRxOpt->currentDemodAsString(); info.name=name; info.tags.clear(); @@ -2494,10 +3067,134 @@ void MainWindow::on_actionAddBookmark_triggered() for (i = 0; i < tags.size(); ++i) info.tags.append(&Bookmarks::Get().findOrAddTag(tags[i])); + //FIXME: implement Bookmarks::replace(&BookmarkInfo, &BookmarkInfo) method + if (bookmarkFound.size()) + { + info.set_freq_lock(bookmarkFound.first().get_freq_lock()); + Bookmarks::Get().remove(bookmarkFound.first()); + } + else + info.set_freq_lock(false); Bookmarks::Get().add(info); uiDockBookmarks->updateTags(); - uiDockBookmarks->updateBookmarks(); - ui->plotter->updateOverlay(); + } +} + +void MainWindow::on_actionAddDemodulator_triggered() +{ + ui->plotter->addVfo(rx->get_current_vfo()); + int n = rx->add_rx(); + ui->plotter->setCurrentVfo(rx->get_rx_count() - 1); + rxSpinBox->setMaximum(rx->get_rx_count() - 1); + rxSpinBox->setValue(n); +} + +void MainWindow::on_actionRemoveDemodulator_triggered() +{ + int old_current = rx->get_current(); + if (old_current != rx->get_rx_count() - 1) + ui->plotter->removeVfo(rx->get_vfo(rx->get_rx_count() - 1)); + int n = rx->delete_rx(); + rxSpinBox->setValue(n); + rxSpinBox->setMaximum(rx->get_rx_count() - 1); + loadRxToGUI(); + if (old_current != n) + ui->plotter->removeVfo(rx->get_vfo(n)); + ui->plotter->setCurrentVfo(n); +} + +void MainWindow::rxSpinBox_valueChanged(int i) +{ + if (i == rx->get_current()) + return; + ui->plotter->addVfo(rx->get_current_vfo()); + int n = rx->select_rx(i); + ui->plotter->removeVfo(rx->get_current_vfo()); + ui->plotter->setCurrentVfo(i); + if (n == receiver::STATUS_OK) + loadRxToGUI(); +} + +void MainWindow::on_plotter_selectVfo(int i) +{ + rxSpinBox->setValue(i); +} + +void MainWindow::loadRxToGUI() +{ + auto rf_freq = rx->get_rf_freq(); + auto new_offset = rx->get_filter_offset(); + auto rx_freq = (double)(rf_freq + d_lnb_lo + new_offset); + + int low, high; + receiver::filter_shape fs; + auto mode_idx = rx->get_demod(); + + ui->plotter->setFilterOffset(new_offset); + uiDockRxOpt->setRxFreq(rx_freq); + uiDockRxOpt->setHwFreq(d_hw_freq); + uiDockRxOpt->setFilterOffset(new_offset); + + ui->freqCtrl->setFrequency(rx_freq); + uiDockBookmarks->setNewFrequency(rx_freq); + remote->setNewFrequency(rx_freq); + uiDockAudio->setRxFrequency(rx_freq); + + if (rx->is_rds_decoder_active()) + rx->reset_rds_parser(); + + rx->get_filter(low, high, fs); + updateDemodGUIRanges(); + uiDockRxOpt->setFreqLock(rx->get_freq_lock()); + uiDockRxOpt->setCurrentFilterShape(fs); + uiDockRxOpt->setFilterParam(low, high); + + uiDockRxOpt->setSquelchLevel(rx->get_sql_level()); + + uiDockRxOpt->setAgcOn(rx->get_agc_on()); + uiDockAudio->setGainEnabled(!rx->get_agc_on()); + uiDockRxOpt->setAgcTargetLevel(rx->get_agc_target_level()); + uiDockRxOpt->setAgcMaxGain(rx->get_agc_max_gain()); + uiDockRxOpt->setAgcAttack(rx->get_agc_attack()); + uiDockRxOpt->setAgcDecay(rx->get_agc_decay()); + uiDockRxOpt->setAgcHang(rx->get_agc_hang()); + uiDockRxOpt->setAgcPanning(rx->get_agc_panning()); + uiDockRxOpt->setAgcPanningAuto(rx->get_agc_panning_auto()); + if (!rx->get_agc_on()) + uiDockAudio->setAudioGain(rx->get_agc_manual_gain() * 10.0); + + uiDockRxOpt->setAmDcr(rx->get_am_dcr()); + uiDockRxOpt->setAmSyncDcr(rx->get_amsync_dcr()); + uiDockRxOpt->setAmSyncPllBw(rx->get_amsync_pll_bw()); + uiDockRxOpt->setFmMaxdev(rx->get_fm_maxdev()); + uiDockRxOpt->setFmEmph(rx->get_fm_deemph()); + uiDockRxOpt->setCwOffset(rx->get_cw_offset()); + + for (int k = 1; k < 3; k++) + uiDockRxOpt->setNoiseBlanker(k,rx->get_nb_on(k), rx->get_nb_threshold(k)); + + uiDockAudio->setRecDir(QString(rx->get_audio_rec_dir().data())); + uiDockAudio->setSquelchTriggered(rx->get_audio_rec_sql_triggered()); + uiDockAudio->setRecMinTime(rx->get_audio_rec_min_time()); + uiDockAudio->setRecMaxGap(rx->get_audio_rec_max_gap()); + + //FIXME Prevent playing incomplete audio or remove audio player + if (rx->is_recording_audio()) + uiDockAudio->audioRecStarted(QString(rx->get_last_audio_filename().data())); + else + uiDockAudio->audioRecStopped(); + d_have_audio = (mode_idx != Modulations::MODE_OFF); + switch (mode_idx) + { + case Modulations::MODE_WFM_MONO: + case Modulations::MODE_WFM_STEREO: + case Modulations::MODE_WFM_STEREO_OIRT: + uiDockRDS->setEnabled(); + setRdsDecoder(rx->is_rds_decoder_active()); + break; + default: + uiDockRDS->setDisabled(); + setRdsDecoder(false); } } diff --git a/src/applications/gqrx/mainwindow.h b/src/applications/gqrx/mainwindow.h index d1a22c5c0b..7aac5c3a35 100644 --- a/src/applications/gqrx/mainwindow.h +++ b/src/applications/gqrx/mainwindow.h @@ -32,6 +32,7 @@ #include #include #include +#include #include "qtgui/dockrxopt.h" #include "qtgui/dockaudio.h" @@ -64,6 +65,7 @@ class MainWindow : public QMainWindow bool loadConfig(const QString& cfgfile, bool check_crash, bool restore_mainwindow); bool saveConfig(const QString& cfgfile); + void readRXSettings(int ver); void storeSession(); bool configOk; /*!< Main app uses this flag to know whether we should abort or continue. */ @@ -84,7 +86,10 @@ public slots: qint64 d_hw_freq_start{}; qint64 d_hw_freq_stop{}; - enum receiver::filter_shape d_filter_shape; + bool d_ignore_limits; + bool d_auto_bookmarks; + + Modulations::filter_shape d_filter_shape; std::complex* d_fftData; float *d_realFftData; float *d_iirFftData; @@ -120,6 +125,7 @@ public slots: std::map devList; + QSpinBox *rxSpinBox; // dummy widget to enforce linking to QtSvg QSvgWidget *qsvg_dummy; @@ -133,6 +139,7 @@ public slots: void frequencyFocusShortcut(); void audioRecEventEmitter(std::string filename, bool is_running); static void audio_rec_event(MainWindow *self, std::string filename, bool is_running); + void loadRxToGUI(); private slots: /* RecentConfig */ @@ -153,8 +160,10 @@ private slots: void setIgnoreLimits(bool ignore_limits); void setFreqCtrlReset(bool enabled); void setInvertScrolling(bool enabled); + void setAutoBookmarks(bool enabled); void selectDemod(const QString& demod); - void selectDemod(int index); + void selectDemod(Modulations::idx index); + void updateDemodGUIRanges(); void setFmMaxdev(float max_dev); void setFmEmph(double tau); void setAmDcr(bool enabled); @@ -168,12 +177,16 @@ private slots: void setAgcDecay(int msec); void setAgcGain(int gain); void setAgcMaxGain(int gain); + void setAgcPanning(int panning); + void setAgcPanningAuto(bool panningAuto); void setNoiseBlanker(int nbid, bool on, float threshold); void setSqlLevel(double level_db); - double setSqlLevelAuto(); + double setSqlLevelAuto(bool global); + void resetSqlLevelGlobal(); void setAudioGain(float gain); void setAudioMute(bool mute); void setPassband(int bandwidth); + void setFreqLock(bool lock, bool all); /* audio recording and playback */ void recDirChanged(const QString dir); @@ -185,6 +198,7 @@ private slots: void audioRecEvent(const QString filename, bool is_running); void startAudioPlayback(const QString& filename); void stopAudioPlayback(); + void copyRecSettingsToAllVFOs(); void startAudioStream(const QString& udp_host, int udp_port, bool stereo); void stopAudioStreaming(); @@ -211,14 +225,18 @@ private slots: void setWfSize(); /* FFT plot */ - void on_plotter_newDemodFreq(qint64 freq, qint64 delta); /*! New demod freq (aka. filter offset). */ + void on_plotter_newDemodFreq(qint64 freq, qint64 delta); /*! New demod freq (aka. filter offset). */ + void on_plotter_newDemodFreqLoad(qint64 freq, qint64 delta);/* tune and load demodulator settings */ + void on_plotter_newDemodFreqAdd(qint64 freq, qint64 delta); /* new demodulator here */ void on_plotter_newFilterFreq(int low, int high); /*! New filter width */ + void on_plotter_selectVfo(int i); /* RDS */ void setRdsDecoder(bool checked); /* Bookmarks */ - void onBookmarkActivated(qint64 freq, const QString& demod, int bandwidth); + void onBookmarkActivated(BookmarkInfo & bm); + void onBookmarkActivatedAddDemod(BookmarkInfo & bm); /* DXC Spots */ void updateClusterSpots(); @@ -242,6 +260,9 @@ private slots: void on_actionAboutQt_triggered(); void on_actionAddBookmark_triggered(); void on_actionDX_Cluster_triggered(); + void on_actionAddDemodulator_triggered(); + void on_actionRemoveDemodulator_triggered(); + void rxSpinBox_valueChanged(int i); /* window close signals */ diff --git a/src/applications/gqrx/mainwindow.ui b/src/applications/gqrx/mainwindow.ui index bc4b09cdda..9c80f27099 100644 --- a/src/applications/gqrx/mainwindow.ui +++ b/src/applications/gqrx/mainwindow.ui @@ -195,7 +195,7 @@ 0 0 521 - 24 + 23 @@ -284,6 +284,8 @@ + + @@ -575,6 +577,36 @@ Ctrl+C + + + + :/icons/icons/add.svg:/icons/icons/add.svg + + + Add demodulator + + + Add demodulator + + + Ins + + + + + + :/icons/icons/remove.svg:/icons/icons/remove.svg + + + Remove demodulator + + + Remove current selected demodulator + + + Alt+Del + + diff --git a/src/applications/gqrx/receiver.cpp b/src/applications/gqrx/receiver.cpp index 3b34c109a7..ad552c731d 100644 --- a/src/applications/gqrx/receiver.cpp +++ b/src/applications/gqrx/receiver.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,6 @@ #include #endif -#define TARGET_QUAD_RATE 1e6 /** * @brief Public constructor. @@ -57,21 +57,19 @@ receiver::receiver(const std::string input_device, const std::string audio_device, unsigned int decimation) - : d_running(false), + : d_current(-1), + d_active(0), + d_running(false), d_input_rate(96000.0), d_audio_rate(48000), d_decim(decimation), d_rf_freq(144800000.0), - d_filter_offset(0.0), - d_cw_offset(0.0), d_recording_iq(false), - d_recording_wav(false), d_sniffer_active(false), d_iq_rev(false), d_dc_cancel(false), d_iq_balance(false), - d_mute(false), - d_demod(RX_DEMOD_OFF) + d_mute(false) { tb = gr::make_top_block("gqrx"); @@ -108,12 +106,18 @@ receiver::receiver(const std::string input_device, d_decim_rate = d_input_rate; } - d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); - d_quad_rate = d_decim_rate / d_ddc_decim; - ddc = make_downconverter_cc(d_ddc_decim, 0.0, d_decim_rate); - rx = make_nbrx(d_quad_rate, d_audio_rate); + rx.reserve(RX_MAX); + rx.clear(); + d_current = 0; + rx.push_back(make_nbrx(d_decim_rate, d_audio_rate)); + rx[d_current]->set_index(d_current); + rx[d_current]->set_rec_event_handler(std::bind(audio_rec_event, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); iq_swap = make_iq_swap_cc(false); + iq_src = iq_swap; dc_corr = make_dc_corr_cc(d_decim_rate, 1.0); iq_fft = make_rx_fft_c(8192u, d_decim_rate, gr::fft::window::WIN_HANN); @@ -121,6 +125,12 @@ receiver::receiver(const std::string input_device, audio_udp_sink = make_udp_sink_f(); + add0 = gr::blocks::add_ff::make(1); + add1 = gr::blocks::add_ff::make(1); + mc0 = gr::blocks::multiply_const_ff::make(1.0, 1); + mc1 = gr::blocks::multiply_const_ff::make(1.0, 1); + null_src = gr::blocks::null_source::make(sizeof(float)); + #ifdef WITH_PULSEAUDIO audio_snk = make_pa_sink(audio_device, d_audio_rate, "GQRX", "Audio output"); #elif WITH_PORTAUDIO @@ -137,14 +147,13 @@ receiver::receiver(const std::string input_device, sniffer = make_sniffer_f(); /* sniffer_rr is created at each activation. */ - set_demod(RX_DEMOD_NFM); + set_demod(Modulations::MODE_OFF); + reconnect_all(); gr::prefs pref; qDebug() << "Using audio backend:" << pref.get_string("audio", "audio_module", "N/A").c_str(); - rx->set_rec_event_handler(std::bind(audio_rec_event, this, - std::placeholders::_1, - std::placeholders::_2)); + } receiver::~receiver() @@ -198,25 +207,20 @@ void receiver::set_input_device(const std::string device) tb->wait(); } - if (d_decim >= 2) - { - tb->disconnect(src, 0, input_decim, 0); - tb->disconnect(input_decim, 0, iq_swap, 0); - } - else - { - tb->disconnect(src, 0, iq_swap, 0); - } + tb->disconnect_all(); + for (auto& rxc : rx) + rxc->connected(false); #if GNURADIO_VERSION < 0x030802 //Work around GNU Radio bug #3184 //temporarily connect dummy source to ensure that previous device is closed src = osmosdr::source::make("file="+escape_filename(get_zero_file())+",freq=428e6,rate=96000,repeat=true,throttle=true"); tb->connect(src, 0, iq_swap, 0); + tb->connect(iq_swap, 0, iq_fft, 0); tb->start(); tb->stop(); tb->wait(); - tb->disconnect(src, 0, iq_swap, 0); + tb->disconnect_all(); #else src.reset(); #endif @@ -230,20 +234,10 @@ void receiver::set_input_device(const std::string device) error = x.what(); src = osmosdr::source::make("file="+escape_filename(get_zero_file())+",freq=428e6,rate=96000,repeat=true,throttle=true"); } - - if(src->get_sample_rate() != 0) + reconnect_all(true); + if (src->get_sample_rate() != 0) set_input_rate(src->get_sample_rate()); - if (d_decim >= 2) - { - tb->connect(src, 0, input_decim, 0); - tb->connect(input_decim, 0, iq_swap, 0); - } - else - { - tb->connect(src, 0, iq_swap, 0); - } - if (d_running) tb->start(); @@ -268,14 +262,11 @@ void receiver::set_output_device(const std::string device) tb->lock(); - if (d_demod != RX_DEMOD_OFF) + if (d_active > 0) { - try - { + try { tb->disconnect(audio_snk); - } - catch(std::exception &x) - { + } catch(std::exception &x) { } } audio_snk.reset(); @@ -289,10 +280,10 @@ void receiver::set_output_device(const std::string device) audio_snk = gr::audio::sink::make(d_audio_rate, device, true); #endif - if ((d_demod != RX_DEMOD_OFF) && !d_mute) + if (d_active > 0) { - tb->connect(rx, 0, audio_snk, 0); - tb->connect(rx, 1, audio_snk, 1); + tb->connect(mc0, 0, audio_snk, 0); + tb->connect(mc1, 0, audio_snk, 1); } tb->unlock(); @@ -361,11 +352,9 @@ double receiver::set_input_rate(double rate) } d_decim_rate = d_input_rate / (double)d_decim; - d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); - d_quad_rate = d_decim_rate / d_ddc_decim; dc_corr->set_sample_rate(d_decim_rate); - ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); - rx->set_quad_rate(d_quad_rate); + for (auto& rxc : rx) + rxc->set_quad_rate(d_decim_rate); iq_fft->set_quad_rate(d_decim_rate); tb->unlock(); @@ -418,11 +407,9 @@ unsigned int receiver::set_input_decim(unsigned int decim) } // update quadrature rate - d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); - d_quad_rate = d_decim_rate / d_ddc_decim; dc_corr->set_sample_rate(d_decim_rate); - ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); - rx->set_quad_rate(d_quad_rate); + for (auto& rxc : rx) + rxc->set_quad_rate(d_decim_rate); iq_fft->set_quad_rate(d_decim_rate); if (d_decim >= 2) @@ -495,7 +482,7 @@ void receiver::set_dc_cancel(bool enable) // until we have a way to switch on/off // inside the dc_corr_cc we do a reconf - set_demod(d_demod, true); + reconnect_all(true); } /** @@ -543,7 +530,8 @@ receiver::status receiver::set_rf_freq(double freq_hz) d_rf_freq = freq_hz; src->set_center_freq(d_rf_freq); - rx->set_center_freq(d_rf_freq);//to generate audio filename + for (auto& rxc : rx) + rxc->set_center_freq(d_rf_freq);//to generate audio filename // FIXME: read back frequency? return STATUS_OK; @@ -643,6 +631,156 @@ receiver::status receiver::set_auto_gain(bool automatic) return STATUS_OK; } +/** + * @brief Add new demodulator and select it + * @return current rx index or -1 if an error occurs. + */ +int receiver::add_rx() +{ + if (rx.size() == RX_MAX) + return -1; + //tb->lock(); does not allow block input count changing + if (d_running) + { + tb->stop(); + tb->wait(); + } + if (d_current >= 0) + background_rx(); + rx.push_back(make_nbrx(d_decim_rate, d_audio_rate)); + int old = d_current; + d_current = rx.size() - 1; + rx[d_current]->set_index(d_current); + rx[d_current]->set_rec_event_handler(std::bind(audio_rec_event, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + set_demod_locked(rx[old]->get_demod(), old); + if (d_running) + tb->start(); + return d_current; +} + +/** + * @brief Get demodulator count + * @return demodulator count. + */ +int receiver::get_rx_count() +{ + return rx.size(); +} + +/** + * @brief Delete selected demodulator, select nearest remaining demodulator. + * @return selected rx index or -1 if there are 0 demodulators remaining. + */ +int receiver::delete_rx() +{ + if (d_current == -1) + return -1; + if (rx.size() <= 1) + return 0; + //tb->lock(); does not allow block input count changing + if (d_running) + { + tb->stop(); + tb->wait(); + } + background_rx(); + disconnect_rx(); + rx[d_current].reset(); + if (rx.size() > 1) + { + if (d_current != int(rx.size()) - 1) + { + disconnect_rx(rx.back()->get_index()); + rx[d_current] = rx.back(); + rx[d_current]->set_index(d_current); + rx.back().reset(); + connect_rx(); + } + else + d_current--; + } + else + { + d_current = -1; + } + rx.pop_back(); + foreground_rx(); + if (d_running) + tb->start(); + return d_current; +} + +/** + * @brief Selects a demodulator. + * @return STATUS_OK or STATUS_ERROR (if requested demodulator does not exist). + */ +receiver::status receiver::select_rx(int no) +{ + if (no == d_current) + return STATUS_OK; + if (no < int(rx.size())) + { + tb->lock(); + if (d_current >= 0) + background_rx(); + d_current = no; + foreground_rx(); + tb->unlock(); + return STATUS_OK; + } + return STATUS_ERROR; +} + +receiver::status receiver::fake_select_rx(int no) +{ + if (no == d_current) + return STATUS_OK; + if (no < int(rx.size())) + { + d_current = no; + return STATUS_OK; + } + return STATUS_ERROR; +} + +int receiver::get_current() +{ + return d_current; +} + +vfo::sptr receiver::get_current_vfo() +{ + return get_vfo(d_current); +} + +vfo::sptr receiver::find_vfo(int64_t freq) +{ + vfo::sptr notfound; + int64_t offset = freq - d_rf_freq; + //FIXME: speedup with index??? + for (auto& rxc : rx) + if (rxc->get_offset() == offset) + return rxc; + return notfound; +} + +vfo::sptr receiver::get_vfo(int n) +{ + return rx[n]; +} + +std::vector receiver::get_vfos() +{ + std::vector vfos; + vfos.reserve(rx.size()); + for (auto& rxc : rx) + vfos.push_back(rxc); + return vfos; +} + /** * @brief Set filter offset. * @param offset_hz The desired filter offset in Hz. @@ -659,9 +797,15 @@ receiver::status receiver::set_auto_gain(bool automatic) */ receiver::status receiver::set_filter_offset(double offset_hz) { - d_filter_offset = offset_hz; - ddc->set_center_freq(d_filter_offset - d_cw_offset); - rx->set_offset(offset_hz);//to generate audio filename from + set_filter_offset(d_current, offset_hz); + return STATUS_OK; +} + +receiver::status receiver::set_filter_offset(int rx_index, double offset_hz) +{ + rx[rx_index]->set_offset(offset_hz);//to generate audio filename from + if (rx[rx_index]->get_agc_panning_auto()) + rx[rx_index]->set_agc_panning(offset_hz * 200.0 / d_decim_rate); return STATUS_OK; } @@ -673,50 +817,51 @@ receiver::status receiver::set_filter_offset(double offset_hz) */ double receiver::get_filter_offset(void) const { - return d_filter_offset; + return rx[d_current]->get_offset(); } +void receiver::set_freq_lock(bool on, bool all) +{ + if (all) + for (auto& rxc : rx) + rxc->set_freq_lock(on); + else + rx[d_current]->set_freq_lock(on); +} + +bool receiver::get_freq_lock() +{ + return rx[d_current]->get_freq_lock(); +} + + /* CW offset can serve as a "BFO" if the GUI needs it */ receiver::status receiver::set_cw_offset(double offset_hz) { - d_cw_offset = offset_hz; - ddc->set_center_freq(d_filter_offset - d_cw_offset); - rx->set_cw_offset(d_cw_offset); + rx[d_current]->set_cw_offset(offset_hz); return STATUS_OK; } double receiver::get_cw_offset(void) const { - return d_cw_offset; + return rx[d_current]->get_cw_offset(); } -receiver::status receiver::set_filter(double low, double high, filter_shape shape) +receiver::status receiver::set_filter(int low, int high, filter_shape shape) { - double trans_width; - if ((low >= high) || (std::abs(high-low) < RX_FILTER_MIN_WIDTH)) return STATUS_ERROR; - switch (shape) { - - case FILTER_SHAPE_SOFT: - trans_width = std::abs(high - low) * 0.5; - break; - - case FILTER_SHAPE_SHARP: - trans_width = std::abs(high - low) * 0.1; - break; - - case FILTER_SHAPE_NORMAL: - default: - trans_width = std::abs(high - low) * 0.2; - break; - - } - - rx->set_filter(low, high, trans_width); + rx[d_current]->set_filter(low, high, Modulations::TwFromFilterShape(low, high, shape)); + return STATUS_OK; +} +receiver::status receiver::get_filter(int &low, int &high, filter_shape &shape) +{ + int tw; + rx[d_current]->get_filter(low, high, tw); + shape = Modulations::FilterShapeFromTw(low, high, tw); return STATUS_OK; } @@ -737,7 +882,7 @@ receiver::status receiver::set_freq_corr(double ppm) */ float receiver::get_signal_pwr() const { - return rx->get_signal_level(); + return rx[d_current]->get_signal_level(); } /** Set new FFT size. */ @@ -765,41 +910,68 @@ void receiver::get_audio_fft_data(std::complex* fftPoints, unsigned int & receiver::status receiver::set_nb_on(int nbid, bool on) { - if (rx->has_nb()) - rx->set_nb_on(nbid, on); + rx[d_current]->set_nb_on(nbid, on); return STATUS_OK; // FIXME } +bool receiver::get_nb_on(int nbid) +{ + return rx[d_current]->get_nb_on(nbid); +} + receiver::status receiver::set_nb_threshold(int nbid, float threshold) { - if (rx->has_nb()) - rx->set_nb_threshold(nbid, threshold); + rx[d_current]->set_nb_threshold(nbid, threshold); return STATUS_OK; // FIXME } +float receiver::get_nb_threshold(int nbid) +{ + return rx[d_current]->get_nb_threshold(nbid); +} + /** * @brief Set squelch level. * @param level_db The new level in dBFS. */ receiver::status receiver::set_sql_level(double level_db) { - if (rx->has_sql()) - rx->set_sql_level(level_db); + rx[d_current]->set_sql_level(level_db); return STATUS_OK; // FIXME } +receiver::status receiver::set_sql_level(double level_offset, bool global, bool relative) +{ + if (global) + for (auto& rxc: rx) + rxc->set_sql_level((relative ? rxc->get_signal_level() : 0) + level_offset); + else + rx[d_current]->set_sql_level((relative ? rx[d_current]->get_signal_level() : 0) + level_offset); + + return STATUS_OK; // FIXME +} + +double receiver::get_sql_level() +{ + return rx[d_current]->get_sql_level(); +} + /** Set squelch alpha */ receiver::status receiver::set_sql_alpha(double alpha) { - if (rx->has_sql()) - rx->set_sql_alpha(alpha); + rx[d_current]->set_sql_alpha(alpha); return STATUS_OK; // FIXME } +double receiver::get_sql_alpha() +{ + return rx[d_current]->get_sql_alpha(); +} + /** * @brief Enable/disable receiver AGC. * @@ -807,73 +979,126 @@ receiver::status receiver::set_sql_alpha(double alpha) */ receiver::status receiver::set_agc_on(bool agc_on) { - if (rx->has_agc()) - rx->set_agc_on(agc_on); + rx[d_current]->set_agc_on(agc_on); return STATUS_OK; // FIXME } +bool receiver::get_agc_on() +{ + return rx[d_current]->get_agc_on(); +} + /** Set AGC hang. */ receiver::status receiver::set_agc_hang(int hang_ms) { - if (rx->has_agc()) - rx->set_agc_hang(hang_ms); + rx[d_current]->set_agc_hang(hang_ms); return STATUS_OK; // FIXME } +int receiver::get_agc_hang() +{ + return rx[d_current]->get_agc_hang(); +} + /** Set AGC target level. */ receiver::status receiver::set_agc_target_level(int target_level) { - if (rx->has_agc()) - rx->set_agc_target_level(target_level); + rx[d_current]->set_agc_target_level(target_level); return STATUS_OK; // FIXME } +int receiver::get_agc_target_level() +{ + return rx[d_current]->get_agc_target_level(); +} + /** Set fixed gain used when AGC is OFF. */ receiver::status receiver::set_agc_manual_gain(float gain) { - if (rx->has_agc()) - rx->set_agc_manual_gain(gain); + rx[d_current]->set_agc_manual_gain(gain); return STATUS_OK; // FIXME } +float receiver::get_agc_manual_gain() +{ + return rx[d_current]->get_agc_manual_gain(); +} + /** Set maximum gain used when AGC is ON. */ receiver::status receiver::set_agc_max_gain(int gain) { - if (rx->has_agc()) - rx->set_agc_max_gain(gain); + rx[d_current]->set_agc_max_gain(gain); return STATUS_OK; // FIXME } +int receiver::get_agc_max_gain() +{ + return rx[d_current]->get_agc_max_gain(); +} + /** Set AGC attack. */ receiver::status receiver::set_agc_attack(int attack_ms) { - if (rx->has_agc()) - rx->set_agc_attack(attack_ms); + rx[d_current]->set_agc_attack(attack_ms); return STATUS_OK; // FIXME } +int receiver::get_agc_attack() +{ + return rx[d_current]->get_agc_attack(); +} + /** Set AGC decay time. */ receiver::status receiver::set_agc_decay(int decay_ms) { - if (rx->has_agc()) - rx->set_agc_decay(decay_ms); + rx[d_current]->set_agc_decay(decay_ms); + + return STATUS_OK; // FIXME +} + +int receiver::get_agc_decay() +{ + return rx[d_current]->get_agc_decay(); +} + +/** Set AGC panning. */ +receiver::status receiver::set_agc_panning(int panning) +{ + rx[d_current]->set_agc_panning(panning); return STATUS_OK; // FIXME } +int receiver::get_agc_panning() +{ + return rx[d_current]->get_agc_panning(); +} + +/** Set AGC panning auto mode. */ +receiver::status receiver::set_agc_panning_auto(bool mode) +{ + rx[d_current]->set_agc_panning_auto(mode); + if (mode) + rx[d_current]->set_agc_panning(rx[d_current]->get_offset() * 200.0 / d_decim_rate); + + return STATUS_OK; // FIXME +} + +bool receiver::get_agc_panning_auto() +{ + return rx[d_current]->get_agc_panning_auto(); +} + /** Get AGC current gain. */ float receiver::get_agc_gain() { - if (rx->has_agc()) - return rx->get_agc_gain(); - else - return 0; + return rx[d_current]->get_agc_gain(); } /** Set audio mute. */ @@ -881,19 +1106,18 @@ receiver::status receiver::set_mute(bool mute) { if (d_mute == mute) return STATUS_OK; - tb->lock(); - if (mute) + d_mute = mute; + if (d_mute) { - tb->disconnect(rx, 0, audio_snk, 0); - tb->disconnect(rx, 1, audio_snk, 1); + mc0->set_k(0); + mc1->set_k(0); } else { - tb->connect(rx, 0, audio_snk, 0); - tb->connect(rx, 1, audio_snk, 1); + float mul_k = get_rx_count() ? 1.0 / float(get_rx_count()) : 1.0; + mc0->set_k(mul_k); + mc1->set_k(mul_k); } - tb->unlock(); - d_mute = mute; return STATUS_OK; } @@ -903,74 +1127,143 @@ bool receiver::get_mute() return d_mute; } -receiver::status receiver::set_demod(rx_demod demod, bool force) +receiver::status receiver::set_demod_locked(Modulations::idx demod, int old_idx) { status ret = STATUS_OK; - - if (!force && (demod == d_demod)) - return ret; - - // tb->lock() seems to hang occasionally - if (d_running) + rx_chain rxc = RX_CHAIN_NONE; + if (old_idx == -1) { - tb->stop(); - tb->wait(); + background_rx(); + disconnect_rx(); } - tb->disconnect_all(); - switch (demod) { - case RX_DEMOD_OFF: - connect_all(RX_CHAIN_NONE); - break; - - case RX_DEMOD_NONE: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_NONE); - break; - - case RX_DEMOD_AM: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_AM); - break; - - case RX_DEMOD_AMSYNC: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_AMSYNC); - break; - - case RX_DEMOD_NFM: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_FM); - break; - - case RX_DEMOD_WFM_M: - connect_all(RX_CHAIN_WFMRX); - rx->set_demod(wfmrx::WFMRX_DEMOD_MONO); - break; - - case RX_DEMOD_WFM_S: - connect_all(RX_CHAIN_WFMRX); - rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO); + case Modulations::MODE_OFF: + rxc = RX_CHAIN_NONE; break; - case RX_DEMOD_WFM_S_OIRT: - connect_all(RX_CHAIN_WFMRX); - rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO_UKW); + case Modulations::MODE_RAW: + case Modulations::MODE_AM: + case Modulations::MODE_AM_SYNC: + case Modulations::MODE_NFM: + case Modulations::MODE_LSB: + case Modulations::MODE_USB: + case Modulations::MODE_CWL: + case Modulations::MODE_CWU: + rxc = RX_CHAIN_NBRX; break; - case RX_DEMOD_SSB: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_SSB); + case Modulations::MODE_WFM_MONO: + case Modulations::MODE_WFM_STEREO: + case Modulations::MODE_WFM_STEREO_OIRT: + rxc = RX_CHAIN_WFMRX; break; default: ret = STATUS_ERROR; break; } + if (ret != STATUS_ERROR) + { + receiver_base_cf_sptr old_rx = rx[(old_idx == -1) ? d_current : old_idx]; + // RX demod chain + switch (rxc) + { + case RX_CHAIN_NBRX: + case RX_CHAIN_NONE: + if (rx[d_current]->name() != "NBRX") + { + rx[d_current].reset(); + rx[d_current] = make_nbrx(d_decim_rate, d_audio_rate); + rx[d_current]->set_index(d_current); + rx[d_current]->set_rec_event_handler(std::bind(audio_rec_event, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } + break; + + case RX_CHAIN_WFMRX: + if (rx[d_current]->name() != "WFMRX") + { + rx[d_current].reset(); + rx[d_current] = make_wfmrx(d_decim_rate, d_audio_rate); + rx[d_current]->set_index(d_current); + rx[d_current]->set_rec_event_handler(std::bind(audio_rec_event, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } + break; + + default: + break; + } + //Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 + tb->connect(iq_src, 0, rx[d_current], 0); + // End temporary workaronud + if (old_rx.get() != rx[d_current].get()) + { + rx[d_current]->restore_settings(*old_rx.get()); + // Recorders + if (old_rx.get() != rx[d_current].get()) + { + if (old_idx == -1) + if (old_rx->get_audio_recording()) + rx[d_current]->continue_audio_recording(old_rx); + } + if (demod == Modulations::MODE_OFF) + { + if (rx[d_current]->get_audio_recording()) + { + rx[d_current]->stop_audio_recording(); + } + } + } + rx[d_current]->set_demod(demod); + //Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 + tb->disconnect(iq_src, 0, rx[d_current], 0); + // End temporary workaronud + } + connect_rx(); + foreground_rx(); + return ret; +} - d_demod = demod; +receiver::status receiver::set_demod(Modulations::idx demod, int old_idx) +{ + status ret = STATUS_OK; + if (rx[d_current]->get_demod() == demod) + return ret; + if (d_running) + { + tb->stop(); + tb->wait(); + } + ret = set_demod_locked(demod, old_idx); + if (d_running) + tb->start(); + + return ret; +} + +receiver::status receiver::reconnect_all(bool force) +{ + status ret = STATUS_OK; + // tb->lock() seems to hang occasionally + if (d_running) + { + tb->stop(); + tb->wait(); + } + if (force) + { + tb->disconnect_all(); + for (auto& rxc : rx) + rxc->connected(false); + } + connect_all(); if (d_running) tb->start(); @@ -978,74 +1271,117 @@ receiver::status receiver::set_demod(rx_demod demod, bool force) return ret; } + /** * @brief Set maximum deviation of the FM demodulator. * @param maxdev_hz The new maximum deviation in Hz. */ receiver::status receiver::set_fm_maxdev(float maxdev_hz) { - if (rx->has_fm()) - rx->set_fm_maxdev(maxdev_hz); + rx[d_current]->set_fm_maxdev(maxdev_hz); return STATUS_OK; } +float receiver::get_fm_maxdev() +{ + return rx[d_current]->get_fm_maxdev(); +} + receiver::status receiver::set_fm_deemph(double tau) { - if (rx->has_fm()) - rx->set_fm_deemph(tau); + rx[d_current]->set_fm_deemph(tau); return STATUS_OK; } +double receiver::get_fm_deemph() +{ + return rx[d_current]->get_fm_deemph(); +} + receiver::status receiver::set_am_dcr(bool enabled) { - if (rx->has_am()) - rx->set_am_dcr(enabled); + rx[d_current]->set_am_dcr(enabled); return STATUS_OK; } +bool receiver::get_am_dcr() +{ + return rx[d_current]->get_am_dcr(); +} + receiver::status receiver::set_amsync_dcr(bool enabled) { - if (rx->has_amsync()) - rx->set_amsync_dcr(enabled); + rx[d_current]->set_amsync_dcr(enabled); return STATUS_OK; } +bool receiver::get_amsync_dcr() +{ + return rx[d_current]->get_amsync_dcr(); +} + receiver::status receiver::set_amsync_pll_bw(float pll_bw) { - if (rx->has_amsync()) - rx->set_amsync_pll_bw(pll_bw); + rx[d_current]->set_amsync_pll_bw(pll_bw); return STATUS_OK; } +float receiver::get_amsync_pll_bw() +{ + return rx[d_current]->get_amsync_pll_bw(); +} + receiver::status receiver::set_audio_rec_dir(const std::string dir) { - rx->set_rec_dir(dir); + //FIXME is it a global option, that should be set with for-loop? + rx[d_current]->set_audio_rec_dir(dir); return STATUS_OK; } +std::string receiver::get_audio_rec_dir() +{ + //FIXME is it a global option, that should be set with for-loop? + return rx[d_current]->get_audio_rec_dir(); +} + receiver::status receiver::set_audio_rec_sql_triggered(const bool enabled) { - rx->set_audio_rec_sql_triggered(enabled); + rx[d_current]->set_audio_rec_sql_triggered(enabled); return STATUS_OK; } +bool receiver::get_audio_rec_sql_triggered() +{ + return rx[d_current]->get_audio_rec_sql_triggered(); +} + receiver::status receiver::set_audio_rec_min_time(const int time_ms) { - rx->set_audio_rec_min_time(time_ms); + rx[d_current]->set_audio_rec_min_time(time_ms); return STATUS_OK; } +int receiver::get_audio_rec_min_time() +{ + return rx[d_current]->get_audio_rec_min_time(); +} + receiver::status receiver::set_audio_rec_max_gap(const int time_ms) { - rx->set_audio_rec_max_gap(time_ms); + rx[d_current]->set_audio_rec_max_gap(time_ms); return STATUS_OK; } +int receiver::get_audio_rec_max_gap() +{ + return rx[d_current]->get_audio_rec_max_gap(); +} + /** * @brief Start WAV file recorder. * @param filename The filename where to record. @@ -1057,7 +1393,7 @@ receiver::status receiver::set_audio_rec_max_gap(const int time_ms) */ receiver::status receiver::start_audio_recording() { - if (d_recording_wav) + if (is_recording_audio()) { /* error - we are already recording */ std::cout << "ERROR: Can not start audio recorder (already recording)" << std::endl; @@ -1072,10 +1408,8 @@ receiver::status receiver::start_audio_recording() return STATUS_ERROR; } - if(rx->start_audio_recording() == 0) - { + if (rx[d_current]->start_audio_recording() == 0) return STATUS_OK; - } else return STATUS_ERROR; } @@ -1083,7 +1417,7 @@ receiver::status receiver::start_audio_recording() /** Stop WAV file recorder. */ receiver::status receiver::stop_audio_recording() { - if (!d_recording_wav) { + if (!is_recording_audio()){ /* error: we are not recording */ std::cout << "ERROR: Can not stop audio recorder (not recording)" << std::endl; @@ -1096,7 +1430,7 @@ receiver::status receiver::stop_audio_recording() return STATUS_ERROR; } - rx->stop_audio_recording(); + rx[d_current]->stop_audio_recording(); return STATUS_OK; } @@ -1104,7 +1438,7 @@ receiver::status receiver::stop_audio_recording() /** get last recorded audio file name. */ std::string receiver::get_last_audio_filename() { - return rx->get_last_audio_filename(); + return rx[d_current]->get_last_audio_filename(); } /** Start audio playback. */ @@ -1148,21 +1482,18 @@ receiver::status receiver::start_audio_playback(const std::string filename) stop(); /* route demodulator output to null sink */ - if (!d_mute) - { - tb->disconnect(rx, 0, audio_snk, 0); - tb->disconnect(rx, 1, audio_snk, 1); - } - tb->disconnect(rx, 0, audio_fft, 0); - tb->disconnect(rx, 0, audio_udp_sink, 0); - tb->disconnect(rx, 1, audio_udp_sink, 1); - tb->connect(rx, 0, audio_null_sink0, 0); /** FIXME: other channel? */ - tb->connect(rx, 1, audio_null_sink1, 0); /** FIXME: other channel? */ - if (!d_mute) + if (d_active > 0) { - tb->connect(wav_src, 0, audio_snk, 0); - tb->connect(wav_src, 1, audio_snk, 1); - } + tb->disconnect(mc0, 0, audio_snk, 0); + tb->disconnect(mc1, 0, audio_snk, 1); + } + tb->disconnect(rx[d_current], 0, audio_fft, 0); + tb->disconnect(rx[d_current], 0, audio_udp_sink, 0); + tb->disconnect(rx[d_current], 1, audio_udp_sink, 1); + tb->connect(rx[d_current], 0, audio_null_sink0, 0); /** FIXME: other channel? */ + tb->connect(rx[d_current], 1, audio_null_sink1, 0); /** FIXME: other channel? */ + tb->connect(wav_src, 0, audio_snk, 0); + tb->connect(wav_src, 1, audio_snk, 1); tb->connect(wav_src, 0, audio_fft, 0); tb->connect(wav_src, 0, audio_udp_sink, 0); tb->connect(wav_src, 1, audio_udp_sink, 1); @@ -1178,24 +1509,21 @@ receiver::status receiver::stop_audio_playback() { /* disconnect wav source and reconnect receiver */ stop(); - if (!d_mute) - { - tb->disconnect(wav_src, 0, audio_snk, 0); - tb->disconnect(wav_src, 1, audio_snk, 1); - } + tb->disconnect(wav_src, 0, audio_snk, 0); + tb->disconnect(wav_src, 1, audio_snk, 1); tb->disconnect(wav_src, 0, audio_fft, 0); tb->disconnect(wav_src, 0, audio_udp_sink, 0); tb->disconnect(wav_src, 1, audio_udp_sink, 1); - tb->disconnect(rx, 0, audio_null_sink0, 0); - tb->disconnect(rx, 1, audio_null_sink1, 0); - if (!d_mute) + tb->disconnect(rx[d_current], 0, audio_null_sink0, 0); + tb->disconnect(rx[d_current], 1, audio_null_sink1, 0); + if (d_active > 0) { - tb->connect(rx, 0, audio_snk, 0); - tb->connect(rx, 1, audio_snk, 1); + tb->connect(mc0, 0, audio_snk, 0); + tb->connect(mc1, 0, audio_snk, 1); } - tb->connect(rx, 0, audio_fft, 0); /** FIXME: other channel? */ - tb->connect(rx, 0, audio_udp_sink, 0); - tb->connect(rx, 1, audio_udp_sink, 1); + tb->connect(rx[d_current], 0, audio_fft, 0); /** FIXME: other channel? */ + tb->connect(rx[d_current], 0, audio_udp_sink, 0); + tb->connect(rx[d_current], 1, audio_udp_sink, 1); start(); /* delete wav_src since we can not change file name */ @@ -1314,7 +1642,7 @@ receiver::status receiver::start_sniffer(unsigned int samprate, int buffsize) sniffer->set_buffer_size(buffsize); sniffer_rr = make_resampler_ff((float)samprate/(float)d_audio_rate); tb->lock(); - tb->connect(rx, 0, sniffer_rr, 0); + tb->connect(rx[d_current], 0, sniffer_rr, 0); tb->connect(sniffer_rr, 0, sniffer, 0); tb->unlock(); d_sniffer_active = true; @@ -1333,11 +1661,11 @@ receiver::status receiver::stop_sniffer() } tb->lock(); - tb->disconnect(rx, 0, sniffer_rr, 0); + tb->disconnect(rx[d_current], 0, sniffer_rr, 0); // Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 - tb->disconnect(ddc, 0, rx, 0); - tb->connect(ddc, 0, rx, 0); + tb->disconnect(rx[d_current], 0, audio_fft, 0); + tb->connect(rx[d_current], 0, audio_fft, 0); // End temporary workaronud tb->disconnect(sniffer_rr, 0, sniffer, 0); @@ -1357,7 +1685,7 @@ void receiver::get_sniffer_data(float * outbuff, unsigned int &num) } /** Convenience function to connect all blocks. */ -void receiver::connect_all(rx_chain type) +void receiver::connect_all() { gr::basic_block_sptr b; @@ -1388,85 +1716,179 @@ void receiver::connect_all(rx_chain type) // Visualization tb->connect(b, 0, iq_fft, 0); + iq_src = b; + + // Audio path (if there is a receiver) + d_active = 0; + std::cerr<<"connect_all "<get_index()); + foreground_rx(); +} - receiver_base_cf_sptr old_rx = rx; - // RX demod chain - switch (type) +void receiver::connect_rx() +{ + connect_rx(d_current); +} + +void receiver::connect_rx(int n) +{ + if (!rx[n]) + return; + if (rx[n]->connected()) + return; + std::cerr<<"connect_rx "<get_demod()<get_demod() != Modulations::MODE_OFF) { - case RX_CHAIN_NBRX: - if (rx->name() != "NBRX") + if (d_active == 0) { - rx.reset(); - rx = make_nbrx(d_quad_rate, d_audio_rate); - rx->set_rec_event_handler(std::bind(audio_rec_event, this, - std::placeholders::_1, - std::placeholders::_2)); + std::cerr<<"connect_rx d_active == 0"<get_index()<get_demod() != Modulations::MODE_OFF) + { + std::cerr<<"connect_rx loop !MODE_OFF"<connect(iq_src, 0, rxc, 0); + tb->connect(rxc, 0, add0, rxc->get_index()); + tb->connect(rxc, 1, add1, rxc->get_index()); + d_active++; + } + else + { + std::cerr<<"connect_rx loop MODE_OFF"<connect(null_src, 0, add0, rxc->get_index()); + tb->connect(null_src, 0, add1, rxc->get_index()); + } + rxc->connected(true); + } + std::cerr<<"connect_rx connect add"<connect(add0, 0, mc0, 0); + tb->connect(add1, 0, mc1, 0); + std::cerr<<"connect audio_snk "<connect(mc0, 0, audio_snk, 0); + tb->connect(mc1, 0, audio_snk, 1); } - break; - - case RX_CHAIN_WFMRX: - if (rx->name() != "WFMRX") + else { - rx.reset(); - rx = make_wfmrx(d_quad_rate, d_audio_rate); - rx->set_rec_event_handler(std::bind(audio_rec_event, this, - std::placeholders::_1, - std::placeholders::_2)); + std::cerr<<"connect_rx d_active > 0"<connect(iq_src, 0, rx[n], 0); + tb->connect(rx[n], 0, add0, n); + tb->connect(rx[n], 1, add1, n); + rx[n]->connected(true); + d_active++; } - break; - - default: - break; } + else + { + if (d_active > 0) + { + std::cerr<<"connect_rx MODE_OFF d_active > 0"<connect(null_src, 0, add0, n); + tb->connect(null_src, 0, add1, n); + rx[n]->connected(true); + } + } +} - if(old_rx.get() != rx.get()) +void receiver::disconnect_rx() +{ + disconnect_rx(d_current); +} + +void receiver::disconnect_rx(int n) +{ + std::cerr<<"disconnect_rx "<get_demod()<get_demod() != Modulations::MODE_OFF) { - //Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 - tb->connect(ddc, 0, rx, 0); - // End temporary workaronud - rx->set_center_freq(d_rf_freq); - rx->set_offset(d_filter_offset); - rx->set_audio_rec_sql_triggered(old_rx->get_audio_rec_sql_triggered()); - rx->set_audio_rec_min_time(old_rx->get_audio_rec_min_time()); - rx->set_audio_rec_max_gap(old_rx->get_audio_rec_max_gap()); - rx->set_rec_dir(old_rx->get_rec_dir()); - //Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 - tb->disconnect(ddc, 0, rx, 0); - // End temporary workaronud + d_active--; + if (d_active == 0) + { + std::cerr<<"disconnect_rx d_active == 0"<get_index()<connected()) + { + if (rxc->get_demod() != Modulations::MODE_OFF) + { + std::cerr<<"disconnect_rx loop !MODE_OFF"<disconnect(iq_src, 0, rxc, 0); + tb->disconnect(rxc, 0, add0, rxc->get_index()); + tb->disconnect(rxc, 1, add1, rxc->get_index()); + } + else + { + std::cerr<<"disconnect_rx loop MODE_OFF"<disconnect(null_src, 0, add0, rxc->get_index()); + tb->disconnect(null_src, 0, add1, rxc->get_index()); + } + rxc->connected(false); + } + } + std::cerr<<"disconnect_rx disconnect add"<disconnect(add0, 0, mc0, 0); + tb->disconnect(add1, 0, mc1, 0); + std::cerr<<"disconnect audio_snk "<disconnect(mc0, 0, audio_snk, 0); + tb->disconnect(mc1, 0, audio_snk, 1); + } + else + { + std::cerr<<"disconnect_rx d_active > 0"<disconnect(iq_src, 0, rx[n], 0); + tb->disconnect(rx[n], 0, add0, n); + tb->disconnect(rx[n], 1, add1, n); + } } - // Audio path (if there is a receiver) - if (type != RX_CHAIN_NONE) + else { - tb->connect(b, 0, ddc, 0); - tb->connect(ddc, 0, rx, 0); - tb->connect(rx, 0, audio_fft, 0); - tb->connect(rx, 0, audio_udp_sink, 0); - tb->connect(rx, 1, audio_udp_sink, 1); - if (!d_mute) + if (d_active > 0) { - tb->connect(rx, 0, audio_snk, 0); - tb->connect(rx, 1, audio_snk, 1); + std::cerr<<"disconnect_rx MODE_OFF d_active > 0"<disconnect(null_src, 0, add0, n); + tb->disconnect(null_src, 0, add1, n); } - // Recorders and sniffers - if(old_rx.get() != rx.get()) + } + rx[n]->connected(false); +} + +void receiver::background_rx() +{ + std::cerr<<"background_rx "<get_demod()<get_demod() != Modulations::MODE_OFF) + { + tb->disconnect(rx[d_current], 0, audio_fft, 0); + tb->disconnect(rx[d_current], 0, audio_udp_sink, 0); + tb->disconnect(rx[d_current], 1, audio_udp_sink, 1); + if (d_sniffer_active) { - if (d_recording_wav) - rx->continue_audio_recording(old_rx); + tb->disconnect(rx[d_current], 0, sniffer_rr, 0); + tb->disconnect(sniffer_rr, 0, sniffer, 0); } + } +} +void receiver::foreground_rx() +{ + std::cerr<<"foreground_rx "<get_demod()<get_demod() != Modulations::MODE_OFF) + { + tb->connect(rx[d_current], 0, audio_fft, 0); + tb->connect(rx[d_current], 0, audio_udp_sink, 0); + tb->connect(rx[d_current], 1, audio_udp_sink, 1); if (d_sniffer_active) { - tb->connect(rx, 0, sniffer_rr, 0); + tb->connect(rx[d_current], 0, sniffer_rr, 0); tb->connect(sniffer_rr, 0, sniffer, 0); } } else { - if (d_recording_wav) - { - rx->stop_audio_recording(); - d_recording_wav = false; - } if (d_sniffer_active) { @@ -1476,49 +1898,55 @@ void receiver::connect_all(rx_chain type) sniffer_rr.reset(); } } + d_mute = !d_mute; + set_mute(!d_mute); } void receiver::get_rds_data(std::string &outbuff, int &num) { - rx->get_rds_data(outbuff, num); + rx[d_current]->get_rds_data(outbuff, num); } void receiver::start_rds_decoder(void) { + if (is_rds_decoder_active()) + return; if (d_running) { stop(); - rx->start_rds_decoder(); + rx[d_current]->start_rds_decoder(); start(); } else { - rx->start_rds_decoder(); + rx[d_current]->start_rds_decoder(); } } void receiver::stop_rds_decoder(void) { + if (!is_rds_decoder_active()) + return; if (d_running) { stop(); - rx->stop_rds_decoder(); + rx[d_current]->stop_rds_decoder(); start(); } else { - rx->stop_rds_decoder(); + rx[d_current]->stop_rds_decoder(); } } bool receiver::is_rds_decoder_active(void) const { - return rx->is_rds_decoder_active(); + return rx[d_current]->is_rds_decoder_active(); } void receiver::reset_rds_parser(void) { - rx->reset_rds_parser(); + rx[d_current]->reset_rds_parser(); } std::string receiver::escape_filename(std::string filename) @@ -1531,19 +1959,14 @@ std::string receiver::escape_filename(std::string filename) return ss2.str(); } -void receiver::audio_rec_event(receiver * self, std::string filename, bool is_running) +void receiver::audio_rec_event(receiver * self, int idx, std::string filename, bool is_running) { if (is_running) - { - self->d_recording_wav = true; std::cout << "Recording audio to " << filename << std::endl; - } else - { - self->d_recording_wav = false; std::cout << "Audio recorder stopped" << std::endl; - } - if(self->d_audio_rec_event_handler) - self->d_audio_rec_event_handler(filename, is_running); + if (self->d_audio_rec_event_handler) + if (idx == self->d_current) + self->d_audio_rec_event_handler(filename, is_running); } diff --git a/src/applications/gqrx/receiver.h b/src/applications/gqrx/receiver.h index 30d90cd728..68fcc15529 100644 --- a/src/applications/gqrx/receiver.h +++ b/src/applications/gqrx/receiver.h @@ -25,19 +25,23 @@ #if GNURADIO_VERSION < 0x030800 #include +#include #else #include +#include #endif #include #include +//temporary workaround to make add_ff happy +#include +//emd workaround #include #include #include #include #include "dsp/correct_iq_cc.h" -#include "dsp/downconverter.h" #include "dsp/filter/fir_decim.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" @@ -82,19 +86,6 @@ class receiver STATUS_ERROR = 1 /*!< There was an error. */ }; - /** Available demodulators. */ - enum rx_demod { - RX_DEMOD_OFF = 0, /*!< No receiver. */ - RX_DEMOD_NONE = 1, /*!< No demod. Raw I/Q to audio. */ - RX_DEMOD_AM = 2, /*!< Amplitude modulation. */ - RX_DEMOD_NFM = 3, /*!< Frequency modulation. */ - RX_DEMOD_WFM_M = 4, /*!< Frequency modulation (wide, mono). */ - RX_DEMOD_WFM_S = 5, /*!< Frequency modulation (wide, stereo). */ - RX_DEMOD_WFM_S_OIRT = 6, /*!< Frequency modulation (wide, stereo oirt). */ - RX_DEMOD_SSB = 7, /*!< Single Side Band. */ - RX_DEMOD_AMSYNC = 8 /*!< Amplitude modulation (synchronous demod). */ - }; - /** Supported receiver types. */ enum rx_chain { RX_CHAIN_NONE = 0, /*!< No receiver, just spectrum analyzer. */ @@ -103,11 +94,7 @@ class receiver }; /** Filter shape (convenience wrappers for "transition width"). */ - enum filter_shape { - FILTER_SHAPE_SOFT = 0, /*!< Soft: Transition band is TBD of width. */ - FILTER_SHAPE_NORMAL = 1, /*!< Normal: Transition band is TBD of width. */ - FILTER_SHAPE_SHARP = 2 /*!< Sharp: Transition band is TBD of width. */ - }; + typedef Modulations::filter_shape filter_shape; typedef std::function audio_rec_event_handler_t; @@ -157,11 +144,27 @@ class receiver status set_gain(std::string name, double value); double get_gain(std::string name) const; + int add_rx(); + int get_rx_count(); + int delete_rx(); + status select_rx(int no); + status fake_select_rx(int no); + int get_current(); + vfo::sptr get_current_vfo(); + vfo::sptr get_vfo(int n); + vfo::sptr find_vfo(int64_t freq); + std::vector get_vfos(); + status set_filter_offset(double offset_hz); + status set_filter_offset(int rx_index, double offset_hz); double get_filter_offset(void) const; + void set_freq_lock(bool on, bool all = false); + bool get_freq_lock(); + status set_cw_offset(double offset_hz); double get_cw_offset(void) const; - status set_filter(double low, double high, filter_shape shape); + status set_filter(int low, int high, filter_shape shape); + status get_filter(int &low, int &high, filter_shape &shape); status set_freq_corr(double ppm); float get_signal_pwr() const; void set_iq_fft_size(int newsize); @@ -173,43 +176,73 @@ class receiver /* Noise blanker */ status set_nb_on(int nbid, bool on); + bool get_nb_on(int nbid); status set_nb_threshold(int nbid, float threshold); + float get_nb_threshold(int nbid); /* Squelch parameter */ status set_sql_level(double level_db); + status set_sql_level(double level_offset, bool global, bool relative); + double get_sql_level(); status set_sql_alpha(double alpha); + double get_sql_alpha(); /* AGC */ status set_agc_on(bool agc_on); + bool get_agc_on(); status set_agc_target_level(int target_level); + int get_agc_target_level(); status set_agc_manual_gain(float gain); + float get_agc_manual_gain(); status set_agc_max_gain(int gain); + int get_agc_max_gain(); status set_agc_attack(int attack_ms); + int get_agc_attack(); status set_agc_decay(int decay_ms); + int get_agc_decay(); status set_agc_hang(int hang_ms); + int get_agc_hang(); + status set_agc_panning(int panning); + int get_agc_panning(); + status set_agc_panning_auto(bool mode); + bool get_agc_panning_auto(); float get_agc_gain(); + /* Mute */ status set_mute(bool mute); bool get_mute(); - status set_demod(rx_demod demod, bool force=false); + /* Demod */ + status set_demod_locked(Modulations::idx demod, int old_idx = -1); + status set_demod(Modulations::idx demod, int old_idx = -1); + Modulations::idx get_demod() {return rx[d_current]->get_demod();} + status reconnect_all(bool force = false); /* FM parameters */ status set_fm_maxdev(float maxdev_hz); + float get_fm_maxdev(); status set_fm_deemph(double tau); + double get_fm_deemph(); /* AM parameters */ status set_am_dcr(bool enabled); + bool get_am_dcr(); /* AM-Sync parameters */ status set_amsync_dcr(bool enabled); + bool get_amsync_dcr(); status set_amsync_pll_bw(float pll_bw); + float get_amsync_pll_bw(); /* Audio parameters */ status set_audio_rec_dir(const std::string dir); + std::string get_audio_rec_dir(); status set_audio_rec_sql_triggered(const bool enabled); + bool get_audio_rec_sql_triggered(); status set_audio_rec_min_time(const int time_ms); + int get_audio_rec_min_time(); status set_audio_rec_max_gap(const int time_ms); + int get_audio_rec_max_gap(); status start_audio_recording(); status stop_audio_recording(); std::string get_last_audio_filename(); @@ -223,13 +256,14 @@ class receiver status start_iq_recording(const std::string filename); status stop_iq_recording(); status seek_iq_file(long pos); + bool is_playing_iq() const { return input_devstr.find("file=") != std::string::npos; } /* sample sniffer */ status start_sniffer(unsigned int samplrate, int buffsize); status stop_sniffer(); void get_sniffer_data(float * outbuff, unsigned int &num); - bool is_recording_audio(void) const { return d_recording_wav; } + bool is_recording_audio(void) const { return rx[d_current]->get_audio_recording(); } bool is_snifffer_active(void) const { return d_sniffer_active; } /* rds functions */ @@ -247,37 +281,46 @@ class receiver } private: - void connect_all(rx_chain type); + void connect_all(); + void connect_rx(); + void connect_rx(int n); + void disconnect_rx(); + void disconnect_rx(int n); + void foreground_rx(); + void background_rx(); + status connect_iq_recorder(); private: + int d_current; /*!< Current selected demodulator. */ + int d_active; /*!< Active demodulator count. */ bool d_running; /*!< Whether receiver is running or not. */ double d_input_rate; /*!< Input sample rate. */ double d_decim_rate; /*!< Rate after decimation (input_rate / decim) */ - double d_quad_rate; /*!< Quadrature rate (after down-conversion) */ double d_audio_rate; /*!< Audio output rate. */ unsigned int d_decim; /*!< input decimation. */ - unsigned int d_ddc_decim; /*!< Down-conversion decimation. */ double d_rf_freq; /*!< Current RF frequency. */ - double d_filter_offset; /*!< Current filter offset */ - double d_cw_offset; /*!< CW offset */ bool d_recording_iq; /*!< Whether we are recording I/Q file. */ - bool d_recording_wav; /*!< Whether we are recording WAV file. */ bool d_sniffer_active; /*!< Only one data decoder allowed. */ bool d_iq_rev; /*!< Whether I/Q is reversed or not. */ bool d_dc_cancel; /*!< Enable automatic DC removal. */ bool d_iq_balance; /*!< Enable automatic IQ balance. */ bool d_mute; /*!< Enable audio mute. */ - std::string input_devstr; /*!< Current input device string. */ - std::string output_devstr; /*!< Current output device string. */ + std::string input_devstr; /*!< Current input device string. */ + std::string output_devstr; /*!< Current output device string. */ - rx_demod d_demod; /*!< Current demodulator. */ + gr::basic_block_sptr iq_src; /*!< Points to the block, connected to rx[]. */ gr::top_block_sptr tb; /*!< The GNU Radio top block. */ osmosdr::source::sptr src; /*!< Real time I/Q source. */ fir_decim_cc_sptr input_decim; /*!< Input decimator. */ - receiver_base_cf_sptr rx; /*!< receiver. */ + std::vector rx; /*!< receiver. */ + gr::blocks::add_ff::sptr add0; /* Audio downmix */ + gr::blocks::add_ff::sptr add1; /* Audio downmix */ + gr::blocks::multiply_const_ff::sptr mc0; /* Audio downmix */ + gr::blocks::multiply_const_ff::sptr mc1; /* Audio downmix */ + gr::blocks::null_source::sptr null_src; /* temporary workaround */ dc_corr_cc_sptr dc_corr; /*!< DC corrector block. */ iq_swap_cc_sptr iq_swap; /*!< I/Q swapping block. */ @@ -285,8 +328,6 @@ class receiver rx_fft_c_sptr iq_fft; /*!< Baseband FFT block. */ rx_fft_f_sptr audio_fft; /*!< Audio FFT block. */ - downconverter_cc_sptr ddc; /*!< Digital down-converter for demod chain. */ - gr::blocks::file_sink::sptr iq_sink; /*!< I/Q file sink. */ gr::blocks::wavfile_source::sptr wav_src; /*!< WAV file source for playback. */ gr::blocks::null_sink::sptr audio_null_sink0; /*!< Audio null sink used during playback. */ @@ -306,7 +347,7 @@ class receiver audio_rec_event_handler_t d_audio_rec_event_handler; //! Get a path to a file containing random bytes static std::string get_zero_file(void); - static void audio_rec_event(receiver * self, std::string filename, + static void audio_rec_event(receiver * self, int idx, std::string filename, bool is_running); }; diff --git a/src/applications/gqrx/remote_control.cpp b/src/applications/gqrx/remote_control.cpp index 167c6608f8..e5b473049b 100644 --- a/src/applications/gqrx/remote_control.cpp +++ b/src/applications/gqrx/remote_control.cpp @@ -39,7 +39,7 @@ RemoteControl::RemoteControl(QObject *parent) : rc_filter_offset = 0; bw_half = 740e3; rc_lnb_lo_mhz = 0.0; - rc_mode = 0; + rc_mode = Modulations::MODE_OFF; rc_passband_lo = 0; rc_passband_hi = 0; rc_program_id = "0000"; @@ -302,11 +302,11 @@ void RemoteControl::setSignalLevel(float level) } /*! \brief Set demodulator (from mainwindow). */ -void RemoteControl::setMode(int mode) +void RemoteControl::setMode(Modulations::idx mode) { rc_mode = mode; - if (rc_mode == 0) + if (rc_mode == Modulations::MODE_OFF) audio_recorder_status = false; } @@ -354,7 +354,7 @@ void RemoteControl::setSquelchLevel(double level) /*! \brief Start audio recorder (from mainwindow). */ void RemoteControl::startAudioRecorder() { - if (rc_mode > 0) + if (rc_mode > Modulations::MODE_OFF) audio_recorder_status = true; } @@ -419,69 +419,69 @@ int RemoteControl::modeStrToInt(QString mode_str) if (mode_str.compare("OFF", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_OFF; + mode_int = Modulations::MODE_OFF; } else if (mode_str.compare("RAW", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_RAW; + mode_int = Modulations::MODE_RAW; } else if (mode_str.compare("AM", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_AM; + mode_int = Modulations::MODE_AM; } else if (mode_str.compare("AMS", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_AM_SYNC; + mode_int = Modulations::MODE_AM_SYNC; } else if (mode_str.compare("LSB", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_LSB; + mode_int = Modulations::MODE_LSB; } else if (mode_str.compare("USB", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_USB; + mode_int = Modulations::MODE_USB; } else if (mode_str.compare("CWL", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_CWL; + mode_int = Modulations::MODE_CWL; hamlib_compatible = false; } else if (mode_str.compare("CWR", Qt::CaseInsensitive) == 0) // "CWR" : "CWL" { - mode_int = DockRxOpt::MODE_CWL; + mode_int = Modulations::MODE_CWL; hamlib_compatible = true; } else if (mode_str.compare("CWU", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_CWU; + mode_int = Modulations::MODE_CWU; hamlib_compatible = false; } else if (mode_str.compare("CW", Qt::CaseInsensitive) == 0) // "CW" : "CWU" { - mode_int = DockRxOpt::MODE_CWU; + mode_int = Modulations::MODE_CWU; hamlib_compatible = true; } else if (mode_str.compare("FM", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_NFM; + mode_int = Modulations::MODE_NFM; } else if (mode_str.compare("WFM", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_WFM_MONO; + mode_int = Modulations::MODE_WFM_MONO; } else if (mode_str.compare("WFM_ST", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_WFM_STEREO; + mode_int = Modulations::MODE_WFM_STEREO; } else if (mode_str.compare("WFM_ST_OIRT", Qt::CaseInsensitive) == 0) { - mode_int = DockRxOpt::MODE_WFM_STEREO_OIRT; + mode_int = Modulations::MODE_WFM_STEREO_OIRT; } return mode_int; } /*! \brief Convert mode enum to string. - * \param mode The mode ID c.f. DockRxOpt::rxopt_mode_idx + * \param mode The mode ID c.f. Modulations::rxopt_mode_idx * \returns The mode string. */ QString RemoteControl::intToModeStr(int mode) @@ -490,51 +490,51 @@ QString RemoteControl::intToModeStr(int mode) switch (mode) { - case DockRxOpt::MODE_OFF: + case Modulations::MODE_OFF: mode_str = "OFF"; break; - case DockRxOpt::MODE_RAW: + case Modulations::MODE_RAW: mode_str = "RAW"; break; - case DockRxOpt::MODE_AM: + case Modulations::MODE_AM: mode_str = "AM"; break; - case DockRxOpt::MODE_AM_SYNC: + case Modulations::MODE_AM_SYNC: mode_str = "AMS"; break; - case DockRxOpt::MODE_LSB: + case Modulations::MODE_LSB: mode_str = "LSB"; break; - case DockRxOpt::MODE_USB: + case Modulations::MODE_USB: mode_str = "USB"; break; - case DockRxOpt::MODE_CWL: + case Modulations::MODE_CWL: mode_str = (hamlib_compatible) ? "CWR" : "CWL"; break; - case DockRxOpt::MODE_CWU: + case Modulations::MODE_CWU: mode_str = (hamlib_compatible) ? "CW" : "CWU"; break; - case DockRxOpt::MODE_NFM: + case Modulations::MODE_NFM: mode_str = "FM"; break; - case DockRxOpt::MODE_WFM_MONO: + case Modulations::MODE_WFM_MONO: mode_str = "WFM"; break; - case DockRxOpt::MODE_WFM_STEREO: + case Modulations::MODE_WFM_STEREO: mode_str = "WFM_ST"; break; - case DockRxOpt::MODE_WFM_STEREO_OIRT: + case Modulations::MODE_WFM_STEREO_OIRT: mode_str = "WFM_ST_OIRT"; break; @@ -593,7 +593,7 @@ QString RemoteControl::cmd_set_mode(QStringList cmdlist) } else { - rc_mode = mode; + rc_mode = Modulations::idx(mode); emit newMode(rc_mode); int passband = cmdlist.value(2, "0").toInt(); @@ -732,7 +732,7 @@ QString RemoteControl::cmd_set_func(QStringList cmdlist) } else if ((func.compare("RECORD", Qt::CaseInsensitive) == 0) && ok) { - if (rc_mode == 0 || !receiver_running) + if (rc_mode == Modulations::MODE_OFF || !receiver_running) { answer = QString("RPRT 1\n"); } @@ -831,7 +831,7 @@ QString RemoteControl::cmd_get_info() const /* Gpredict / Gqrx specific command: AOS - satellite AOS event */ QString RemoteControl::cmd_AOS() { - if (rc_mode > 0 && receiver_running) + if (rc_mode > Modulations::MODE_OFF && receiver_running) { emit startAudioRecorderEvent(); audio_recorder_status = true; diff --git a/src/applications/gqrx/remote_control.h b/src/applications/gqrx/remote_control.h index 3d29527f8d..bc0ce2e19d 100644 --- a/src/applications/gqrx/remote_control.h +++ b/src/applications/gqrx/remote_control.h @@ -30,6 +30,8 @@ #include #include #include +#include "receivers/defines.h" +#include "receivers/modulations.h" /* For gain_t and gain_list_t */ #include "qtgui/dockinputctl.h" @@ -89,7 +91,7 @@ public slots: void setLnbLo(double freq_mhz); void setBandwidth(qint64 bw); void setSignalLevel(float level); - void setMode(int mode); + void setMode(Modulations::idx mode); void setPassband(int passband_lo, int passband_hi); void setSquelchLevel(double level); void startAudioRecorder(); @@ -102,7 +104,7 @@ public slots: void newFrequency(qint64 freq); void newFilterOffset(qint64 offset); void newLnbLo(double freq_mhz); - void newMode(int mode); + void newMode(Modulations::idx mode); void newPassband(int passband); void newSquelchLevel(double level); void startAudioRecorderEvent(); @@ -127,7 +129,7 @@ private slots: qint64 bw_half; double rc_lnb_lo_mhz; /*!< Current LNB LO freq in MHz */ - int rc_mode; /*!< Current mode. */ + Modulations::idx rc_mode; /*!< Current mode. */ int rc_passband_lo; /*!< Current low cutoff. */ int rc_passband_hi; /*!< Current high cutoff. */ bool rds_status; /*!< RDS decoder enabled */ diff --git a/src/dsp/rx_agc_xx.cpp b/src/dsp/rx_agc_xx.cpp index 1153a4a764..349e0fc5b1 100644 --- a/src/dsp/rx_agc_xx.cpp +++ b/src/dsp/rx_agc_xx.cpp @@ -34,13 +34,18 @@ #define MIN_GAIN_DB (-20.0f) #define MIN_GAIN exp10f(MIN_GAIN_DB) #define MAX_SAMPLE_RATE 96000 +#define PANNING_DELAY_K 4000.0 +#define PANNING_GAIN_K 100.0 +//TODO Make this user-configurable as extra peak history time +#define AGC_AVG_BUF_SCALE 2 rx_agc_2f_sptr make_rx_agc_2f(double sample_rate, bool agc_on, int target_level, - int manual_gain, int max_gain, int attack, int decay, int hang) + int manual_gain, int max_gain, int attack, + int decay, int hang, int panning) { return gnuradio::get_initial_sptr(new rx_agc_2f(sample_rate, agc_on, target_level, manual_gain, max_gain, attack, decay, - hang)); + hang, panning)); } /** @@ -49,7 +54,8 @@ rx_agc_2f_sptr make_rx_agc_2f(double sample_rate, bool agc_on, int target_level, * Use make_rx_agc_2f() instead. */ rx_agc_2f::rx_agc_2f(double sample_rate, bool agc_on, int target_level, - int manual_gain, int max_gain, int attack, int decay, int hang) + int manual_gain, int max_gain, int attack, + int decay, int hang, int panning) : gr::sync_block ("rx_agc_2f", gr::io_signature::make(2, 2, sizeof(float)), gr::io_signature::make(2, 2, sizeof(float))), @@ -61,6 +67,7 @@ rx_agc_2f::rx_agc_2f(double sample_rate, bool agc_on, int target_level, d_attack(attack), d_decay(decay), d_hang(hang), + d_panning(panning), d_target_mag(1), d_hang_samp(0), d_buf_samples(0), @@ -74,12 +81,16 @@ rx_agc_2f::rx_agc_2f(double sample_rate, bool agc_on, int target_level, d_decay_step(1.01), d_attack_step(0.99), d_floor(0.0001), + d_gain_l(1.0), + d_gain_r(1.0), + d_delay_l(0), + d_delay_r(0), d_refill(false), d_running(false) { - set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, true); - set_history(MAX_SAMPLE_RATE + 1); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, d_panning, true); + set_history(MAX_SAMPLE_RATE + 1 + MAX_SAMPLE_RATE * 100 / PANNING_DELAY_K); } rx_agc_2f::~rx_agc_2f() @@ -148,15 +159,15 @@ int rx_agc_2f::work(int noutput_items, float sample_in0 = in0[k_hist]; float sample_in1 = in1[k_hist]; mag_in = std::max(fabs(sample_in0),fabs(sample_in1)); - float sample_out0 = in0[k_hist - d_buf_samples]; - float sample_out1 = in1[k_hist - d_buf_samples]; + float sample_out0 = in0[k_hist - d_buf_samples - d_delay_l]; + float sample_out1 = in1[k_hist - d_buf_samples - d_delay_r]; d_mag_buf[d_buf_p] = mag_in; update_buffer(d_buf_p); max_out = get_peak(); int buf_p_next = d_buf_p + 1; - if (buf_p_next >= d_buf_samples) + if (buf_p_next >= d_buf_size) buf_p_next = 0; if (max_out > d_floor) @@ -197,16 +208,17 @@ int rx_agc_2f::work(int noutput_items, d_hang_counter--; if (d_current_gain < MIN_GAIN) d_current_gain = MIN_GAIN; - out0[k] = sample_out0 * d_current_gain; - out1[k] = sample_out1 * d_current_gain; + out0[k] = sample_out0 * d_current_gain * d_gain_l; + out1[k] = sample_out1 * d_current_gain * d_gain_r; d_buf_p = buf_p_next; } } else{ - volk_32f_s32f_multiply_32f((float *)out0, (float *)&in0[history() - 1], d_current_gain, noutput_items); - volk_32f_s32f_multiply_32f((float *)out1, (float *)&in1[history() - 1], d_current_gain, noutput_items); + volk_32f_s32f_multiply_32f((float *)out0, (float *)&in0[history() - 1 - d_delay_l], d_current_gain * d_gain_l, noutput_items); + volk_32f_s32f_multiply_32f((float *)out1, (float *)&in1[history() - 1 - d_delay_r], d_current_gain * d_gain_r, noutput_items); } #ifdef AGC_DEBUG2 + static TYPEFLOAT d_prev_dbg = 0.0; if(d_prev_dbg != d_target_gain) { std::cerr<<"------ d_target_gain="< guard(d_mutex); - d_squelch_triggered = enabled; - d_prev_action = ACT_NONE; + if (d_squelch_triggered == enabled) + return; + { + std::unique_lock guard(d_mutex); + d_squelch_triggered = enabled; + d_prev_action = ACT_NONE; + } } void wavfile_sink_gqrx::set_bits_per_sample(int bits_per_sample) diff --git a/src/interfaces/wav_sink.h b/src/interfaces/wav_sink.h index ff8da03a29..a9b5d5151d 100644 --- a/src/interfaces/wav_sink.h +++ b/src/interfaces/wav_sink.h @@ -183,6 +183,7 @@ class wavfile_sink_gqrx : virtual public gr::sync_block int get_rec_max_gap() { return d_max_gap_ms; } int get_min_time(); int get_max_gap(); + bool is_active() { return !! d_fp; } private: bool open_unlocked(const char* filename); int open_new_unlocked(); diff --git a/src/qtgui/agc_options.cpp b/src/qtgui/agc_options.cpp index 40ff5052d5..57a3f10dec 100644 --- a/src/qtgui/agc_options.cpp +++ b/src/qtgui/agc_options.cpp @@ -230,6 +230,33 @@ void CAgcOptions::enableHang(bool enabled) ui->hangTitle->setEnabled(enabled); } +/*! \brief Get panning fixed position. */ +int CAgcOptions::panning() +{ + return ui->panningSlider->value(); +} + +/*! \brief Set panning fixed position. */ +void CAgcOptions::setPanning(int value) +{ + if(value < -100) + return; + if(value > 100) + return; + ui->panningSlider->setValue(value); +} + +/*! \brief Get panning auto mode. */ +bool CAgcOptions::panningAuto() +{ + return (Qt::Checked == ui->panningAutoCheckBox->checkState()); +} + +/*! \brief Set panning auto mode. */ +void CAgcOptions::setPanningAuto(bool value) +{ + ui->panningAutoCheckBox->setCheckState(value ? Qt::Checked : Qt::Unchecked); +} /*! \brief AGC gain slider value has changed. */ @@ -273,4 +300,18 @@ void CAgcOptions::on_hangSlider_valueChanged(int value) ui->hangLabel->setText(QString("%1 ms").arg(ui->hangSlider->value())); emit hangChanged(value); } +/*! \brief Panning slider value has changed. */ +void CAgcOptions::on_panningSlider_valueChanged(int value) +{ + ui->panningLabel->setText(QString::number(value)); + emit panningChanged(value); +} +/*! \brief Panning auto checkbox state changed. */ +void CAgcOptions::on_panningAutoCheckBox_stateChanged(int state) +{ + ui->panningSlider->setEnabled(!(state == Qt::Checked)); + ui->panningTitle->setEnabled(!(state == Qt::Checked)); + ui->panningLabel->setEnabled(!(state == Qt::Checked)); + emit panningAutoChanged(state == Qt::Checked); +} diff --git a/src/qtgui/agc_options.h b/src/qtgui/agc_options.h index f989b81279..b264470354 100644 --- a/src/qtgui/agc_options.h +++ b/src/qtgui/agc_options.h @@ -74,6 +74,10 @@ class CAgcOptions : public QDialog void setHang(int value); void enableHang(bool enabled); + int panning(); + void setPanning(int value); + bool panningAuto(); + void setPanningAuto(bool value); enum agc_preset_e { @@ -93,6 +97,8 @@ class CAgcOptions : public QDialog void attackChanged(int decay); void decayChanged(int decay); void hangChanged(int hang); + void panningChanged(int panning); + void panningAutoChanged(bool panningAuto); private slots: void on_gainSlider_valueChanged(int value); @@ -101,6 +107,8 @@ private slots: void on_attackSlider_valueChanged(int value); void on_decaySlider_valueChanged(int value); void on_hangSlider_valueChanged(int value); + void on_panningSlider_valueChanged(int value); + void on_panningAutoCheckBox_stateChanged(int state); private: Ui::CAgcOptions *ui; diff --git a/src/qtgui/agc_options.ui b/src/qtgui/agc_options.ui index ca8a787e8d..6d185922b9 100644 --- a/src/qtgui/agc_options.ui +++ b/src/qtgui/agc_options.ui @@ -7,7 +7,7 @@ 0 0 293 - 253 + 299 @@ -33,7 +33,7 @@ 5 - + Target @@ -43,16 +43,6 @@ - - - - 0 dB - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - @@ -174,28 +164,6 @@ - - - - Qt::StrongFocus - - - Target output level. - - - -100 - - - 0 - - - 0 - - - Qt::Horizontal - - - @@ -344,6 +312,105 @@ + + + + 0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Enable auto stereo panning mode depending on offset frequency. + + + Auto panning + + + + + + + Qt::StrongFocus + + + Target output level. + + + -100 + + + 0 + + + 0 + + + Qt::Horizontal + + + + + + + true + + + Qt::StrongFocus + + + AGC stereo panning position. + + + -100 + + + 100 + + + 10 + + + 50 + + + 0 + + + 0 + + + Qt::Horizontal + + + + + + + Panning + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -364,7 +431,6 @@ gainSlider - targetLevelSlider decaySlider diff --git a/src/qtgui/audio_options.cpp b/src/qtgui/audio_options.cpp index f4b2319b82..e0e8db029f 100644 --- a/src/qtgui/audio_options.cpp +++ b/src/qtgui/audio_options.cpp @@ -280,3 +280,8 @@ void CAudioOptions::on_udpStereo_stateChanged(int state) { emit newUdpStereo(state); } + +void CAudioOptions::on_toAllVFOsButton_clicked() +{ + emit copyRecSettingsToAllVFOs(); +} diff --git a/src/qtgui/audio_options.h b/src/qtgui/audio_options.h index 6b5624e31b..5f6eca9d65 100644 --- a/src/qtgui/audio_options.h +++ b/src/qtgui/audio_options.h @@ -80,6 +80,7 @@ public slots: void newSquelchTriggered(bool enabled); void newRecMinTime(int time_ms); void newRecMaxGap(int time_ms); + void copyRecSettingsToAllVFOs(); private slots: void on_fftSplitSlider_valueChanged(int value); @@ -94,6 +95,7 @@ private slots: void on_squelchTriggered_stateChanged(int state); void on_recMinTime_valueChanged(int value); void on_recMaxGap_valueChanged(int value); + void on_toAllVFOsButton_clicked(); private: Ui::CAudioOptions *ui; /*!< The user interface widget. */ diff --git a/src/qtgui/audio_options.ui b/src/qtgui/audio_options.ui index 2a9fd32f24..f495d93ef3 100644 --- a/src/qtgui/audio_options.ui +++ b/src/qtgui/audio_options.ui @@ -7,7 +7,7 @@ 0 0 315 - 209 + 242 @@ -167,6 +167,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -275,6 +288,23 @@ + + + + Copy to + + + + + + + Copy location, min time, max gap settings to all VFOs + + + All VFOs + + + diff --git a/src/qtgui/bookmarks.cpp b/src/qtgui/bookmarks.cpp index c57ba74421..2bbe6c6368 100644 --- a/src/qtgui/bookmarks.cpp +++ b/src/qtgui/bookmarks.cpp @@ -29,6 +29,7 @@ #include #include #include "bookmarks.h" +#include "qtgui/bookmarkstablemodel.h" const QColor TagInfo::DefaultColor(Qt::lightGray); const QString TagInfo::strUntagged("Untagged"); @@ -61,14 +62,18 @@ void Bookmarks::add(BookmarkInfo &info) m_BookmarkList.append(info); std::stable_sort(m_BookmarkList.begin(),m_BookmarkList.end()); save(); - emit( BookmarksChanged() ); } void Bookmarks::remove(int index) { m_BookmarkList.removeAt(index); save(); - emit BookmarksChanged(); +} + +void Bookmarks::remove(const BookmarkInfo &info) +{ + m_BookmarkList.removeOne(info); + save(); } bool Bookmarks::load() @@ -121,7 +126,21 @@ bool Bookmarks::load() info.frequency = strings[0].toLongLong(); info.name = strings[1].trimmed(); info.modulation = strings[2].trimmed(); - info.bandwidth = strings[3].toInt(); + info.set_demod(Modulations::GetEnumForModulationString(info.modulation)); + int bandwidth = strings[3].toInt(); + switch(info.get_demod()) + { + case Modulations::MODE_LSB: + case Modulations::MODE_CWL: + info.set_filter(-100 - bandwidth, -100, bandwidth * 0.2); + break; + case Modulations::MODE_USB: + case Modulations::MODE_CWU: + info.set_filter(100, 100 + bandwidth, bandwidth * 0.2); + break; + default: + info.set_filter(-bandwidth / 2, bandwidth / 2, bandwidth * 0.2); + } // Multiple Tags may be separated by comma. QString strTags = strings[4]; QStringList TagList = strTags.split(","); @@ -132,6 +151,51 @@ bool Bookmarks::load() m_BookmarkList.append(info); } + else if (strings.count() == BookmarksTableModel::COLUMN_COUNT) + { + BookmarkInfo info; + int i = 0; + info.frequency = strings[i++].toLongLong(); + info.name = strings[i++].trimmed(); + // Multiple Tags may be separated by comma. + QString strTags = strings[i++]; + QStringList TagList = strTags.split(","); + + for (int iTag = 0; iTag < TagList.size(); ++iTag) + info.tags.append(&findOrAddTag(TagList[iTag].trimmed())); + + info.set_freq_lock(strings[i++].trimmed() == "true"); + info.modulation = strings[i++].trimmed(); + info.set_demod(Modulations::GetEnumForModulationString(info.modulation)); + info.set_filter_low(strings[i++].toInt()); + info.set_filter_high(strings[i++].toInt()); + info.set_filter_tw(strings[i++].toInt()); + info.set_agc_on(strings[i++].trimmed() == "true"); + info.set_agc_target_level(strings[i++].toInt()); + info.set_agc_manual_gain(strings[i++].toFloat()); + info.set_agc_max_gain(strings[i++].toInt()); + info.set_agc_attack(strings[i++].toInt()); + info.set_agc_decay(strings[i++].toInt()); + info.set_agc_hang(strings[i++].toInt()); + info.set_agc_panning(strings[i++].toInt()); + info.set_agc_panning_auto(strings[i++].trimmed() == "true"); + info.set_cw_offset(strings[i++].toInt()); + info.set_fm_maxdev(strings[i++].toFloat()); + info.set_fm_deemph(1.0e-6 * strings[i++].toFloat()); + info.set_am_dcr(strings[i++].trimmed() == "true"); + info.set_amsync_dcr(strings[i++].trimmed() == "true"); + info.set_amsync_pll_bw(strings[i++].toFloat()); + info.set_nb_on(1, strings[i++].trimmed() == "true"); + info.set_nb_threshold(1, strings[i++].toFloat()); + info.set_nb_on(2, strings[i++].trimmed() == "true"); + info.set_nb_threshold(2, strings[i++].toFloat()); + info.set_audio_rec_dir(strings[i++].trimmed().toStdString()); + info.set_audio_rec_sql_triggered(strings[i++].trimmed() == "true"); + info.set_audio_rec_min_time(strings[i++].toInt()); + info.set_audio_rec_max_gap(strings[i++].toInt()); + + m_BookmarkList.append(info); + } else { std::cout << "Bookmarks: Ignoring Line:" << std::endl; @@ -178,41 +242,93 @@ bool Bookmarks::save() stream << '\n'; stream << QString("# Frequency").leftJustified(12) + "; " + - QString("Name").leftJustified(25)+ "; " + + QString("Name").leftJustified(25) + "; " + + QString("Tags").leftJustified(25) + "; " + + QString("Autostart").rightJustified(10) + "; " + QString("Modulation").leftJustified(20) + "; " + - QString("Bandwidth").rightJustified(10) + "; " + - QString("Tags") << '\n'; - + QString("Filter Low").rightJustified(16) + "; " + + QString("Filter High").rightJustified(16) + "; " + + QString("Filter TW").rightJustified(16) + "; " + + QString("AGC On").rightJustified(10) + "; " + + QString("AGC target level").rightJustified(16) + "; " + + QString("AGC manual gain").rightJustified(16) + "; " + + QString("AGC max gain").rightJustified(16) + "; " + + QString("AGC attack").rightJustified(16) + "; " + + QString("AGC decay").rightJustified(16) + "; " + + QString("AGC hang").rightJustified(16) + "; " + + QString("Panning").rightJustified(16) + "; " + + QString("Auto panning").rightJustified(16) + "; " + + QString("CW offset").rightJustified(16) + "; " + + QString("FM max deviation").rightJustified(16) + "; " + + QString("FM deemphasis").rightJustified(16) + "; " + + QString("AM DCR").rightJustified(16) + "; " + + QString("AM SYNC DCR").rightJustified(16) + "; " + + QString("AM SYNC PLL BW").rightJustified(16) + "; " + + QString("NB1 ON").rightJustified(16) + "; " + + QString("NB1 threshold").rightJustified(16) + "; " + + QString("NB2 ON").rightJustified(16) + "; " + + QString("NB2 threshold").rightJustified(16) + "; " + + QString("REC DIR").rightJustified(16) + "; " + + QString("REC SQL trig").rightJustified(16) + "; " + + QString("REC Min time").rightJustified(16) + "; " + + QString("REC Max gap").rightJustified(16) + << "\n"; for (int i = 0; i < m_BookmarkList.size(); i++) { BookmarkInfo& info = m_BookmarkList[i]; - QString line = QString::number(info.frequency).rightJustified(12) + - "; " + info.name.leftJustified(25) + "; " + - info.modulation.leftJustified(20)+ "; " + - QString::number(info.bandwidth).rightJustified(10) + "; "; - for(int iTag = 0; iTag Bookmarks::getBookmarksInRange(qint64 low, qint64 high) +QList Bookmarks::getBookmarksInRange(qint64 low, qint64 high, bool autoAdded) { BookmarkInfo info; - info.frequency=low; + info.frequency = low; QList::const_iterator lb = std::lower_bound(m_BookmarkList.begin(), m_BookmarkList.end(), info); info.frequency=high; QList::const_iterator ub = std::upper_bound(m_BookmarkList.begin(), m_BookmarkList.end(), info); @@ -222,10 +338,8 @@ QList Bookmarks::getBookmarksInRange(qint64 low, qint64 high) while (lb != ub) { const BookmarkInfo& info = *lb; - //if(info.IsActive()) - { - found.append(info); - } + if (!autoAdded || lb->get_freq_lock()) + found.append(info); lb++; } @@ -233,6 +347,12 @@ QList Bookmarks::getBookmarksInRange(qint64 low, qint64 high) } +int Bookmarks::find(const BookmarkInfo &info) +{ + return m_BookmarkList.indexOf(info); +} + + TagInfo &Bookmarks::findOrAddTag(QString tagName) { tagName = tagName.trimmed(); @@ -283,8 +403,8 @@ bool Bookmarks::removeTag(QString tagName) // Delete Tag. m_TagList.removeAt(idx); - emit BookmarksChanged(); emit TagListChanged(); + save(); return true; } diff --git a/src/qtgui/bookmarks.h b/src/qtgui/bookmarks.h index cfb795e3a3..b6d2255f1f 100644 --- a/src/qtgui/bookmarks.h +++ b/src/qtgui/bookmarks.h @@ -30,6 +30,7 @@ #include #include #include +#include "receivers/vfo.h" struct TagInfo { @@ -57,18 +58,21 @@ struct TagInfo } }; -struct BookmarkInfo +class BookmarkInfo:public vfo_s { - qint64 frequency; - QString name; - QString modulation; - qint64 bandwidth; - QList tags; + public: +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + static sptr make() + { + return sptr(new BookmarkInfo()); + } - BookmarkInfo() + BookmarkInfo():vfo_s(),frequency(0) { - this->frequency = 0; - this->bandwidth = 0; } /* BookmarkInfo( qint64 frequency, QString name, qint64 bandwidth, QString modulation ) @@ -83,6 +87,10 @@ struct BookmarkInfo { return frequency < other.frequency; } + bool operator==(const BookmarkInfo &other) const + { + return frequency == other.frequency; + } /* void setTags(QString tagString); QString getTagString(); @@ -92,6 +100,11 @@ struct BookmarkInfo const QColor GetColor() const; bool IsActive() const; + + qint64 frequency; + QString name; + QString modulation; + QList tags; }; class Bookmarks : public QObject @@ -104,11 +117,13 @@ class Bookmarks : public QObject void add(BookmarkInfo& info); void remove(int index); + void remove(const BookmarkInfo &info); bool load(); bool save(); int size() { return m_BookmarkList.size(); } BookmarkInfo& getBookmark(int i) { return m_BookmarkList[i]; } - QList getBookmarksInRange(qint64 low, qint64 high); + QList getBookmarksInRange(qint64 low, qint64 high, bool autoAdded = false); + int find(const BookmarkInfo &info); //int lowerBound(qint64 low); //int upperBound(qint64 high); @@ -123,9 +138,9 @@ class Bookmarks : public QObject private: Bookmarks(); // Singleton Constructor is private. QList m_BookmarkList; - QList m_TagList; - QString m_bookmarksFile; - static Bookmarks* m_pThis; + QList m_TagList; + QString m_bookmarksFile; + static Bookmarks* m_pThis; signals: void BookmarksChanged(void); diff --git a/src/qtgui/bookmarkstablemodel.cpp b/src/qtgui/bookmarkstablemodel.cpp index 710eeed956..4dbc7aaf15 100644 --- a/src/qtgui/bookmarkstablemodel.cpp +++ b/src/qtgui/bookmarkstablemodel.cpp @@ -20,6 +20,7 @@ * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ +#include #include #include #include "bookmarks.h" @@ -28,7 +29,9 @@ BookmarksTableModel::BookmarksTableModel(QObject *parent) : - QAbstractTableModel(parent) + QAbstractTableModel(parent), + m_sortCol(0), + m_sortDir(Qt::AscendingOrder) { } @@ -38,7 +41,7 @@ int BookmarksTableModel::rowCount ( const QModelIndex & /*parent*/ ) const } int BookmarksTableModel::columnCount ( const QModelIndex & /*parent*/ ) const { - return 5; + return COLUMN_COUNT; } QVariant BookmarksTableModel::headerData ( int section, Qt::Orientation orientation, int role ) const @@ -53,14 +56,92 @@ QVariant BookmarksTableModel::headerData ( int section, Qt::Orientation orientat case COL_NAME: return QString("Name"); break; + case COL_TAGS: + return QString("Tag"); + break; + case COL_LOCKED: + return QString("Auto"); + break; case COL_MODULATION: return QString("Modulation"); break; - case COL_BANDWIDTH: - return QString("Bandwidth"); + case COL_FILTER_LOW: + return QString("Filter Low"); break; - case COL_TAGS: - return QString("Tag"); + case COL_FILTER_HIGH: + return QString("Filter High"); + break; + case COL_FILTER_TW: + return QString("Filter Tw"); + break; + case COL_AGC_ON: + return QString("AGC"); + break; + case COL_AGC_TARGET: + return QString("AGC Target"); + break; + case COL_AGC_MANUAL: + return QString("AGC Manual"); + break; + case COL_AGC_MAX: + return QString("AGC Max Gain"); + break; + case COL_AGC_ATTACK: + return QString("AGC Attack"); + break; + case COL_AGC_DECAY: + return QString("AGC Decay"); + break; + case COL_AGC_HANG: + return QString("AGC Hang"); + break; + case COL_AGC_PANNING: + return QString("Panning"); + break; + case COL_AGC_PANNING_AUTO: + return QString("Autopanning"); + break; + case COL_CW_OFFSET: + return QString("CW Offset"); + break; + case COL_FM_MAXDEV: + return QString("FM Deviation"); + break; + case COL_FM_DEEMPH: + return QString("FM Deemphasis"); + break; + case COL_AM_DCR: + return QString("AM DCR"); + break; + case COL_AMSYNC_DCR: + return QString("AM SYNC DCR"); + break; + case COL_AMSYNC_PLL_BW: + return QString("AM SYNC PLL BW"); + break; + case COL_NB1_ON: + return QString("NB1 ON"); + break; + case COL_NB1_THRESHOLD: + return QString("NB1 Threshold"); + break; + case COL_NB2_ON: + return QString("NB2 ON"); + break; + case COL_NB2_THRESHOLD: + return QString("NB2 Threshold"); + break; + case COL_REC_DIR: + return QString("REC Directory"); + break; + case COL_REC_SQL_TRIGGERED: + return QString("REC SQL-triggered"); + break; + case COL_REC_MIN_TIME: + return QString("REC Min Time"); + break; + case COL_REC_MAX_GAP: + return QString("REC Max Gap"); break; } } @@ -71,29 +152,16 @@ QVariant BookmarksTableModel::headerData ( int section, Qt::Orientation orientat return QVariant(); } -QVariant BookmarksTableModel::data ( const QModelIndex & index, int role ) const +QVariant BookmarksTableModel::dataFromBookmark(BookmarkInfo &info, int index) { - BookmarkInfo& info = *m_Bookmarks[index.row()]; - - if(role==Qt::BackgroundRole) + switch(index) { - QColor bg(info.GetColor()); - bg.setAlpha(0x60); - return bg; - } - else if(role == Qt::DisplayRole || role==Qt::EditRole) - { - switch(index.column()) + case COL_FREQUENCY: + return info.frequency; + case COL_NAME: + return info.name; + case COL_TAGS: { - case COL_FREQUENCY: - return info.frequency; - case COL_NAME: - return (role==Qt::EditRole)?QString(info.name):info.name; - case COL_MODULATION: - return info.modulation; - case COL_BANDWIDTH: - return (info.bandwidth==0)?QVariant(""):QVariant(info.bandwidth); - case COL_TAGS: QString strTags; for(int iTag=0; iTag= + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toLongLong(); + else + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toLongLong() < + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toLongLong(); + + case COL_FILTER_LOW://int + case COL_FILTER_HIGH: + case COL_FILTER_TW: + case COL_AGC_TARGET: + case COL_AGC_MAX: + case COL_AGC_ATTACK: + case COL_AGC_DECAY: + case COL_AGC_HANG: + case COL_AGC_PANNING: + case COL_CW_OFFSET: + case COL_REC_MIN_TIME: + case COL_REC_MAX_GAP: + if (order) + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toInt() >= + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toInt(); + else + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toInt() < + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toInt(); + + case COL_LOCKED://bool + case COL_AGC_ON: + case COL_AGC_PANNING_AUTO: + case COL_AM_DCR: + case COL_AMSYNC_DCR: + case COL_NB1_ON: + case COL_NB2_ON: + case COL_REC_SQL_TRIGGERED: + if (order) + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toBool() >= + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toBool(); + else + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toBool() < + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toBool(); + + case COL_AGC_MANUAL://float + case COL_FM_MAXDEV: + case COL_FM_DEEMPH: + case COL_AMSYNC_PLL_BW: + case COL_NB1_THRESHOLD: + case COL_NB2_THRESHOLD: + if (order) + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toFloat() >= + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toFloat(); + else + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toFloat() < + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toFloat(); + + case COL_NAME://string + case COL_TAGS: + case COL_MODULATION: + case COL_REC_DIR: + default: + if (order) + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toString() >= + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toString(); + else + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toString() < + dataFromBookmark(Bookmarks::Get().getBookmark(b), column).toString(); + + } +} + +void BookmarksTableModel::sort(int column, Qt::SortOrder order) +{ + if (column < 0) + return; + m_sortCol = column; + m_sortDir = order; + std::stable_sort(m_Bookmarks.begin(), m_Bookmarks.end(), + std::bind(bmCompare, std::placeholders::_1, + std::placeholders::_2, column, order)); + emit layoutChanged(); } diff --git a/src/qtgui/bookmarkstablemodel.h b/src/qtgui/bookmarkstablemodel.h index 66c4046daf..6a7ad2cf14 100644 --- a/src/qtgui/bookmarkstablemodel.h +++ b/src/qtgui/bookmarkstablemodel.h @@ -25,10 +25,11 @@ #include #include +#include "receivers/defines.h" +#include "receivers/modulations.h" #include "bookmarks.h" - class BookmarksTableModel : public QAbstractTableModel { Q_OBJECT @@ -36,28 +37,61 @@ class BookmarksTableModel : public QAbstractTableModel public: enum EColumns { - COL_FREQUENCY, + COL_FREQUENCY = 0, COL_NAME, + COL_TAGS, + COL_LOCKED, COL_MODULATION, - COL_BANDWIDTH, - COL_TAGS + COL_FILTER_LOW, + COL_FILTER_HIGH, + COL_FILTER_TW, + COL_AGC_ON, + COL_AGC_TARGET, + COL_AGC_MANUAL, + COL_AGC_MAX, + COL_AGC_ATTACK, + COL_AGC_DECAY, + COL_AGC_HANG, + COL_AGC_PANNING, + COL_AGC_PANNING_AUTO, + COL_CW_OFFSET, + COL_FM_MAXDEV, + COL_FM_DEEMPH, + COL_AM_DCR, + COL_AMSYNC_DCR, + COL_AMSYNC_PLL_BW, + COL_NB1_ON, + COL_NB1_THRESHOLD, + COL_NB2_ON, + COL_NB2_THRESHOLD, + COL_REC_DIR, + COL_REC_SQL_TRIGGERED, + COL_REC_MIN_TIME, + COL_REC_MAX_GAP, + COLUMN_COUNT }; explicit BookmarksTableModel(QObject *parent = 0); - - int rowCount ( const QModelIndex & parent = QModelIndex() ) const; - int columnCount ( const QModelIndex & parent = QModelIndex() ) const; - QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; - QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const; - bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ); - Qt::ItemFlags flags ( const QModelIndex & index ) const; - BookmarkInfo* getBookmarkAtRow(int row); + int rowCount ( const QModelIndex & parent = QModelIndex() ) const override; + int columnCount ( const QModelIndex & parent = QModelIndex() ) const override; + QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; + QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override; + bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override; + Qt::ItemFlags flags ( const QModelIndex & index ) const override; + + BookmarkInfo* getBookmarkAtRow(int row) const; int GetBookmarksIndexForRow(int iRow); + int GetRowForBookmarkIndex(int index); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; private: - QList m_Bookmarks; - QMap m_mapRowToBookmarksIndex; + static QVariant dataFromBookmark(BookmarkInfo &info, int index); + static bool bmCompare(const int a, const int b, int column, int order); +private: + QList m_Bookmarks; + int m_sortCol; + Qt::SortOrder m_sortDir; signals: public slots: diff --git a/src/qtgui/demod_options.cpp b/src/qtgui/demod_options.cpp index c1f4b113a1..2fccd60e5b 100644 --- a/src/qtgui/demod_options.cpp +++ b/src/qtgui/demod_options.cpp @@ -231,3 +231,13 @@ void CDemodOptions::on_pllBwSelector_activated(int index) { emit amSyncPllBwSelected(pll_bw_from_index(index)); } + +void CDemodOptions::setAmDcr(bool on) +{ + ui->dcrCheckBox->setChecked(on); +} + +void CDemodOptions::setAmSyncDcr(bool on) +{ + ui->syncdcrCheckBox->setChecked(on); +} diff --git a/src/qtgui/demod_options.h b/src/qtgui/demod_options.h index 581ef81944..cb4a107f46 100644 --- a/src/qtgui/demod_options.h +++ b/src/qtgui/demod_options.h @@ -70,6 +70,9 @@ class CDemodOptions : public QDialog void setPllBw(float pll_bw); float getPllBw(void) const; + void setAmDcr(bool on); + void setAmSyncDcr(bool on); + signals: /*! \brief Signal emitted when new FM deviation is selected. */ void fmMaxdevSelected(float max_dev); diff --git a/src/qtgui/dockaudio.cpp b/src/qtgui/dockaudio.cpp index ab2472d787..355050b55b 100644 --- a/src/qtgui/dockaudio.cpp +++ b/src/qtgui/dockaudio.cpp @@ -50,15 +50,16 @@ DockAudio::DockAudio(QWidget *parent) : audioOptions = new CAudioOptions(this); connect(audioOptions, SIGNAL(newFftSplit(int)), ui->audioSpectrum, SLOT(setPercent2DScreen(int))); - connect(audioOptions, SIGNAL(newPandapterRange(int,int)), this, SLOT(setNewPandapterRange(int,int))); - connect(audioOptions, SIGNAL(newWaterfallRange(int,int)), this, SLOT(setNewWaterfallRange(int,int))); - connect(audioOptions, SIGNAL(newRecDirSelected(QString)), this, SLOT(setNewRecDir(QString))); - connect(audioOptions, SIGNAL(newUdpHost(QString)), this, SLOT(setNewUdpHost(QString))); - connect(audioOptions, SIGNAL(newUdpPort(int)), this, SLOT(setNewUdpPort(int))); - connect(audioOptions, SIGNAL(newUdpStereo(bool)), this, SLOT(setNewUdpStereo(bool))); - connect(audioOptions, SIGNAL(newSquelchTriggered(bool)), this, SLOT(setNewSquelchTriggered(bool))); - connect(audioOptions, SIGNAL(newRecMinTime(int)), this, SLOT(setRecMinTime(int))); - connect(audioOptions, SIGNAL(newRecMaxGap(int)), this, SLOT(setRecMaxGap(int))); + connect(audioOptions, SIGNAL(newPandapterRange(int,int)), this, SLOT(pandapterRange_changed(int,int))); + connect(audioOptions, SIGNAL(newWaterfallRange(int,int)), this, SLOT(waterfallRange_changed(int,int))); + connect(audioOptions, SIGNAL(newRecDirSelected(QString)), this, SLOT(recDir_changed(QString))); + connect(audioOptions, SIGNAL(newUdpHost(QString)), this, SLOT(udpHost_changed(QString))); + connect(audioOptions, SIGNAL(newUdpPort(int)), this, SLOT(udpPort_changed(int))); + connect(audioOptions, SIGNAL(newUdpStereo(bool)), this, SLOT(udpStereo_changed(bool))); + connect(audioOptions, SIGNAL(newSquelchTriggered(bool)), this, SLOT(squelchTriggered_changed(bool))); + connect(audioOptions, SIGNAL(newRecMinTime(int)), this, SLOT(recMinTime_changed(int))); + connect(audioOptions, SIGNAL(newRecMaxGap(int)), this, SLOT(recMaxGap_changed(int))); + connect(audioOptions, SIGNAL(copyRecSettingsToAllVFOs()), this, SLOT(copyRecSettingsToAllVFOs_clicked())); connect(ui->audioSpectrum, SIGNAL(pandapterRangeChanged(float,float)), audioOptions, SLOT(setPandapterSliderValues(float,float))); @@ -159,6 +160,30 @@ bool DockAudio::getSquelchTriggered() return squelch_triggered; } +void DockAudio::setSquelchTriggered(bool mode) +{ + squelch_triggered = mode; + audioOptions->setSquelchTriggered(mode); +} + +void DockAudio::setRecDir(const QString &dir) +{ + rec_dir = dir; + audioOptions->setRecDir(dir); +} + +void DockAudio::setRecMinTime(int time_ms) +{ + recMinTime = time_ms; + audioOptions->setRecMinTime(time_ms); +} + +void DockAudio::setRecMaxGap(int time_ms) +{ + recMaxGap = time_ms; + audioOptions->setRecMaxGap(time_ms); +} + /*! Public slot to set new RX frequency in Hz. */ void DockAudio::setRxFrequency(qint64 freq) { @@ -300,8 +325,6 @@ void DockAudio::saveSettings(QSettings *settings) settings->beginGroup("audio"); - settings->setValue("gain", audioGain()); - ival = audioOptions->getFftSplit(); if (ival != DEFAULT_FFT_SPLIT) settings->setValue("fft_split", ival); @@ -333,11 +356,6 @@ void DockAudio::saveSettings(QSettings *settings) else settings->remove("db_ranges_locked"); - if (rec_dir != QDir::homePath()) - settings->setValue("rec_dir", rec_dir); - else - settings->remove("rec_dir"); - if (udp_host.isEmpty()) settings->remove("udp_host"); else @@ -353,21 +371,6 @@ void DockAudio::saveSettings(QSettings *settings) else settings->remove("udp_stereo"); - if (squelch_triggered != false) - settings->setValue("squelch_triggered_recording", squelch_triggered); - else - settings->remove("squelch_triggered_recording"); - - if(recMinTime != 0) - settings->setValue("rec_min_time", recMinTime); - else - settings->remove("rec_min_time"); - - if(recMaxGap != 0) - settings->setValue("rec_max_gap", recMaxGap); - else - settings->remove("rec_max_gap"); - settings->endGroup(); } @@ -381,10 +384,6 @@ void DockAudio::readSettings(QSettings *settings) settings->beginGroup("audio"); - ival = settings->value("gain", QVariant(-60)).toInt(&conv_ok); - if (conv_ok) - setAudioGain(ival); - ival = settings->value("fft_split", DEFAULT_FFT_SPLIT).toInt(&conv_ok); if (conv_ok) audioOptions->setFftSplit(ival); @@ -408,10 +407,6 @@ void DockAudio::readSettings(QSettings *settings) bool_val = settings->value("db_ranges_locked", false).toBool(); audioOptions->setLockButtonState(bool_val); - // Location of audio recordings - rec_dir = settings->value("rec_dir", QDir::homePath()).toString(); - audioOptions->setRecDir(rec_dir); - // Audio streaming host, port and stereo setting udp_host = settings->value("udp_host", "localhost").toString(); udp_port = settings->value("udp_port", 7355).toInt(&conv_ok); @@ -423,27 +418,15 @@ void DockAudio::readSettings(QSettings *settings) audioOptions->setUdpPort(udp_port); audioOptions->setUdpStereo(udp_stereo); - squelch_triggered = settings->value("squelch_triggered_recording", false).toBool(); - audioOptions->setSquelchTriggered(squelch_triggered); - - recMinTime = settings->value("rec_min_time", 0).toInt(&conv_ok); - if (!conv_ok) - recMinTime = 0; - audioOptions->setRecMinTime(recMinTime); - recMaxGap = settings->value("rec_max_gap", 0).toInt(&conv_ok); - if (!conv_ok) - recMaxGap = 0; - audioOptions->setRecMaxGap(recMaxGap); - settings->endGroup(); } -void DockAudio::setNewPandapterRange(int min, int max) +void DockAudio::pandapterRange_changed(int min, int max) { ui->audioSpectrum->setPandapterRange(min, max); } -void DockAudio::setNewWaterfallRange(int min, int max) +void DockAudio::waterfallRange_changed(int min, int max) { ui->audioSpectrum->setWaterfallRange(min, max); } @@ -451,14 +434,14 @@ void DockAudio::setNewWaterfallRange(int min, int max) /*! \brief Slot called when a new valid recording directory has been selected * in the audio conf dialog. */ -void DockAudio::setNewRecDir(const QString &dir) +void DockAudio::recDir_changed(const QString &dir) { rec_dir = dir; emit recDirChanged(dir); } /*! \brief Slot called when a new network host has been entered. */ -void DockAudio::setNewUdpHost(const QString &host) +void DockAudio::udpHost_changed(const QString &host) { if (host.isEmpty()) udp_host = "localhost"; @@ -467,13 +450,13 @@ void DockAudio::setNewUdpHost(const QString &host) } /*! \brief Slot called when a new network port has been entered. */ -void DockAudio::setNewUdpPort(int port) +void DockAudio::udpPort_changed(int port) { udp_port = port; } /*! \brief Slot called when the mono/stereo streaming setting changes. */ -void DockAudio::setNewUdpStereo(bool enabled) +void DockAudio::udpStereo_changed(bool enabled) { udp_stereo = enabled; } @@ -498,20 +481,20 @@ void DockAudio::audioRecStopped() } -void DockAudio::setNewSquelchTriggered(bool enabled) +void DockAudio::squelchTriggered_changed(bool enabled) { squelch_triggered = enabled; ui->audioRecButton->setStyleSheet(enabled?"color: rgb(255,0,0)":""); emit recSquelchTriggeredChanged(enabled); } -void DockAudio::setRecMinTime(int time_ms) +void DockAudio::recMinTime_changed(int time_ms) { recMinTime = time_ms; emit recMinTimeChanged(time_ms); } -void DockAudio::setRecMaxGap(int time_ms) +void DockAudio::recMaxGap_changed(int time_ms) { recMaxGap = time_ms; emit recMaxGapChanged(time_ms); @@ -533,3 +516,8 @@ void DockAudio::increaseAudioGainShortcut() { void DockAudio::decreaseAudioGainShortcut() { ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub); } + +void DockAudio::copyRecSettingsToAllVFOs_clicked() +{ + emit copyRecSettingsToAllVFOs(); +} diff --git a/src/qtgui/dockaudio.h b/src/qtgui/dockaudio.h index 6ae277c591..4e952ba2bb 100644 --- a/src/qtgui/dockaudio.h +++ b/src/qtgui/dockaudio.h @@ -66,6 +66,12 @@ class DockAudio : public QDockWidget void setFftFill(bool enabled); bool getSquelchTriggered(); + void setSquelchTriggered(bool mode); + void setRecDir(const QString &dir); + void setRecMinTime(int time_ms); + void setRecMaxGap(int time_ms); + + void saveSettings(QSettings *settings); void readSettings(QSettings *settings); @@ -116,6 +122,9 @@ public slots: /*! \brief Signal emitted when squelch triggered recording max gap time is changed. */ void recMaxGapChanged(int time_ms); + /*! \brief Signal emitted when toAllVFOs button is clicked. */ + void copyRecSettingsToAllVFOs(); + private slots: void on_audioGainSlider_valueChanged(int value); void on_audioStreamButton_clicked(bool checked); @@ -123,15 +132,16 @@ private slots: void on_audioPlayButton_clicked(bool checked); void on_audioConfButton_clicked(); void on_audioMuteButton_clicked(bool checked); - void setNewPandapterRange(int min, int max); - void setNewWaterfallRange(int min, int max); - void setNewRecDir(const QString &dir); - void setNewUdpHost(const QString &host); - void setNewUdpPort(int port); - void setNewUdpStereo(bool enabled); - void setNewSquelchTriggered(bool enabled); - void setRecMinTime(int time_ms); - void setRecMaxGap(int time_ms); + void pandapterRange_changed(int min, int max); + void waterfallRange_changed(int min, int max); + void recDir_changed(const QString &dir); + void udpHost_changed(const QString &host); + void udpPort_changed(int port); + void udpStereo_changed(bool enabled); + void squelchTriggered_changed(bool enabled); + void recMinTime_changed(int time_ms); + void recMaxGap_changed(int time_ms); + void copyRecSettingsToAllVFOs_clicked(); private: diff --git a/src/qtgui/dockbookmarks.cpp b/src/qtgui/dockbookmarks.cpp index d7378ef9d2..63ff6135ce 100644 --- a/src/qtgui/dockbookmarks.cpp +++ b/src/qtgui/dockbookmarks.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "bookmarks.h" #include "bookmarkstaglist.h" @@ -51,10 +52,12 @@ DockBookmarks::DockBookmarks(QWidget *parent) : ui->tableViewFrequencyList->setSelectionBehavior(QAbstractItemView::SelectRows); ui->tableViewFrequencyList->setSelectionMode(QAbstractItemView::SingleSelection); ui->tableViewFrequencyList->installEventFilter(this); + ui->tableViewFrequencyList->setSortingEnabled(true); + ui->tableViewFrequencyList->sortByColumn(0, Qt::AscendingOrder); // Demod Selection in Frequency List Table. ComboBoxDelegateModulation* delegateModulation = new ComboBoxDelegateModulation(this); - ui->tableViewFrequencyList->setItemDelegateForColumn(2, delegateModulation); + ui->tableViewFrequencyList->setItemDelegateForColumn(BookmarksTableModel::COL_MODULATION, delegateModulation); // Bookmarks Context menu contextmenu = new QMenu(this); @@ -64,11 +67,35 @@ DockBookmarks::DockBookmarks(QWidget *parent) : contextmenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(DeleteSelectedBookmark())); } + // MenuItem Tune + { + QAction* action = new QAction("Tune", this); + contextmenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(tuneHere())); + } + // MenuItem Tune and load + { + QAction* action = new QAction("Tune and load settings", this); + contextmenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(tuneAndLoad())); + } + // MenuItem New demodulator + { + QAction* action = new QAction("New demodulator", this); + contextmenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(newDemod())); + } // MenuItem Add { actionAddBookmark = new QAction("Add Bookmark", this); contextmenu->addAction(actionAddBookmark); } + // MenuItem Select Columns + { + QAction* action = new QAction("Select columns...", this); + contextmenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(changeVisibleColumns())); + } ui->tableViewFrequencyList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->tableViewFrequencyList, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowContextMenu(const QPoint&))); @@ -103,25 +130,36 @@ DockBookmarks::~DockBookmarks() void DockBookmarks::activated(const QModelIndex & index) { - BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(index.row()); - emit newBookmarkActivated(info->frequency, info->modulation, info->bandwidth); + bool activate = false; + if (index.column() == BookmarksTableModel::COL_NAME) + activate = true; + if (index.column() == BookmarksTableModel::COL_FREQUENCY) + activate = true; + if (activate) + { + BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(index.row()); + emit newBookmarkActivated(*info); + } } void DockBookmarks::setNewFrequency(qint64 rx_freq) { - ui->tableViewFrequencyList->clearSelection(); - const int iRowCount = bookmarksTableModel->rowCount(); - for (int row = 0; row < iRowCount; ++row) + m_currentFrequency = rx_freq; + BookmarkInfo bi; + bi.frequency = rx_freq; + const int iBookmarkIndex = Bookmarks::Get().find(bi); + if (iBookmarkIndex > 0) { - BookmarkInfo& info = *(bookmarksTableModel->getBookmarkAtRow(row)); - if (std::abs(rx_freq - info.frequency) <= ((info.bandwidth / 2 ) + 1)) + int iRow = bookmarksTableModel->GetRowForBookmarkIndex(iBookmarkIndex); + if (iRow > 0) { - ui->tableViewFrequencyList->selectRow(row); - ui->tableViewFrequencyList->scrollTo(ui->tableViewFrequencyList->currentIndex(), QAbstractItemView::EnsureVisible ); - break; + ui->tableViewFrequencyList->selectRow(iRow); + ui->tableViewFrequencyList->scrollTo(ui->tableViewFrequencyList->currentIndex(), QAbstractItemView::EnsureVisible); + return; } } - m_currentFrequency = rx_freq; + ui->tableViewFrequencyList->clearSelection(); + return; } void DockBookmarks::updateTags() @@ -187,6 +225,36 @@ bool DockBookmarks::DeleteSelectedBookmark() return true; } +bool DockBookmarks::tuneHere() +{ + QModelIndexList selected = ui->tableViewFrequencyList->selectionModel()->selectedRows(); + if (selected.empty()) + return true; + BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(selected.first().row()); + emit newBookmarkActivated(info->frequency); + return true; +} + +bool DockBookmarks::tuneAndLoad() +{ + QModelIndexList selected = ui->tableViewFrequencyList->selectionModel()->selectedRows(); + if (selected.empty()) + return true; + BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(selected.first().row()); + emit newBookmarkActivated(*info); + return true; +} + +bool DockBookmarks::newDemod() +{ + QModelIndexList selected = ui->tableViewFrequencyList->selectionModel()->selectedRows(); + if (selected.empty()) + return true; + BookmarkInfo *info = bookmarksTableModel->getBookmarkAtRow(selected.first().row()); + emit newBookmarkActivatedAddDemod(*info); + return true; +} + void DockBookmarks::ShowContextMenu(const QPoint& pos) { contextmenu->popup(ui->tableViewFrequencyList->viewport()->mapToGlobal(pos)); @@ -208,9 +276,9 @@ ComboBoxDelegateModulation::ComboBoxDelegateModulation(QObject *parent) QWidget *ComboBoxDelegateModulation::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &index) const { QComboBox* comboBox = new QComboBox(parent); - for (int i = 0; i < DockRxOpt::ModulationStrings.size(); ++i) + for (int i = 0; i < Modulations::Strings.size(); ++i) { - comboBox->addItem(DockRxOpt::ModulationStrings[i]); + comboBox->addItem(Modulations::Strings[i]); } setEditorData(comboBox, index); return comboBox; @@ -220,7 +288,7 @@ void ComboBoxDelegateModulation::setEditorData(QWidget *editor, const QModelInde { QComboBox *comboBox = static_cast(editor); QString value = index.model()->data(index, Qt::EditRole).toString(); - int iModulation = DockRxOpt::GetEnumForModulationString(value); + int iModulation = Modulations::GetEnumForModulationString(value); comboBox->setCurrentIndex(iModulation); } @@ -277,3 +345,68 @@ void DockBookmarks::changeBookmarkTags(int row, int /*column*/) } } } + +void DockBookmarks::changeVisibleColumns() +{ + QDialog dialog(this); + dialog.setWindowTitle("Change Visible Columns"); + + QListWidget* colList = new QListWidget(&dialog); + QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok + | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + + QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); + mainLayout->addWidget(colList); + mainLayout->addWidget(buttonBox); + for (int k = 0 ; k < BookmarksTableModel::COLUMN_COUNT ; k++) + { + QListWidgetItem* qi = new QListWidgetItem(ui->tableViewFrequencyList->model()->headerData(k, Qt::Horizontal, Qt::DisplayRole).toString(), colList, 0); + qi->setCheckState(ui->tableViewFrequencyList->isColumnHidden(k) ? Qt::Unchecked : Qt::Checked); + if (k <= BookmarksTableModel::COL_NAME) + qi->setFlags(qi->flags() & ~Qt::ItemIsEnabled); + colList->addItem(qi); + } + + if (dialog.exec()) + { + for (int k = 0 ; k < BookmarksTableModel::COLUMN_COUNT ; k++) + ui->tableViewFrequencyList->setColumnHidden(k, colList->item(k)->checkState() == Qt::Unchecked); + } +} + +void DockBookmarks::saveSettings(QSettings *settings) +{ + QStringList list; + if (!settings) + return; + + settings->beginGroup("bookmarks"); + + for (int k = 0 ; k < BookmarksTableModel::COLUMN_COUNT ; k++) + if (ui->tableViewFrequencyList->isColumnHidden(k)) + list.append(ui->tableViewFrequencyList->model()->headerData(k, Qt::Horizontal, Qt::DisplayRole).toString()); + if (list.size() > 0) + settings->setValue("hidden_columns", list.join(",")); + else + settings->remove("hidden_columns"); + settings->setValue("splitter_sizes",ui->splitter->saveState()); + settings->endGroup(); +} + +void DockBookmarks::readSettings(QSettings *settings) +{ + if (!settings) + return; + + settings->beginGroup("bookmarks"); + + QString strval = settings->value("hidden_columns", "").toString(); + QStringList list = strval.split(","); + for (int k = 0 ; k < BookmarksTableModel::COLUMN_COUNT ; k++) + ui->tableViewFrequencyList->setColumnHidden(k, list.contains(ui->tableViewFrequencyList->model()->headerData(k, Qt::Horizontal, Qt::DisplayRole).toString())); + + ui->splitter->restoreState(settings->value("splitter_sizes").toByteArray()); + settings->endGroup(); +} diff --git a/src/qtgui/dockbookmarks.h b/src/qtgui/dockbookmarks.h index baf3818b0f..7d426b93d5 100644 --- a/src/qtgui/dockbookmarks.h +++ b/src/qtgui/dockbookmarks.h @@ -26,6 +26,9 @@ #include #include "qtgui/bookmarkstablemodel.h" #include +#include +#include "receivers/defines.h" +#include "receivers/modulations.h" namespace Ui { class DockBookmarks; @@ -65,9 +68,13 @@ class DockBookmarks : public QDockWidget void updateTags(); void updateBookmarks(); void changeBookmarkTags(int row, int /*column*/); + void saveSettings(QSettings *settings); + void readSettings(QSettings *settings); signals: - void newBookmarkActivated(qint64, QString, int); + void newBookmarkActivated(BookmarkInfo& bookmark); + void newBookmarkActivated(qint64); + void newBookmarkActivatedAddDemod(BookmarkInfo& bookmark); public slots: void setNewFrequency(qint64 rx_freq); @@ -80,5 +87,9 @@ private slots: void on_tableWidgetTagList_itemChanged(QTableWidgetItem* item); void ShowContextMenu(const QPoint&pos); bool DeleteSelectedBookmark(); + bool tuneHere(); + bool tuneAndLoad(); + bool newDemod(); void doubleClicked(const QModelIndex & index); + void changeVisibleColumns(); }; diff --git a/src/qtgui/dockinputctl.cpp b/src/qtgui/dockinputctl.cpp index d2518859bf..d2f9859fc9 100644 --- a/src/qtgui/dockinputctl.cpp +++ b/src/qtgui/dockinputctl.cpp @@ -114,6 +114,10 @@ void DockInputCtl::readSettings(QSettings * settings) bool_val = settings->value("gui/invert_scrolling", false).toBool(); emit invertScrollingChanged(bool_val); ui->invertScrollingButton->setChecked(bool_val); + + bool_val = settings->value("gui/auto_bookmarks", false).toBool(); + emit autoBookmarksChanged(bool_val); + ui->autoBookmarksButton->setChecked(bool_val); } void DockInputCtl::saveSettings(QSettings * settings) @@ -181,6 +185,12 @@ void DockInputCtl::saveSettings(QSettings * settings) settings->setValue("gui/invert_scrolling", true); else settings->remove("gui/invert_scrolling"); + + // Remember state of auto bookmarks button. Default is unchecked. + if (ui->autoBookmarksButton->isChecked()) + settings->setValue("gui/auto_bookmarks", true); + else + settings->remove("gui/auto_bookmarks"); } void DockInputCtl::readLnbLoFromSettings(QSettings * settings) @@ -524,6 +534,12 @@ void DockInputCtl::on_invertScrollingButton_toggled(bool checked) emit invertScrollingChanged(checked); } +/** Auto bookmarks box has changed */ +void DockInputCtl::on_autoBookmarksButton_toggled(bool checked) +{ + emit autoBookmarksChanged(checked); +} + /** Remove all widgets from the lists. */ void DockInputCtl::clearWidgets() { diff --git a/src/qtgui/dockinputctl.h b/src/qtgui/dockinputctl.h index c9a8fb47a1..1d93b34b96 100644 --- a/src/qtgui/dockinputctl.h +++ b/src/qtgui/dockinputctl.h @@ -117,6 +117,7 @@ public slots: void antennaSelected(QString antenna); void freqCtrlResetChanged(bool enabled); void invertScrollingChanged(bool enabled); + void autoBookmarksChanged(bool checked); public slots: void setLnbLo(double freq_mhz); @@ -132,6 +133,7 @@ private slots: void on_antSelector_currentIndexChanged(int index); void on_freqCtrlResetButton_toggled(bool checked); void on_invertScrollingButton_toggled(bool checked); + void on_autoBookmarksButton_toggled(bool checked); void sliderValueChanged(int value); diff --git a/src/qtgui/dockinputctl.ui b/src/qtgui/dockinputctl.ui index ece2d7d4ab..3cbcba55d6 100644 --- a/src/qtgui/dockinputctl.ui +++ b/src/qtgui/dockinputctl.ui @@ -7,13 +7,13 @@ 0 0 240 - 240 + 299 240 - 240 + 299 @@ -238,6 +238,16 @@ + + + + Automatically create demodulators when 'automatic' bookmark comes into bandwidth + + + Enable automatic demodulators + + + diff --git a/src/qtgui/dockrds.cpp b/src/qtgui/dockrds.cpp index 1ef688a7a5..89177af13c 100644 --- a/src/qtgui/dockrds.cpp +++ b/src/qtgui/dockrds.cpp @@ -106,17 +106,13 @@ void DockRDS::ClearTextFields() void DockRDS::showEnabled() { ClearTextFields(); - if (!ui->rdsCheckbox->isChecked()) - { - ui->rdsCheckbox->blockSignals(true); - ui->rdsCheckbox->setChecked(true); - ui->rdsCheckbox->blockSignals(false); - } + ui->rdsCheckbox->setChecked(true); } void DockRDS::showDisabled() { ClearTextFields(); + ui->rdsCheckbox->setChecked(false); } void DockRDS::setDisabled() @@ -133,7 +129,7 @@ void DockRDS::setEnabled() } /** Enable/disable RDS decoder */ -void DockRDS::on_rdsCheckbox_toggled(bool checked) +void DockRDS::on_rdsCheckbox_clicked(bool checked) { emit rdsDecoderToggled(checked); } diff --git a/src/qtgui/dockrds.h b/src/qtgui/dockrds.h index be995b5f3e..7a87a07347 100644 --- a/src/qtgui/dockrds.h +++ b/src/qtgui/dockrds.h @@ -32,7 +32,7 @@ public slots: void rdsPI(QString text); private slots: - void on_rdsCheckbox_toggled(bool checked); + void on_rdsCheckbox_clicked(bool checked); private: Ui::DockRDS *ui; /*! The Qt designer UI file. */ diff --git a/src/qtgui/dockrxopt.cpp b/src/qtgui/dockrxopt.cpp index da943604fb..f15d95c5c0 100644 --- a/src/qtgui/dockrxopt.cpp +++ b/src/qtgui/dockrxopt.cpp @@ -27,42 +27,6 @@ #include "dockrxopt.h" #include "ui_dockrxopt.h" - -QStringList DockRxOpt::ModulationStrings; - -// Lookup table for conversion from old settings -static const int old2new[] = { - DockRxOpt::MODE_OFF, - DockRxOpt::MODE_RAW, - DockRxOpt::MODE_AM, - DockRxOpt::MODE_NFM, - DockRxOpt::MODE_WFM_MONO, - DockRxOpt::MODE_WFM_STEREO, - DockRxOpt::MODE_LSB, - DockRxOpt::MODE_USB, - DockRxOpt::MODE_CWL, - DockRxOpt::MODE_CWU, - DockRxOpt::MODE_WFM_STEREO_OIRT, - DockRxOpt::MODE_AM_SYNC -}; - -// Filter preset table per mode, preset and lo/hi -static const int filter_preset_table[DockRxOpt::MODE_LAST][3][2] = -{ // WIDE NORMAL NARROW - {{ 0, 0}, { 0, 0}, { 0, 0}}, // MODE_OFF - {{ -15000, 15000}, { -5000, 5000}, { -1000, 1000}}, // MODE_RAW - {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_AM - {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_AMSYNC - {{ -4000, -100}, { -2800, -100}, { -2400, -300}}, // MODE_LSB - {{ 100, 4000}, { 100, 2800}, { 300, 2400}}, // MODE_USB - {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWL - {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWU - {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_NFM - {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_MONO - {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_STEREO - {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}} // MODE_WFM_STEREO_OIRT -}; - DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : QDockWidget(parent), ui(new Ui::DockRxOpt), @@ -71,23 +35,35 @@ DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : { ui->setupUi(this); - if (ModulationStrings.size() == 0) + ui->modeSelector->addItems(Modulations::Strings); + freqLockButtonMenu = new QMenu(this); + // MenuItem Lock all { - // Keep in sync with rxopt_mode_idx and filter_preset_table - ModulationStrings.append("Demod Off"); - ModulationStrings.append("Raw I/Q"); - ModulationStrings.append("AM"); - ModulationStrings.append("AM-Sync"); - ModulationStrings.append("LSB"); - ModulationStrings.append("USB"); - ModulationStrings.append("CW-L"); - ModulationStrings.append("CW-U"); - ModulationStrings.append("Narrow FM"); - ModulationStrings.append("WFM (mono)"); - ModulationStrings.append("WFM (stereo)"); - ModulationStrings.append("WFM (oirt)"); + QAction* action = new QAction("Lock all", this); + freqLockButtonMenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(menuFreqLockAll())); } - ui->modeSelector->addItems(ModulationStrings); + // MenuItem Unlock all + { + QAction* action = new QAction("Unlock all", this); + freqLockButtonMenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(menuFreqUnlockAll())); + } + ui->freqLockButton->setContextMenuPolicy(Qt::CustomContextMenu); + squelchButtonMenu = new QMenu(this); + // MenuItem Auto all + { + QAction* action = new QAction("AUTO all", this); + squelchButtonMenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(menuSquelchAutoAll())); + } + // MenuItem Reset all + { + QAction* action = new QAction("Reset all", this); + squelchButtonMenu->addAction(action); + connect(action, SIGNAL(triggered()), this, SLOT(menuSquelchResetAll())); + } + ui->autoSquelchButton->setContextMenuPolicy(Qt::CustomContextMenu); #ifdef Q_OS_LINUX ui->modeButton->setMinimumSize(32, 24); @@ -124,6 +100,8 @@ DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : connect(agcOpt, SIGNAL(attackChanged(int)), this, SLOT(agcOpt_attackChanged(int))); connect(agcOpt, SIGNAL(decayChanged(int)), this, SLOT(agcOpt_decayChanged(int))); connect(agcOpt, SIGNAL(hangChanged(int)), this, SLOT(agcOpt_hangChanged(int))); + connect(agcOpt, SIGNAL(panningChanged(int)), this, SLOT(agcOpt_panningChanged(int))); + connect(agcOpt, SIGNAL(panningAutoChanged(bool)), this, SLOT(agcOpt_panningAutoChanged(bool))); // Noise blanker options nbOpt = new CNbOptions(this); @@ -248,19 +226,8 @@ void DockRxOpt::updateHwFreq() */ unsigned int DockRxOpt::filterIdxFromLoHi(int lo, int hi) const { - int mode_index = ui->modeSelector->currentIndex(); - - if (lo == filter_preset_table[mode_index][FILTER_PRESET_WIDE][0] && - hi == filter_preset_table[mode_index][FILTER_PRESET_WIDE][1]) - return FILTER_PRESET_WIDE; - else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][0] && - hi == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][1]) - return FILTER_PRESET_NORMAL; - else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NARROW][0] && - hi == filter_preset_table[mode_index][FILTER_PRESET_NARROW][1]) - return FILTER_PRESET_NARROW; - - return FILTER_PRESET_USER; + Modulations::idx mode_index = Modulations::idx(ui->modeSelector->currentIndex()); + return Modulations::FindFilterPreset(mode_index, lo, hi); } /** @@ -318,9 +285,9 @@ int DockRxOpt::currentFilterShape() const * @brief Select new demodulator. * @param demod Demodulator index corresponding to receiver::demod. */ -void DockRxOpt::setCurrentDemod(int demod) +void DockRxOpt::setCurrentDemod(Modulations::idx demod) { - if ((demod >= MODE_OFF) && (demod < MODE_LAST)) + if ((demod >= Modulations::MODE_OFF) && (demod < Modulations::MODE_LAST)) { ui->modeSelector->setCurrentIndex(demod); updateDemodOptPage(demod); @@ -331,14 +298,14 @@ void DockRxOpt::setCurrentDemod(int demod) * @brief Get current demodulator selection. * @return The current demodulator corresponding to receiver::demod. */ -int DockRxOpt::currentDemod() const +Modulations::idx DockRxOpt::currentDemod() const { - return ui->modeSelector->currentIndex(); + return Modulations::idx(ui->modeSelector->currentIndex()); } QString DockRxOpt::currentDemodAsString() { - return GetStringForModulationIndex(currentDemod()); + return Modulations::GetStringForModulationIndex(currentDemod()); } float DockRxOpt::currentMaxdev() const @@ -374,27 +341,14 @@ double DockRxOpt::currentSquelchLevel() const return ui->sqlSpinBox->value(); } - -/** Get filter lo/hi for a given mode and preset */ -void DockRxOpt::getFilterPreset(int mode, int preset, int * lo, int * hi) const +int DockRxOpt::getCwOffset() const { - if (mode < 0 || mode >= MODE_LAST) - { - qDebug() << __func__ << ": Invalid mode:" << mode; - mode = MODE_AM; - } - else if (preset < 0 || preset > 2) - { - qDebug() << __func__ << ": Invalid preset:" << preset; - preset = FILTER_PRESET_NORMAL; - } - *lo = filter_preset_table[mode][preset][0]; - *hi = filter_preset_table[mode][preset][1]; + return demodOpt->getCwOffset(); } -int DockRxOpt::getCwOffset() const +void DockRxOpt::setCwOffset(int offset) { - return demodOpt->getCwOffset(); + demodOpt->setCwOffset(offset); } /** Get agc settings */ @@ -403,14 +357,23 @@ bool DockRxOpt::getAgcOn() return agc_is_on; } +void DockRxOpt::setAgcOn(bool on) +{ + if (on) + setAgcPresetFromParams(getAgcDecay()); + else + ui->agcPresetCombo->setCurrentIndex(4); + agc_is_on = on; +} + int DockRxOpt::getAgcTargetLevel() { return agcOpt->targetLevel(); } -int DockRxOpt::getAgcManualGain() +void DockRxOpt::setAgcTargetLevel(int level) { - return agcOpt->gain(); + agcOpt->setTargetLevel(level); } int DockRxOpt::getAgcMaxGain() @@ -418,193 +381,116 @@ int DockRxOpt::getAgcMaxGain() return agcOpt->maxGain(); } +void DockRxOpt::setAgcMaxGain(int gain) +{ + agcOpt->setMaxGain(gain); +} + int DockRxOpt::getAgcAttack() { return agcOpt->attack(); } +void DockRxOpt::setAgcAttack(int attack) +{ + agcOpt->setAttack(attack); +} + int DockRxOpt::getAgcDecay() { return agcOpt->decay(); } +void DockRxOpt::setAgcDecay(int decay) +{ + agcOpt->setDecay(decay); + setAgcOn(agc_is_on); +} + int DockRxOpt::getAgcHang() { return agcOpt->hang(); } -/** Read receiver configuration from settings data. */ -void DockRxOpt::readSettings(QSettings *settings) +void DockRxOpt::setAgcHang(int hang) { - bool conv_ok; - int int_val; - double dbl_val; - - int_val = settings->value("receiver/cwoffset", 700).toInt(&conv_ok); - if (conv_ok) - demodOpt->setCwOffset(int_val); - - int_val = settings->value("receiver/fm_maxdev", 2500).toInt(&conv_ok); - if (conv_ok) - demodOpt->setMaxDev(int_val); - - dbl_val = settings->value("receiver/fm_deemph", 75).toDouble(&conv_ok); - if (conv_ok && dbl_val >= 0) - demodOpt->setEmph(1.0e-6 * dbl_val); // was stored as usec - - qint64 offs = settings->value("receiver/offset", 0).toInt(&conv_ok); - if (offs) - { - setFilterOffset(offs); - emit filterOffsetChanged(offs); - } - - dbl_val = settings->value("receiver/sql_level", 1.0).toDouble(&conv_ok); - if (conv_ok && dbl_val < 1.0) - ui->sqlSpinBox->setValue(dbl_val); - - // AGC settings - //TODO: cleanup config - #if 0 - int_val = settings->value("receiver/agc_threshold", -100).toInt(&conv_ok); - if (conv_ok) - agcOpt->setThreshold(int_val); - #endif - int_val = settings->value("receiver/agc_target_level", 0).toInt(&conv_ok); - if (conv_ok) - agcOpt->setTargetLevel(int_val); - - //TODO: store/restore the preset correctly - int_val = settings->value("receiver/agc_decay", 500).toInt(&conv_ok); - if (conv_ok) - { - agcOpt->setDecay(int_val); - if (int_val == 100) - ui->agcPresetCombo->setCurrentIndex(0); - else if (int_val == 500) - ui->agcPresetCombo->setCurrentIndex(1); - else if (int_val == 2000) - ui->agcPresetCombo->setCurrentIndex(2); - else - ui->agcPresetCombo->setCurrentIndex(3); - } - - int_val = settings->value("receiver/agc_attack", 20).toInt(&conv_ok); - if (conv_ok) - agcOpt->setAttack(int_val); - - int_val = settings->value("receiver/agc_hang", 0).toInt(&conv_ok); - if (conv_ok) - agcOpt->setHang(int_val); - - int_val = settings->value("receiver/agc_gain", 0).toInt(&conv_ok); - if (conv_ok) - agcOpt->setGain(int_val); - - int_val = settings->value("receiver/agc_maxgain", 100).toInt(&conv_ok); - if (conv_ok) - agcOpt->setMaxGain(int_val); - - if (settings->value("receiver/agc_off", false).toBool()) - ui->agcPresetCombo->setCurrentIndex(4); - - int_val = MODE_AM; - if (settings->contains("receiver/demod")) { - if (settings->value("configversion").toInt(&conv_ok) >= 3) { - int_val = GetEnumForModulationString(settings->value("receiver/demod").toString()); - } else { - int_val = old2new[settings->value("receiver/demod").toInt(&conv_ok)]; - } - } - - setCurrentDemod(int_val); - emit demodSelected(int_val); - + agcOpt->setHang(hang); } -/** Save receiver configuration to settings. */ -void DockRxOpt::saveSettings(QSettings *settings) +int DockRxOpt::getAgcPanning() { - int int_val; - - settings->setValue("receiver/demod", currentDemodAsString()); + return agcOpt->panning(); +} - int cwofs = demodOpt->getCwOffset(); - if (cwofs == 700) - settings->remove("receiver/cwoffset"); - else - settings->setValue("receiver/cwoffset", cwofs); +void DockRxOpt::setAgcPanning(int panning) +{ + agcOpt->setPanning(panning); +} - // currently we do not need the decimal - int_val = (int)demodOpt->getMaxDev(); - if (int_val == 2500) - settings->remove("receiver/fm_maxdev"); - else - settings->setValue("receiver/fm_maxdev", int_val); +bool DockRxOpt::getAgcPanningAuto() +{ + return agcOpt->panningAuto(); +} - // save as usec - int_val = (int)(1.0e6 * demodOpt->getEmph()); - if (int_val == 75) - settings->remove("receiver/fm_deemph"); - else - settings->setValue("receiver/fm_deemph", int_val); +void DockRxOpt::setAgcPanningAuto(bool panningAuto) +{ + agcOpt->setPanningAuto(panningAuto); +} - qint64 offs = ui->filterFreq->getFrequency(); - if (offs) - settings->setValue("receiver/offset", offs); +void DockRxOpt::setAgcPresetFromParams(int decay) +{ + if (decay == 100) + ui->agcPresetCombo->setCurrentIndex(0); + else if (decay == 500) + ui->agcPresetCombo->setCurrentIndex(1); + else if (decay == 2000) + ui->agcPresetCombo->setCurrentIndex(2); else - settings->remove("receiver/offset"); + ui->agcPresetCombo->setCurrentIndex(3); +} - qDebug() << __func__ << "*** FIXME_ SQL on/off"; - //int sql_lvl = double(ui->sqlSlider->value()); // note: dBFS*10 as int - double sql_lvl = ui->sqlSpinBox->value(); - if (sql_lvl > -150.0) - settings->setValue("receiver/sql_level", sql_lvl); - else - settings->remove("receiver/sql_level"); +void DockRxOpt::setAmDcr(bool on) +{ + demodOpt->setAmDcr(on); +} - // AGC settings - int_val = agcOpt->targetLevel(); - if (int_val != 0) - settings->setValue("receiver/agc_target_level", int_val); - else - settings->remove("receiver/agc_target_level"); +void DockRxOpt::setAmSyncDcr(bool on) +{ + demodOpt->setAmSyncDcr(on); +} - int_val = agcOpt->attack(); - if (int_val != 20) - settings->setValue("receiver/agc_attack", int_val); - else - settings->remove("receiver/agc_decay"); +void DockRxOpt::setAmSyncPllBw(float bw) +{ + demodOpt->setPllBw(bw); +} - int_val = agcOpt->decay(); - if (int_val != 500) - settings->setValue("receiver/agc_decay", int_val); - else - settings->remove("receiver/agc_decay"); +void DockRxOpt::setFmMaxdev(float max_hz) +{ + demodOpt->setMaxDev(max_hz); +} - int_val = agcOpt->hang(); - if (int_val != 0) - settings->setValue("receiver/agc_hang", int_val); - else - settings->remove("receiver/agc_hang"); +void DockRxOpt::setFmEmph(double tau) +{ + demodOpt->setEmph(tau); +} - int_val = agcOpt->gain(); - if (int_val != 0) - settings->setValue("receiver/agc_gain", int_val); +void DockRxOpt::setNoiseBlanker(int nbid, bool on, float threshold) +{ + if (nbid == 1) + ui->nb1Button->setChecked(on); else - settings->remove("receiver/agc_gain"); + ui->nb2Button->setChecked(on); + nbOpt->setNbThreshold(nbid, threshold); +} - int_val = agcOpt->maxGain(); - if (int_val != 100) - settings->setValue("receiver/agc_maxgain", int_val); - else - settings->remove("receiver/agc_maxgain"); +void DockRxOpt::setFreqLock(bool lock) +{ + ui->freqLockButton->setChecked(lock); +} - // AGC Off - if (ui->agcPresetCombo->currentIndex() == 4) - settings->setValue("receiver/agc_off", true); - else - settings->remove("receiver/agc_off"); +bool DockRxOpt::getFreqLock() +{ + return ui->freqLockButton->isChecked(); } /** RX frequency changed through spin box */ @@ -663,7 +549,7 @@ void DockRxOpt::on_filterCombo_activated(int index) qDebug() << "New filter preset:" << ui->filterCombo->currentText(); qDebug() << " shape:" << ui->filterShapeCombo->currentIndex(); - emit demodSelected(ui->modeSelector->currentIndex()); + emit demodSelected(Modulations::idx(ui->modeSelector->currentIndex())); } /** @@ -679,20 +565,20 @@ void DockRxOpt::on_filterCombo_activated(int index) */ void DockRxOpt::on_modeSelector_activated(int index) { - updateDemodOptPage(index); - emit demodSelected(index); + updateDemodOptPage(Modulations::idx(index)); + emit demodSelected(Modulations::idx(index)); } -void DockRxOpt::updateDemodOptPage(int demod) +void DockRxOpt::updateDemodOptPage(Modulations::idx demod) { // update demodulator option widget - if (demod == MODE_NFM) + if (demod == Modulations::MODE_NFM) demodOpt->setCurrentPage(CDemodOptions::PAGE_FM_OPT); - else if (demod == MODE_AM) + else if (demod == Modulations::MODE_AM) demodOpt->setCurrentPage(CDemodOptions::PAGE_AM_OPT); - else if (demod == MODE_CWL || demod == MODE_CWU) + else if (demod == Modulations::MODE_CWL || demod == Modulations::MODE_CWU) demodOpt->setCurrentPage(CDemodOptions::PAGE_CW_OPT); - else if (demod == MODE_AM_SYNC) + else if (demod == Modulations::MODE_AM_SYNC) demodOpt->setCurrentPage(CDemodOptions::PAGE_AMSYNC_OPT); else demodOpt->setCurrentPage(CDemodOptions::PAGE_NO_OPT); @@ -717,7 +603,18 @@ void DockRxOpt::on_agcButton_clicked() */ void DockRxOpt::on_autoSquelchButton_clicked() { - double newval = sqlAutoClicked(); // FIXME: We rely on signal only being connected to one slot + double newval = sqlAutoClicked(false); // FIXME: We rely on signal only being connected to one slot + ui->sqlSpinBox->setValue(newval); +} + +void DockRxOpt::on_autoSquelchButton_customContextMenuRequested(const QPoint& pos) +{ + squelchButtonMenu->popup(ui->autoSquelchButton->mapToGlobal(pos)); +} + +void DockRxOpt::menuSquelchAutoAll() +{ + double newval = sqlAutoClicked(true); // FIXME: We rely on signal only being connected to one slot ui->sqlSpinBox->setValue(newval); } @@ -726,6 +623,12 @@ void DockRxOpt::on_resetSquelchButton_clicked() ui->sqlSpinBox->setValue(-150.0); } +void DockRxOpt::menuSquelchResetAll() +{ + ui->sqlSpinBox->setValue(-150.0); + emit sqlResetAllClicked(); +} + /** AGC preset has changed. */ void DockRxOpt::on_agcPresetCombo_currentIndexChanged(int index) { @@ -813,6 +716,24 @@ void DockRxOpt::agcOpt_maxGainChanged(int gain) emit agcMaxGainChanged(gain); } +/** + * @brief AGC panning changed. + * @param value The new relative panning position. + */ +void DockRxOpt::agcOpt_panningChanged(int value) +{ + emit agcPanningChanged(value); +} + +/** + * @brief AGC panning auto mode changed. + * @param value The new auto mode state. + */ +void DockRxOpt::agcOpt_panningAutoChanged(bool value) +{ + emit agcPanningAuto(value); +} + /** * @brief Squelch level change. * @param value The new squelch level in dB. @@ -884,96 +805,88 @@ void DockRxOpt::on_nb2Button_toggled(bool checked) emit noiseBlankerChanged(2, checked, (float) nbOpt->nbThreshold(2)); } -/** Noise blanker threshold has been changed. */ -void DockRxOpt::nbOpt_thresholdChanged(int nbid, double value) +void DockRxOpt::on_freqLockButton_clicked() { - if (nbid == 1) - emit noiseBlankerChanged(nbid, ui->nb1Button->isChecked(), (float) value); - else - emit noiseBlankerChanged(nbid, ui->nb2Button->isChecked(), (float) value); + emit freqLock(ui->freqLockButton->isChecked(), false); } -void DockRxOpt::on_nbOptButton_clicked() +void DockRxOpt::on_freqLockButton_customContextMenuRequested(const QPoint& pos) { - nbOpt->show(); + freqLockButtonMenu->popup(ui->freqLockButton->mapToGlobal(pos)); } -int DockRxOpt::GetEnumForModulationString(QString param) +void DockRxOpt::menuFreqLockAll() { - int iModulation = -1; - for(int i=0; ifreqLockButton->setChecked(true); +} + +void DockRxOpt::menuFreqUnlockAll() +{ + emit freqLock(false, true); + ui->freqLockButton->setChecked(false); } -bool DockRxOpt::IsModulationValid(QString strModulation) +/** Noise blanker threshold has been changed. */ +void DockRxOpt::nbOpt_thresholdChanged(int nbid, double value) { - return DockRxOpt::ModulationStrings.contains(strModulation, Qt::CaseInsensitive); + if (nbid == 1) + emit noiseBlankerChanged(nbid, ui->nb1Button->isChecked(), (float) value); + else + emit noiseBlankerChanged(nbid, ui->nb2Button->isChecked(), (float) value); } -QString DockRxOpt::GetStringForModulationIndex(int iModulationIndex) +void DockRxOpt::on_nbOptButton_clicked() { - return ModulationStrings[iModulationIndex]; + nbOpt->show(); } void DockRxOpt::modeOffShortcut() { - on_modeSelector_activated(MODE_OFF); + on_modeSelector_activated(Modulations::MODE_OFF); } void DockRxOpt::modeRawShortcut() { - on_modeSelector_activated(MODE_RAW); + on_modeSelector_activated(Modulations::MODE_RAW); } void DockRxOpt::modeAMShortcut() { - on_modeSelector_activated(MODE_AM); + on_modeSelector_activated(Modulations::MODE_AM); } void DockRxOpt::modeNFMShortcut() { - on_modeSelector_activated(MODE_NFM); + on_modeSelector_activated(Modulations::MODE_NFM); } void DockRxOpt::modeWFMmonoShortcut() { - on_modeSelector_activated(MODE_WFM_MONO); + on_modeSelector_activated(Modulations::MODE_WFM_MONO); } void DockRxOpt::modeWFMstereoShortcut() { - on_modeSelector_activated(MODE_WFM_STEREO); + on_modeSelector_activated(Modulations::MODE_WFM_STEREO); } void DockRxOpt::modeLSBShortcut() { - on_modeSelector_activated(MODE_LSB); + on_modeSelector_activated(Modulations::MODE_LSB); } void DockRxOpt::modeUSBShortcut() { - on_modeSelector_activated(MODE_USB); + on_modeSelector_activated(Modulations::MODE_USB); } void DockRxOpt::modeCWLShortcut() { - on_modeSelector_activated(MODE_CWL); + on_modeSelector_activated(Modulations::MODE_CWL); } void DockRxOpt::modeCWUShortcut() { - on_modeSelector_activated(MODE_CWU); + on_modeSelector_activated(Modulations::MODE_CWU); } void DockRxOpt::modeWFMoirtShortcut() { - on_modeSelector_activated(MODE_WFM_STEREO_OIRT); + on_modeSelector_activated(Modulations::MODE_WFM_STEREO_OIRT); } void DockRxOpt::modeAMsyncShortcut() { - on_modeSelector_activated(MODE_AM_SYNC); + on_modeSelector_activated(Modulations::MODE_AM_SYNC); } void DockRxOpt::filterNarrowShortcut() { diff --git a/src/qtgui/dockrxopt.h b/src/qtgui/dockrxopt.h index 00f0aaa589..c7614aee25 100644 --- a/src/qtgui/dockrxopt.h +++ b/src/qtgui/dockrxopt.h @@ -25,14 +25,12 @@ #include #include +#include #include "qtgui/agc_options.h" #include "qtgui/demod_options.h" #include "qtgui/nb_options.h" - -#define FILTER_PRESET_WIDE 0 -#define FILTER_PRESET_NORMAL 1 -#define FILTER_PRESET_NARROW 2 -#define FILTER_PRESET_USER 3 +#include "receivers/defines.h" +#include "receivers/modulations.h" namespace Ui { class DockRxOpt; @@ -56,35 +54,9 @@ class DockRxOpt : public QDockWidget public: - /** - * Mode selector entries. - * - * @note If you change this enum, remember to update the TCP interface. - * @note Keep in same order as the Strings in ModulationStrings, see - * DockRxOpt.cpp constructor. - */ - enum rxopt_mode_idx { - MODE_OFF = 0, /*!< Demodulator completely off. */ - MODE_RAW = 1, /*!< Raw I/Q passthrough. */ - MODE_AM = 2, /*!< Amplitude modulation. */ - MODE_AM_SYNC = 3, /*!< Amplitude modulation (synchronous demod). */ - MODE_LSB = 4, /*!< Lower side band. */ - MODE_USB = 5, /*!< Upper side band. */ - MODE_CWL = 6, /*!< CW using LSB filter. */ - MODE_CWU = 7, /*!< CW using USB filter. */ - MODE_NFM = 8, /*!< Narrow band FM. */ - MODE_WFM_MONO = 9, /*!< Broadcast FM (mono). */ - MODE_WFM_STEREO = 10, /*!< Broadcast FM (stereo). */ - MODE_WFM_STEREO_OIRT = 11, /*!< Broadcast FM (stereo oirt). */ - MODE_LAST = 12 - }; - explicit DockRxOpt(qint64 filterOffsetRange = 90000, QWidget *parent = 0); ~DockRxOpt(); - void readSettings(QSettings *settings); - void saveSettings(QSettings *settings); - void setFilterOffsetRange(qint64 range_hz); void setFilterParam(int lo, int hi); @@ -100,40 +72,54 @@ class DockRxOpt : public QDockWidget void setResetLowerDigits(bool enabled); void setInvertScrolling(bool enabled); - int currentDemod() const; + Modulations::idx currentDemod() const; QString currentDemodAsString(); float currentMaxdev() const; double currentEmph() const; double currentSquelchLevel() const; - void getFilterPreset(int mode, int preset, int * lo, int * hi) const; int getCwOffset() const; + void setCwOffset(int offset); double getSqlLevel(void) const; bool getAgcOn(); + void setAgcOn(bool on); int getAgcTargetLevel(); - int getAgcManualGain(); + void setAgcTargetLevel(int level); int getAgcMaxGain(); + void setAgcMaxGain(int gain); int getAgcAttack(); + void setAgcAttack(int attack); int getAgcDecay(); + void setAgcDecay(int decay); int getAgcHang(); - - static QStringList ModulationStrings; - static QString GetStringForModulationIndex(int iModulationIndex); - static int GetEnumForModulationString(QString param); - static bool IsModulationValid(QString strModulation); + void setAgcHang(int hang); + int getAgcPanning(); + void setAgcPanning(int panning); + bool getAgcPanningAuto(); + void setAgcPanningAuto(bool panningAuto); + + void setAmDcr(bool on); + void setAmSyncDcr(bool on); + void setAmSyncPllBw(float bw); + void setFmMaxdev(float max_hz); + void setFmEmph(double tau); + void setNoiseBlanker(int nbid, bool on, float threshold); + + void setFreqLock(bool lock); + bool getFreqLock(); public slots: void setRxFreq(qint64 freq_hz); - void setCurrentDemod(int demod); + void setCurrentDemod(Modulations::idx demod); void setFilterOffset(qint64 freq_hz); void setSquelchLevel(double level); private: void updateHwFreq(); - void updateDemodOptPage(int demod); + void updateDemodOptPage(Modulations::idx demod); unsigned int filterIdxFromLoHi(int lo, int hi) const; void modeOffShortcut(); @@ -151,6 +137,7 @@ public slots: void filterNarrowShortcut(); void filterNormalShortcut(); void filterWideShortcut(); + void setAgcPresetFromParams(int decay); signals: /** Signal emitted when receiver frequency has changed */ @@ -160,7 +147,7 @@ public slots: void filterOffsetChanged(qint64 freq_hz); /** Signal emitted when new demodulator is selected. */ - void demodSelected(int demod); + void demodSelected(Modulations::idx demod); /** Signal emitted when new FM deviation is selected. */ void fmMaxdevSelected(float max_dev); @@ -188,7 +175,10 @@ public slots: * * @note Need current signal/noise level returned */ - double sqlAutoClicked(); + double sqlAutoClicked(bool global); + + /** Signal emitted when squelch reset all popup menu item is clicked. */ + void sqlResetAllClicked(); /** Signal emitted when AGC is togglen ON/OFF. */ void agcToggled(bool agc_on); @@ -211,9 +201,18 @@ public slots: /** Signal emitted when AGC hang is changed. Hang is in millisec.*/ void agcHangChanged(int hang); + /** Signal emitted when AGC panning is changed. Panning is relative position -100...100 */ + void agcPanningChanged(int panning); + + /** Signal emitted when AGC panning auto mode is changed. */ + void agcPanningAuto(bool panningAuto); + /** Signal emitted when noise blanker status has changed. */ void noiseBlankerChanged(int nbid, bool on, float threshold); + /** Signal emitted when freq lock mode changed. */ + void freqLock(bool lock, bool all); + void cwOffsetChanged(int offset); private slots: @@ -224,13 +223,20 @@ private slots: void on_modeButton_clicked(); void on_agcButton_clicked(); void on_autoSquelchButton_clicked(); + void on_autoSquelchButton_customContextMenuRequested(const QPoint& pos); + void menuSquelchAutoAll(); void on_resetSquelchButton_clicked(); + void menuSquelchResetAll(); //void on_agcPresetCombo_activated(int index); void on_agcPresetCombo_currentIndexChanged(int index); void on_sqlSpinBox_valueChanged(double value); void on_nb1Button_toggled(bool checked); void on_nb2Button_toggled(bool checked); void on_nbOptButton_clicked(); + void on_freqLockButton_clicked(); + void on_freqLockButton_customContextMenuRequested(const QPoint& pos); + void menuFreqLockAll(); + void menuFreqUnlockAll(); // Signals coming from noise blanker pop-up void nbOpt_thresholdChanged(int nbid, double value); @@ -250,12 +256,16 @@ private slots: void agcOpt_attackChanged(int value); void agcOpt_decayChanged(int value); void agcOpt_hangChanged(int value); + void agcOpt_panningChanged(int value); + void agcOpt_panningAutoChanged(bool value); private: Ui::DockRxOpt *ui; /** The Qt designer UI file. */ CDemodOptions *demodOpt; /** Demodulator options. */ CAgcOptions *agcOpt; /** AGC options. */ CNbOptions *nbOpt; /** Noise blanker options. */ + QMenu *freqLockButtonMenu; + QMenu *squelchButtonMenu; bool agc_is_on; diff --git a/src/qtgui/dockrxopt.ui b/src/qtgui/dockrxopt.ui index 7d6c91e11c..8fafddaaa2 100644 --- a/src/qtgui/dockrxopt.ui +++ b/src/qtgui/dockrxopt.ui @@ -6,8 +6,8 @@ 0 0 - 280 - 330 + 295 + 355 @@ -18,8 +18,8 @@ - 280 - 330 + 295 + 355 @@ -588,6 +588,12 @@ This is an offset from the hardware RF frequency.</p></body></htm + + + 0 + 0 + + Receiver frequency @@ -597,6 +603,9 @@ This is an offset from the hardware RF frequency.</p></body></htm Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + kHz + 3 @@ -611,13 +620,6 @@ This is an offset from the hardware RF frequency.</p></body></htm - - - - kHz - - - @@ -736,6 +738,53 @@ This is an offset from the hardware RF frequency.</p></body></htm + + + + + 0 + 0 + + + + + 50 + 30 + + + + + 16777215 + 16777215 + + + + Demodulator options + + + Mode options + + + + + + + :/icons/icons/lock.svg:/icons/icons/lock.svg + + + + 16 + 16 + + + + true + + + false + + + diff --git a/src/qtgui/nb_options.cpp b/src/qtgui/nb_options.cpp index e6d037a9b6..029d3cae1f 100644 --- a/src/qtgui/nb_options.cpp +++ b/src/qtgui/nb_options.cpp @@ -54,6 +54,13 @@ double CNbOptions::nbThreshold(int nbid) else return ui->nb2Threshold->value(); } +void CNbOptions::setNbThreshold(int nbid, double threshold) +{ + if (nbid == 1) + ui->nb1Threshold->setValue(threshold); + else + ui->nb2Threshold->setValue(threshold); +} void CNbOptions::on_nb1Threshold_valueChanged(double val) { diff --git a/src/qtgui/nb_options.h b/src/qtgui/nb_options.h index 4a965a7fd0..61dc8508e0 100644 --- a/src/qtgui/nb_options.h +++ b/src/qtgui/nb_options.h @@ -41,6 +41,7 @@ class CNbOptions : public QDialog void closeEvent(QCloseEvent *event); double nbThreshold(int nbid); + void setNbThreshold(int nbid, double threshold); signals: void thresholdChanged(int nb, double val); diff --git a/src/qtgui/plotter.cpp b/src/qtgui/plotter.cpp index b7613fbc5f..4ed3e19dd2 100644 --- a/src/qtgui/plotter.cpp +++ b/src/qtgui/plotter.cpp @@ -37,7 +37,6 @@ #include #include "plotter.h" #include "bandplan.h" -#include "bookmarks.h" #include "dxc_spots.h" Q_LOGGING_CATEGORY(plotter, "plotter") @@ -161,6 +160,10 @@ CPlotter::CPlotter(QWidget *parent) : QFrame(parent) wf_span = 0; fft_rate = 15; memset(m_wfbuf, 255, MAX_SCREENSIZE); + m_currentVfo = 0; + m_capturedVfo = 0; + m_lookup_vfo = vfo::make(); + m_lookup_vfo->set_index(0); } CPlotter::~CPlotter() @@ -199,6 +202,19 @@ void CPlotter::mouseMoveEvent(QMouseEvent* event) } } } + if (!m_vfos.empty()) + { + m_lookup_vfo->set_offset(freqFromX(pt.x()) - m_CenterFreq); + m_vfos_lb = m_vfos.lower_bound(m_lookup_vfo); + if (m_vfos_lb == m_vfos.end()) + m_vfos_ub = --m_vfos_lb; + else + { + m_vfos_ub = m_vfos_lb--; + if(m_vfos_ub == m_vfos.begin()) + m_vfos_lb = m_vfos_ub; + } + } // if no mouse button monitor grab regions and change cursor icon if (onTag) { @@ -226,9 +242,13 @@ void CPlotter::mouseMoveEvent(QMouseEvent* event) // in move demod box center frequency region if (CENTER != m_CursorCaptured) setCursor(QCursor(Qt::SizeHorCursor)); + m_capturedVfo = m_currentVfo; m_CursorCaptured = CENTER; if (m_TooltipsEnabled) - showToolTip(event, QString("Demod: %1 kHz").arg(m_DemodCenterFreq/1.e3, 0, 'f', 3)); + showToolTip(event, + QString("Current demod %1: %2 kHz") + .arg(m_currentVfo) + .arg(m_DemodCenterFreq/1.e3, 0, 'f', 3)); } else if (isPointCloseTo(pt.x(), m_DemodHiCutFreqX, m_CursorCaptureDelta)) { @@ -248,6 +268,30 @@ void CPlotter::mouseMoveEvent(QMouseEvent* event) if (m_TooltipsEnabled) showToolTip(event, QString("Low cut: %1 Hz").arg(m_DemodLowCutFreq)); } + else if (!m_vfos.empty() && isPointCloseTo(pt.x(), xFromFreq((*m_vfos_lb)->get_offset() + m_CenterFreq), m_CursorCaptureDelta)) + { + if (CENTER != m_CursorCaptured) + setCursor(QCursor(Qt::SizeHorCursor)); + m_CursorCaptured = CENTER; + m_capturedVfo = (*m_vfos_lb)->get_index(); + if (m_TooltipsEnabled) + showToolTip(event, + QString("Demod %1: %2 kHz") + .arg((*m_vfos_lb)->get_index()) + .arg(((*m_vfos_lb)->get_offset() + m_CenterFreq)/1.e3, 0, 'f', 3)); + } + else if (!m_vfos.empty() && isPointCloseTo(pt.x(), xFromFreq((*m_vfos_ub)->get_offset() + m_CenterFreq), m_CursorCaptureDelta)) + { + if (CENTER != m_CursorCaptured) + setCursor(QCursor(Qt::SizeHorCursor)); + m_CursorCaptured = CENTER; + m_capturedVfo = (*m_vfos_ub)->get_index(); + if (m_TooltipsEnabled) + showToolTip(event, + QString("Demod %1: %2 kHz") + .arg((*m_vfos_ub)->get_index()) + .arg(((*m_vfos_ub)->get_offset() + m_CenterFreq)/1.e3, 0, 'f', 3)); + } else { //if not near any grab boundaries if (NOCAP != m_CursorCaptured) @@ -578,6 +622,35 @@ bool CPlotter::saveWaterfall(const QString & filename) const return pixmap.save(filename, nullptr, -1); } +void CPlotter::setCurrentVfo(int current) +{ + m_currentVfo = current; + updateOverlay(); +} + +void CPlotter::addVfo(vfo::sptr n_vfo) +{ + m_vfos.insert(n_vfo); +} + +void CPlotter::removeVfo(vfo::sptr n_vfo) +{ + m_vfos.erase(n_vfo); +} + +void CPlotter::clearVfos() +{ + m_vfos.clear(); +} + +void CPlotter::getLockedVfos(std::vector &to) +{ + to.clear(); + for (auto& cvfo : m_vfos) + if (cvfo->get_freq_lock()) + to.push_back(cvfo); +} + /** Get waterfall time resolution in milleconds / line. */ quint64 CPlotter::getWfTimeRes() const { @@ -675,12 +748,35 @@ void CPlotter::mousePressEvent(QMouseEvent * event) { if (tag.first.contains(event->pos())) { - m_DemodCenterFreq = tag.second; - emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); - break; + if (event->buttons() == Qt::LeftButton) + { + //just tune + m_DemodCenterFreq = tag.second; + emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } + else if (event->buttons() == Qt::MiddleButton) + { + //tune and load settings + m_DemodCenterFreq = tag.second; + emit newDemodFreqAdd(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } + else if (event->buttons() == Qt::RightButton) + { + //new demod here + m_DemodCenterFreq = tag.second; + emit newDemodFreqLoad(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } } } } + else if (m_CursorCaptured == CENTER) + { + if (m_currentVfo != m_capturedVfo) + emit selectVfo(m_capturedVfo); + } } } @@ -1298,7 +1394,7 @@ void CPlotter::drawOverlay() static const int fontHeight = fm.ascent() + 1; static const int slant = 5; static const int levelHeight = fontHeight + 5; - static const int nLevels = h / (levelHeight + slant); + static const int nLevels = h / (levelHeight + slant) + 1; if (m_BookmarksEnabled) { tags = Bookmarks::Get().getBookmarksInRange(m_CenterFreq + m_FftCenter - m_Span / 2, @@ -1329,13 +1425,13 @@ void CPlotter::drawOverlay() x = xFromFreq(tag.frequency); int nameWidth = fm.boundingRect(tag.name).width(); - int level = 0; + int level = 1; while(level < nLevels && tagEnd[level] > x) level++; if(level >= nLevels) { - level = 0; + level = 1; if (tagEnd[level] > x) continue; // no overwrite at level 0 } @@ -1480,19 +1576,23 @@ void CPlotter::drawOverlay() // Draw demod filter box if (m_FilterBoxEnabled) { + for(auto &vfoc : m_vfos) + { + const qint64 vfoFreq = m_CenterFreq + qint64(vfoc->get_offset()); + const int demodFreqX = xFromFreq(vfoFreq); + const int demodLowCutFreqX = xFromFreq(vfoFreq + qint64(vfoc->get_filter_low())); + const int demodHiCutFreqX = xFromFreq(vfoFreq + qint64(vfoc->get_filter_high())); + + const int dw = demodHiCutFreqX - demodLowCutFreqX; + drawVfo(painter, demodFreqX, demodLowCutFreqX, dw, h, vfoc->get_index(), false); + } + m_DemodFreqX = xFromFreq(m_DemodCenterFreq); m_DemodLowCutFreqX = xFromFreq(m_DemodCenterFreq + m_DemodLowCutFreq); m_DemodHiCutFreqX = xFromFreq(m_DemodCenterFreq + m_DemodHiCutFreq); int dw = m_DemodHiCutFreqX - m_DemodLowCutFreqX; - - painter.setOpacity(0.3); - painter.fillRect(m_DemodLowCutFreqX, 0, dw, h, - QColor(PLOTTER_FILTER_BOX_COLOR)); - - painter.setOpacity(1.0); - painter.setPen(QColor(PLOTTER_FILTER_LINE_COLOR)); - painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); + drawVfo(painter, m_DemodFreqX, m_DemodLowCutFreqX, dw, h, m_currentVfo, true); } if (!m_Running) @@ -1508,6 +1608,22 @@ void CPlotter::drawOverlay() painter.end(); } +void CPlotter::drawVfo(QPainter &painter, const int demodFreqX, const int demodLowCutFreqX, const int dw, const int h, const int index, const bool is_selected) +{ + QFontMetrics metrics(m_Font); + QRect br = metrics.boundingRect("+256+"); + painter.setOpacity(0.3); + painter.fillRect(demodLowCutFreqX, br.height(), dw, h, + QColor(PLOTTER_FILTER_BOX_COLOR)); + + painter.setOpacity(1.0); + painter.setPen(QColor(is_selected ? PLOTTER_FILTER_LINE_COLOR : PLOTTER_TEXT_COLOR)); + painter.drawLine(demodFreqX, br.height(), demodFreqX, h); + painter.drawText(demodFreqX - br.width() / 2, 0, br.width(), br.height(), + Qt::AlignVCenter | Qt::AlignHCenter, + QString::number(index)); +} + // Create frequency division strings based on start frequency, span frequency, // and frequency units. // Places in QString array m_HDivText diff --git a/src/qtgui/plotter.h b/src/qtgui/plotter.h index ebb3cdbec9..3c020d9c0a 100644 --- a/src/qtgui/plotter.h +++ b/src/qtgui/plotter.h @@ -7,7 +7,11 @@ #include #include #include +#include #include +#include "bookmarks.h" +#include "receivers/defines.h" +#include "receivers/vfo.h" #define HORZ_DIVS_MAX 12 //50 #define VERT_DIVS_MIN 5 @@ -88,7 +92,7 @@ class CPlotter : public QFrame m_Span = (qint32)s; setFftCenterFreq(m_FftCenter); } - drawOverlay(); + updateOverlay(); } void setHdivDelta(int delta) { m_HdivDelta = delta; } @@ -122,15 +126,23 @@ class CPlotter : public QFrame void setFftRate(int rate_hz); void clearWaterfall(); bool saveWaterfall(const QString & filename) const; + void setCurrentVfo(int current); + void addVfo(vfo::sptr n_vfo); + void removeVfo(vfo::sptr n_vfo); + void clearVfos(); + void getLockedVfos(std::vector &to); signals: void newDemodFreq(qint64 freq, qint64 delta); /* delta is the offset from the center */ + void newDemodFreqLoad(qint64 freq, qint64 delta);/* tune and load demodulator settings */ + void newDemodFreqAdd(qint64 freq, qint64 delta);/* new demodulator here */ void newLowCutFreq(int f); void newHighCutFreq(int f); void newFilterFreq(int low, int high); /* substitute for NewLow / NewHigh */ void pandapterRangeChanged(float min, float max); void newZoomLevel(float level); void newSize(); + void selectVfo(int); public slots: // zoom functions @@ -179,6 +191,9 @@ public slots: }; void drawOverlay(); + void drawVfo(QPainter &painter, const int demodFreqX, + const int demodLowCutFreqX, const int dw, const int h, + const int index, const bool is_selected); void makeFrequencyStrs(); int xFromFreq(qint64 freq); qint64 freqFromX(int x); @@ -279,6 +294,12 @@ public slots: QMap m_Peaks; QList< QPair > m_Taglist; + vfo::set m_vfos; + vfo::set::iterator m_vfos_ub; + vfo::set::iterator m_vfos_lb; + vfo::sptr m_lookup_vfo; + int m_currentVfo; + int m_capturedVfo; // Waterfall averaging quint64 tlast_wf_ms; // last time waterfall has been updated diff --git a/src/receivers/CMakeLists.txt b/src/receivers/CMakeLists.txt index 65c9dc97f0..8bc9236712 100644 --- a/src/receivers/CMakeLists.txt +++ b/src/receivers/CMakeLists.txt @@ -7,4 +7,9 @@ add_source_files(SRCS_LIST receiver_base.h wfmrx.cpp wfmrx.h + defines.h + modulations.h + modulations.cpp + vfo.h + vfo.cpp ) diff --git a/src/receivers/defines.h b/src/receivers/defines.h new file mode 100644 index 0000000000..88ea53c3f1 --- /dev/null +++ b/src/receivers/defines.h @@ -0,0 +1,47 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2011-2016 Alexandru Csete OZ9AEC. + * + * Gqrx is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gqrx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef DEFINES_H +#define DEFINES_H + +/* Maximum number of receivers */ +#define RX_MAX 256 + + +#define TARGET_QUAD_RATE 1e6 + +/* Number of noice blankers */ +#define RECEIVER_NB_COUNT 2 + +// NB: Remember to adjust filter ranges in MainWindow +#define NB_PREF_QUAD_RATE 96000.f + +#define WFM_PREF_QUAD_RATE 240e3 // Nominal channel spacing is 200 kHz + +#define RX_FILTER_MIN_WIDTH 100 /*! Minimum width of filter */ + +#include +#include +#include + + +#endif // DEFINES_H diff --git a/src/receivers/modulations.cpp b/src/receivers/modulations.cpp new file mode 100644 index 0000000000..f643a5e9a6 --- /dev/null +++ b/src/receivers/modulations.cpp @@ -0,0 +1,295 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2011-2016 Alexandru Csete OZ9AEC. + * Copyright 2022 vladisslav2011@gmail.com. + * + * Gqrx is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gqrx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#include "receivers/defines.h" +#include "receivers/modulations.h" + +class ModulationsInitializer:public Modulations +{ +public: + ModulationsInitializer():Modulations() + { + } + ~ModulationsInitializer() + { + } +}; + +QStringList Modulations::Strings; +static ModulationsInitializer modulations = ModulationsInitializer(); +// Lookup table for conversion from old settings +static const Modulations::idx old2new[] = { + Modulations::MODE_OFF, + Modulations::MODE_RAW, + Modulations::MODE_AM, + Modulations::MODE_NFM, + Modulations::MODE_WFM_MONO, + Modulations::MODE_WFM_STEREO, + Modulations::MODE_LSB, + Modulations::MODE_USB, + Modulations::MODE_CWL, + Modulations::MODE_CWU, + Modulations::MODE_WFM_STEREO_OIRT, + Modulations::MODE_AM_SYNC +}; + +// Filter preset table per mode, preset and lo/hi +static const int filter_preset_table[Modulations::MODE_LAST][3][2] = +{ // WIDE NORMAL NARROW + {{ 0, 0}, { 0, 0}, { 0, 0}}, // MODE_OFF + {{ -15000, 15000}, { -5000, 5000}, { -1000, 1000}}, // MODE_RAW + {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_AM + {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_AMSYNC + {{ -4000, -100}, { -2800, -100}, { -2400, -300}}, // MODE_LSB + {{ 100, 4000}, { 100, 2800}, { 300, 2400}}, // MODE_USB + {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWL + {{ -1000, 1000}, { -250, 250}, { -100, 100}}, // MODE_CWU + {{ -10000, 10000}, { -5000, 5000}, { -2500, 2500}}, // MODE_NFM + {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_MONO + {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}}, // MODE_WFM_STEREO + {{-100000, 100000}, {-80000, 80000}, {-60000, 60000}} // MODE_WFM_STEREO_OIRT +}; + +// Filter ranges table per mode +static const int filter_ranges_table[Modulations::MODE_LAST][2][2] = +{ //LOW MIN MAX HIGH MIN MAX + {{ 0, 0}, { 0, 0}}, // MODE_OFF + {{ -40000, -200}, { 200, 40000}}, // MODE_RAW + {{ -40000, -200}, { 200, 40000}}, // MODE_AM + {{ -40000, -200}, { 200, 40000}}, // MODE_AMSYNC + {{ -40000, -100}, { -5000, 0}}, // MODE_LSB + {{ 0, 5000}, { 100, 40000}}, // MODE_USB + {{ -5000, -100}, { 100, 5000}}, // MODE_CWL + {{ -5000, -100}, { 100, 5000}}, // MODE_CWU + {{ -40000, -200}, { 200, 40000}}, // MODE_NFM + {{-120000, -10000}, { 10000,120000}}, // MODE_WFM_MONO + {{-120000, -10000}, { 10000,120000}}, // MODE_WFM_STEREO + {{-120000, -10000}, { 10000,120000}} // MODE_WFM_STEREO_OIRT +}; + + + +QString Modulations::GetStringForModulationIndex(int iModulationIndex) +{ + return Modulations::Strings[iModulationIndex]; +} + +bool Modulations::IsModulationValid(QString strModulation) +{ + return Modulations::Strings.contains(strModulation, Qt::CaseInsensitive); +} + +Modulations::idx Modulations::GetEnumForModulationString(QString param) +{ + int iModulation = -1; + for(int i = 0; i < Modulations::Strings.size(); ++i) + { + QString& strModulation = Modulations::Strings[i]; + if (param.compare(strModulation, Qt::CaseInsensitive) == 0) + { + iModulation = i; + break; + } + } + if(iModulation == -1) + { + std::cout << "Modulation '" << param.toStdString() << "' is unknown." << std::endl; + iModulation = MODE_OFF; + } + return idx(iModulation); +} + +bool Modulations::GetFilterPreset(Modulations::idx iModulationIndex, int preset, int& low, int& high) +{ + if (iModulationIndex >= MODE_LAST) + iModulationIndex = MODE_AM; + if (preset == FILTER_PRESET_USER) + return false; + low = filter_preset_table[iModulationIndex][preset][0]; + high = filter_preset_table[iModulationIndex][preset][1]; + return true; +} + +int Modulations::FindFilterPreset(Modulations::idx mode_index, int lo, int hi) +{ + if (lo == filter_preset_table[mode_index][FILTER_PRESET_WIDE][0] && + hi == filter_preset_table[mode_index][FILTER_PRESET_WIDE][1]) + return FILTER_PRESET_WIDE; + else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][0] && + hi == filter_preset_table[mode_index][FILTER_PRESET_NORMAL][1]) + return FILTER_PRESET_NORMAL; + else if (lo == filter_preset_table[mode_index][FILTER_PRESET_NARROW][0] && + hi == filter_preset_table[mode_index][FILTER_PRESET_NARROW][1]) + return FILTER_PRESET_NARROW; + + return FILTER_PRESET_USER; +} + +void Modulations::GetFilterRanges(Modulations::idx iModulationIndex, int& lowMin, int& lowMax, int& highMin, int& highMax) +{ + if (iModulationIndex >= MODE_LAST) + iModulationIndex = MODE_AM; + lowMin = filter_ranges_table[iModulationIndex][0][0]; + lowMax = filter_ranges_table[iModulationIndex][0][1]; + highMin = filter_ranges_table[iModulationIndex][1][0]; + highMax = filter_ranges_table[iModulationIndex][1][1]; +} + +bool Modulations::IsFilterSymmetric(idx iModulationIndex) +{ + if (iModulationIndex >= MODE_LAST) + iModulationIndex = MODE_AM; + return (-filter_ranges_table[iModulationIndex][0][0] == filter_ranges_table[iModulationIndex][1][1]); +} + +bool Modulations::UpdateFilterRange(Modulations::idx iModulationIndex, int& low, int& high) +{ + bool updated = false; + if (iModulationIndex >= MODE_LAST) + iModulationIndex = MODE_AM; + if (-filter_ranges_table[iModulationIndex][0][0] == filter_ranges_table[iModulationIndex][1][1]) + if (high != (high - low) / 2) + { + if (high > -low) + low = -high; + else + high = -low; + } + if (low < filter_ranges_table[iModulationIndex][0][0]) + { + low = filter_ranges_table[iModulationIndex][0][0]; + updated = true; + } + if (low > filter_ranges_table[iModulationIndex][0][1]) + { + low = filter_ranges_table[iModulationIndex][0][1]; + updated = true; + } + if (high < filter_ranges_table[iModulationIndex][1][0]) + { + high = filter_ranges_table[iModulationIndex][1][0]; + updated = true; + } + if (high > filter_ranges_table[iModulationIndex][1][1]) + { + high = filter_ranges_table[iModulationIndex][1][1]; + updated = true; + } + return updated; +} + +Modulations::idx Modulations::ConvertFromOld(int old) +{ + if (old < 0) + return old2new[0]; + if (old >= int(sizeof(old2new) / sizeof(old2new[0]))) + return old2new[2]; + return old2new[old]; +} + +bool Modulations::UpdateTw(const int low, const int high, int& tw) +{ + int sharp = std::abs(high - low) * 0.1; + if (tw == sharp) + return false; + if (tw < std::abs(high - low) * 0.15) + { + tw = sharp; + return true; + } + int normal = std::abs(high - low) * 0.2; + if (tw == normal) + return false; + if (tw < std::abs(high - low) * 0.25) + { + tw = normal; + return true; + } + int soft = std::abs(high - low) * 0.5; + if(tw == soft) + return false; + tw = soft; + return true; +} + +Modulations::filter_shape Modulations::FilterShapeFromTw(const int low, const int high, const int tw) +{ + Modulations::filter_shape shape = FILTER_SHAPE_SOFT; + + if (tw < std::abs(high - low) * 0.25) + shape = FILTER_SHAPE_NORMAL; + if (tw < std::abs(high - low) * 0.15) + shape = FILTER_SHAPE_SHARP; + + return shape; +} + +int Modulations::TwFromFilterShape(const int low, const int high, const Modulations::filter_shape shape) +{ + float trans_width = RX_FILTER_MIN_WIDTH * 0.1; + if ((low >= high) || (std::abs(high - low) < RX_FILTER_MIN_WIDTH)) + return trans_width; + + switch (shape) { + + case Modulations::FILTER_SHAPE_SOFT: + trans_width = std::abs(high - low) * 0.5; + break; + + case Modulations::FILTER_SHAPE_SHARP: + trans_width = std::abs(high - low) * 0.1; + break; + + case Modulations::FILTER_SHAPE_NORMAL: + default: + trans_width = std::abs(high - low) * 0.2; + break; + + } + + return trans_width; +} + +Modulations::Modulations() +{ + if (Modulations::Strings.size() == 0) + { + // Keep in sync with rxopt_mode_idx and filter_preset_table + Modulations::Strings.append("Demod Off"); + Modulations::Strings.append("Raw I/Q"); + Modulations::Strings.append("AM"); + Modulations::Strings.append("AM-Sync"); + Modulations::Strings.append("LSB"); + Modulations::Strings.append("USB"); + Modulations::Strings.append("CW-L"); + Modulations::Strings.append("CW-U"); + Modulations::Strings.append("Narrow FM"); + Modulations::Strings.append("WFM (mono)"); + Modulations::Strings.append("WFM (stereo)"); + Modulations::Strings.append("WFM (oirt)"); + } +} + +Modulations::~Modulations() +{ +} diff --git a/src/receivers/modulations.h b/src/receivers/modulations.h new file mode 100644 index 0000000000..8b57fd714c --- /dev/null +++ b/src/receivers/modulations.h @@ -0,0 +1,87 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2011-2016 Alexandru Csete OZ9AEC. + * Copyright 2022 vladisslav2011@gmail.com. + * + * Gqrx is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gqrx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef MODULATIONS_H +#define MODULATIONS_H +#include +#include + +//FIXME: Convert to enum? +#define FILTER_PRESET_WIDE 0 +#define FILTER_PRESET_NORMAL 1 +#define FILTER_PRESET_NARROW 2 +#define FILTER_PRESET_USER 3 + +class Modulations +{ +public: + /** Filter shape (convenience wrappers for "transition width"). */ + enum filter_shape { + FILTER_SHAPE_SOFT = 0, /*!< Soft: Transition band is TBD of width. */ + FILTER_SHAPE_NORMAL = 1, /*!< Normal: Transition band is TBD of width. */ + FILTER_SHAPE_SHARP = 2 /*!< Sharp: Transition band is TBD of width. */ + }; + /** + * Mode selector entries. + * + * @note If you change this enum, remember to update the TCP interface. + * @note Keep in same order as the Strings in Strings, see + * DockRxOpt.cpp constructor. + */ + enum rxopt_mode_idx { + MODE_OFF = 0, /*!< Demodulator completely off. */ + MODE_RAW = 1, /*!< Raw I/Q passthrough. */ + MODE_AM = 2, /*!< Amplitude modulation. */ + MODE_AM_SYNC = 3, /*!< Amplitude modulation (synchronous demod). */ + MODE_LSB = 4, /*!< Lower side band. */ + MODE_USB = 5, /*!< Upper side band. */ + MODE_CWL = 6, /*!< CW using LSB filter. */ + MODE_CWU = 7, /*!< CW using USB filter. */ + MODE_NFM = 8, /*!< Narrow band FM. */ + MODE_WFM_MONO = 9, /*!< Broadcast FM (mono). */ + MODE_WFM_STEREO = 10, /*!< Broadcast FM (stereo). */ + MODE_WFM_STEREO_OIRT = 11, /*!< Broadcast FM (stereo oirt). */ + MODE_LAST = 12 + }; + typedef enum rxopt_mode_idx idx; + + static QStringList Strings; + + static QString GetStringForModulationIndex(int iModulationIndex); + static bool IsModulationValid(QString strModulation); + static idx GetEnumForModulationString(QString param); + static idx ConvertFromOld(int old); + static bool GetFilterPreset(idx iModulationIndex, int preset, int& low, int& high); + static int FindFilterPreset(idx mode_index, int lo, int hi); + static void GetFilterRanges(idx iModulationIndex, int& lowMin, int& lowMax, int& highMin, int& highMax); + static bool IsFilterSymmetric(idx iModulationIndex); + static bool UpdateFilterRange(idx iModulationIndex, int& low, int& high); + static bool UpdateTw(const int low, const int high, int& tw); + static filter_shape FilterShapeFromTw(const int low, const int high, const int tw); + static int TwFromFilterShape(const int low, const int high, const filter_shape shape); + ~Modulations(); +protected: + Modulations(); +}; + +#endif // MODULATIONS_H diff --git a/src/receivers/nbrx.cpp b/src/receivers/nbrx.cpp index e6a9904499..643654b9fd 100644 --- a/src/receivers/nbrx.cpp +++ b/src/receivers/nbrx.cpp @@ -25,8 +25,6 @@ #include #include "receivers/nbrx.h" -// NB: Remember to adjust filter ranges in MainWindow -#define PREF_QUAD_RATE 96000.f nbrx_sptr make_nbrx(float quad_rate, float audio_rate) { @@ -34,51 +32,34 @@ nbrx_sptr make_nbrx(float quad_rate, float audio_rate) } nbrx::nbrx(float quad_rate, float audio_rate) - : receiver_base_cf("NBRX", PREF_QUAD_RATE, quad_rate, audio_rate), - d_running(false), - d_demod(NBRX_DEMOD_FM) + : receiver_base_cf("NBRX", NB_PREF_QUAD_RATE, quad_rate, audio_rate), + d_running(false) { - nb = make_rx_nb_cc(PREF_QUAD_RATE, 3.3, 2.5); - filter = make_rx_filter(PREF_QUAD_RATE, -5000.0, 5000.0, 1000.0); + nb = make_rx_nb_cc(NB_PREF_QUAD_RATE, 3.3, 2.5); + filter = make_rx_filter(NB_PREF_QUAD_RATE, -5000.0, 5000.0, 1000.0); demod_raw = gr::blocks::complex_to_float::make(1); demod_ssb = gr::blocks::complex_to_real::make(1); - demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 5000.0, 75.0e-6); - demod_am = make_rx_demod_am(PREF_QUAD_RATE, true); - demod_amsync = make_rx_demod_amsync(PREF_QUAD_RATE, true, 0.001); + demod_fm = make_rx_demod_fm(NB_PREF_QUAD_RATE, 5000.0, 75.0e-6); + demod_am = make_rx_demod_am(NB_PREF_QUAD_RATE, true); + demod_amsync = make_rx_demod_amsync(NB_PREF_QUAD_RATE, true, 0.001); audio_rr0.reset(); audio_rr1.reset(); - if (d_audio_rate != PREF_QUAD_RATE) + if (d_audio_rate != NB_PREF_QUAD_RATE) { - std::cout << "Resampling audio " << PREF_QUAD_RATE << " -> " + std::cout << "Resampling audio " << NB_PREF_QUAD_RATE << " -> " << d_audio_rate << std::endl; - audio_rr0 = make_resampler_ff(d_audio_rate/PREF_QUAD_RATE); - audio_rr1 = make_resampler_ff(d_audio_rate/PREF_QUAD_RATE); + audio_rr0 = make_resampler_ff(d_audio_rate/NB_PREF_QUAD_RATE); + audio_rr1 = make_resampler_ff(d_audio_rate/NB_PREF_QUAD_RATE); } - demod = demod_fm; - connect(self(), 0, iq_resamp, 0); + demod = demod_raw; + connect(ddc, 0, iq_resamp, 0); connect(iq_resamp, 0, nb, 0); connect(nb, 0, filter, 0); connect(filter, 0, meter, 0); connect(filter, 0, sql, 0); - connect(sql, 0, demod, 0); -// connect(sql, 0, agc, 0); -// connect(agc, 0, demod, 0); - - if (audio_rr0) - { - connect(demod, 0, audio_rr0, 0); - - connect(audio_rr0, 0, agc, 0); // left channel - connect(audio_rr0, 0, agc, 1); // right channel - } - else - { - connect(demod, 0, agc, 0); - connect(demod, 0, agc, 1); - } connect(agc, 0, self(), 0); connect(agc, 1, self(), 1); } @@ -97,18 +78,51 @@ bool nbrx::stop() return true; } -void nbrx::set_filter(double low, double high, double tw) +void nbrx::set_filter(int low, int high, int tw) { - filter->set_param(low, high, tw); + receiver_base_cf::set_filter(low, high, tw); + filter->set_param(double(low), double(high), double(tw)); } -void nbrx::set_cw_offset(double offset) +void nbrx::set_cw_offset(int offset) { - filter->set_cw_offset(offset); + vfo_s::set_cw_offset(offset); + switch (get_demod()) + { + case Modulations::MODE_CWL: + ddc->set_center_freq(get_offset() + get_cw_offset()); + filter->set_cw_offset(-get_cw_offset()); + break; + case Modulations::MODE_CWU: + ddc->set_center_freq(get_offset() - get_cw_offset()); + filter->set_cw_offset(get_cw_offset()); + break; + default: + ddc->set_center_freq(get_offset()); + filter->set_cw_offset(0); + } +} + +void nbrx::set_offset(int offset) +{ + vfo_s::set_offset(offset); + switch (get_demod()) + { + case Modulations::MODE_CWL: + ddc->set_center_freq(offset + get_cw_offset()); + break; + case Modulations::MODE_CWU: + ddc->set_center_freq(offset - get_cw_offset()); + break; + default: + ddc->set_center_freq(offset); + } + wav_sink->set_offset(offset); } void nbrx::set_nb_on(int nbid, bool on) { + receiver_base_cf::set_nb_on(nbid, on); if (nbid == 1) nb->set_nb1_on(on); else if (nbid == 2) @@ -117,142 +131,173 @@ void nbrx::set_nb_on(int nbid, bool on) void nbrx::set_nb_threshold(int nbid, float threshold) { + receiver_base_cf::set_nb_threshold(nbid, threshold); if (nbid == 1) nb->set_threshold1(threshold); else if (nbid == 2) nb->set_threshold2(threshold); } -void nbrx::set_demod(int rx_demod) +void nbrx::set_demod(Modulations::idx new_demod) { - nbrx_demod current_demod = d_demod; + Modulations::idx current_demod = receiver_base_cf::get_demod(); - /* check if new demodulator selection is valid */ - if ((rx_demod < NBRX_DEMOD_NONE) || (rx_demod >= NBRX_DEMOD_NUM)) - return; - - if (rx_demod == current_demod) { + if (new_demod == current_demod) { /* nothing to do */ return; } - disconnect(sql, 0, demod, 0); - if (audio_rr0) - { - if (current_demod == NBRX_DEMOD_NONE) - { - disconnect(demod, 0, audio_rr0, 0); - disconnect(demod, 1, audio_rr1, 0); + /* check if new demodulator selection is valid */ + if ((new_demod < Modulations::MODE_OFF) || (new_demod > Modulations::MODE_NFM)) + return; - disconnect(audio_rr0, 0, agc, 0); - disconnect(audio_rr1, 0, agc, 1); - } - else - { - disconnect(demod, 0, audio_rr0, 0); - disconnect(audio_rr0, 0, agc, 0); - disconnect(audio_rr0, 0, agc, 1); - } - } - else + if (current_demod > Modulations::MODE_OFF) { - if (current_demod == NBRX_DEMOD_NONE) + disconnect(sql, 0, demod, 0); + if (audio_rr0) { - disconnect(demod, 0, agc, 0); - disconnect(demod, 1, agc, 1); + if (current_demod == Modulations::MODE_RAW) + { + disconnect(demod, 0, audio_rr0, 0); + disconnect(demod, 1, audio_rr1, 0); + + disconnect(audio_rr0, 0, agc, 0); + disconnect(audio_rr1, 0, agc, 1); + } + else + { + disconnect(demod, 0, audio_rr0, 0); + + disconnect(audio_rr0, 0, agc, 0); + disconnect(audio_rr0, 0, agc, 1); + } } else { - disconnect(demod, 0, agc, 0); - disconnect(demod, 0, agc, 1); + if (current_demod == Modulations::MODE_RAW) + { + disconnect(demod, 0, agc, 0); + disconnect(demod, 1, agc, 1); + } + else + { + disconnect(demod, 0, agc, 0); + disconnect(demod, 0, agc, 1); + } } } - switch (rx_demod) { + switch (new_demod) { - case NBRX_DEMOD_NONE: - d_demod = NBRX_DEMOD_NONE; + case Modulations::MODE_RAW: + case Modulations::MODE_OFF: demod = demod_raw; break; - case NBRX_DEMOD_SSB: - d_demod = NBRX_DEMOD_SSB; + case Modulations::MODE_LSB: + case Modulations::MODE_USB: + case Modulations::MODE_CWL: + case Modulations::MODE_CWU: demod = demod_ssb; break; - case NBRX_DEMOD_AM: - d_demod = NBRX_DEMOD_AM; + case Modulations::MODE_AM: demod = demod_am; break; - case NBRX_DEMOD_AMSYNC: - d_demod = NBRX_DEMOD_AMSYNC; + case Modulations::MODE_AM_SYNC: demod = demod_amsync; break; - case NBRX_DEMOD_FM: + case Modulations::MODE_NFM: default: - d_demod = NBRX_DEMOD_FM; demod = demod_fm; break; } - connect(sql, 0, demod, 0); - if (audio_rr0) + if (new_demod > Modulations::MODE_OFF) { - if (d_demod == NBRX_DEMOD_NONE) + connect(sql, 0, demod, 0); + if (audio_rr0) { - connect(demod, 0, audio_rr0, 0); - connect(demod, 1, audio_rr1, 0); - - connect(audio_rr0, 0, agc, 0); - connect(audio_rr1, 0, agc, 1); + if (new_demod == Modulations::MODE_RAW) + { + connect(demod, 0, audio_rr0, 0); + connect(demod, 1, audio_rr1, 0); + + connect(audio_rr0, 0, agc, 0); + connect(audio_rr1, 0, agc, 1); + } + else + { + connect(demod, 0, audio_rr0, 0); + + connect(audio_rr0, 0, agc, 0); + connect(audio_rr0, 0, agc, 1); + } } else { - connect(demod, 0, audio_rr0, 0); - - connect(audio_rr0, 0, agc, 0); - connect(audio_rr0, 0, agc, 1); + if (new_demod == Modulations::MODE_RAW) + { + connect(demod, 0, agc, 0); + connect(demod, 1, agc, 1); + } + else + { + connect(demod, 0, agc, 0); + connect(demod, 0, agc, 1); + } } } - else + receiver_base_cf::set_demod(new_demod); + switch (get_demod()) { - if (d_demod == NBRX_DEMOD_NONE) - { - connect(demod, 0, agc, 0); - connect(demod, 1, agc, 1); - } - else - { - connect(demod, 0, agc, 0); - connect(demod, 0, agc, 1); - } + case Modulations::MODE_CWL: + ddc->set_center_freq(get_offset() + get_cw_offset()); + filter->set_cw_offset(-get_cw_offset()); + break; + case Modulations::MODE_CWU: + ddc->set_center_freq(get_offset() - get_cw_offset()); + filter->set_cw_offset(get_cw_offset()); + break; + default: + ddc->set_center_freq(get_offset()); + filter->set_cw_offset(0); } } void nbrx::set_fm_maxdev(float maxdev_hz) { + receiver_base_cf::set_fm_maxdev(maxdev_hz); demod_fm->set_max_dev(maxdev_hz); } void nbrx::set_fm_deemph(double tau) { + receiver_base_cf::set_fm_deemph(tau); demod_fm->set_tau(tau); } void nbrx::set_am_dcr(bool enabled) { + receiver_base_cf::set_am_dcr(enabled); + lock(); demod_am->set_dcr(enabled); + unlock(); } void nbrx::set_amsync_dcr(bool enabled) { + receiver_base_cf::set_amsync_dcr(enabled); + lock(); demod_amsync->set_dcr(enabled); + unlock(); } void nbrx::set_amsync_pll_bw(float pll_bw) { + receiver_base_cf::set_amsync_pll_bw(pll_bw); demod_amsync->set_pll_bw(pll_bw); } diff --git a/src/receivers/nbrx.h b/src/receivers/nbrx.h index 55e0dd97b3..03a9f8ebd5 100644 --- a/src/receivers/nbrx.h +++ b/src/receivers/nbrx.h @@ -50,59 +50,38 @@ nbrx_sptr make_nbrx(float quad_rate, float audio_rate); */ class nbrx : public receiver_base_cf { -public: - /*! \brief Available demodulators. */ - enum nbrx_demod { - NBRX_DEMOD_NONE = 0, /*!< No demod. Raw I/Q to audio. */ - NBRX_DEMOD_AM = 1, /*!< Amplitude modulation. */ - NBRX_DEMOD_FM = 2, /*!< Frequency modulation. */ - NBRX_DEMOD_SSB = 3, /*!< Single Side Band. */ - NBRX_DEMOD_AMSYNC = 4, /*!< Amplitude modulation (synchronous demod). */ - NBRX_DEMOD_NUM = 5 /*!< Included for convenience. */ - }; - public: nbrx(float quad_rate, float audio_rate); virtual ~nbrx() { }; - bool start(); - bool stop(); + bool start() override; + bool stop() override; - void set_filter(double low, double high, double tw); - void set_cw_offset(double offset); + void set_filter(int low, int high, int tw) override; + void set_offset(int offset) override; + void set_cw_offset(int offset) override; /* Noise blanker */ - bool has_nb() { return true; } - void set_nb_on(int nbid, bool on); - void set_nb_threshold(int nbid, float threshold); - - /* Squelch parameter */ - bool has_sql() { return true; } + bool has_nb() override { return true; } + void set_nb_on(int nbid, bool on) override; + void set_nb_threshold(int nbid, float threshold) override; - /* AGC */ - bool has_agc() { return true; } - - void set_demod(int demod); + void set_demod(Modulations::idx new_demod) override; /* FM parameters */ - bool has_fm() { return true; } - void set_fm_maxdev(float maxdev_hz); - void set_fm_deemph(double tau); + void set_fm_maxdev(float maxdev_hz) override; + void set_fm_deemph(double tau) override; /* AM parameters */ - bool has_am() { return true; } - void set_am_dcr(bool enabled); + void set_am_dcr(bool enabled) override; /* AM-Sync parameters */ - bool has_amsync() { return true; } - void set_amsync_dcr(bool enabled); - void set_amsync_pll_bw(float pll_bw); + void set_amsync_dcr(bool enabled) override; + void set_amsync_pll_bw(float pll_bw) override; private: bool d_running; /*!< Whether receiver is running or not. */ - nbrx_demod d_demod; /*!< Current demodulator. */ - rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_nb_cc_sptr nb; /*!< Noise blanker. */ diff --git a/src/receivers/receiver_base.cpp b/src/receivers/receiver_base.cpp index 7d222e503f..53a4c33fee 100644 --- a/src/receivers/receiver_base.cpp +++ b/src/receivers/receiver_base.cpp @@ -27,23 +27,34 @@ #include #include - static const int MIN_IN = 1; /* Minimum number of input streams. */ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 2; /* Minimum number of output streams. */ static const int MAX_OUT = 2; /* Maximum number of output streams. */ -receiver_base_cf::receiver_base_cf(std::string src_name, float pref_quad_rate, float quad_rate, int audio_rate) +receiver_base_cf::receiver_base_cf(std::string src_name, float pref_quad_rate, double quad_rate, int audio_rate) : gr::hier_block2 (src_name, gr::io_signature::make (MIN_IN, MAX_IN, sizeof(gr_complex)), gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(float))), - d_quad_rate(quad_rate), + vfo_s(), + d_connected(false), + d_decim_rate(quad_rate), + d_quad_rate(0), + d_ddc_decim(1), d_audio_rate(audio_rate), + d_center_freq(145500000.0), d_pref_quad_rate(pref_quad_rate) { + d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); + d_quad_rate = d_decim_rate / d_ddc_decim; + ddc = make_downconverter_cc(d_ddc_decim, 0.0, d_decim_rate); + connect(self(), 0, ddc, 0); + iq_resamp = make_resampler_cc(d_pref_quad_rate/d_quad_rate); - agc = make_rx_agc_2f(d_audio_rate, false, 0, 0, 100, 500, 500, 0); - sql = make_rx_sql_cc(-150.0, 0.001); + agc = make_rx_agc_2f(d_audio_rate, d_agc_on, d_agc_target_level, + d_agc_manual_gain, d_agc_max_gain, d_agc_attack_ms, + d_agc_decay_ms, d_agc_hang_ms, d_agc_panning); + sql = make_rx_sql_cc(d_level_db, d_alpha); meter = make_rx_meter_c(d_pref_quad_rate); wav_sink = wavfile_sink_gqrx::make(0, 2, (unsigned int) d_audio_rate, wavfile_sink_gqrx::FORMAT_WAV, @@ -57,20 +68,40 @@ receiver_base_cf::receiver_base_cf(std::string src_name, float pref_quad_rate, f receiver_base_cf::~receiver_base_cf() { //Prevent segfault - if(wav_sink) + if (wav_sink) wav_sink->set_rec_event_handler(nullptr); } -void receiver_base_cf::set_quad_rate(float quad_rate) +void receiver_base_cf::set_demod(Modulations::idx demod) { - if (std::abs(d_quad_rate-quad_rate) > 0.5) + if ((get_demod() == Modulations::MODE_OFF) && (demod != Modulations::MODE_OFF)) { - qDebug() << "Changing RX quad rate:" << d_quad_rate << "->" << quad_rate; - d_quad_rate = quad_rate; + qDebug() << "Changing RX quad rate:" << d_decim_rate << "->" << d_quad_rate; lock(); + ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); iq_resamp->set_rate(d_pref_quad_rate/d_quad_rate); unlock(); } + vfo_s::set_demod(demod); +} + +void receiver_base_cf::set_quad_rate(double quad_rate) +{ + if (std::abs(d_decim_rate-quad_rate) > 0.5) + { + d_decim_rate = quad_rate; + d_ddc_decim = std::max(1, (int)(d_decim_rate / TARGET_QUAD_RATE)); + d_quad_rate = d_decim_rate / d_ddc_decim; + //avoid triggering https://github.com/gnuradio/gnuradio/issues/5436 + if (get_demod() != Modulations::MODE_OFF) + { + qDebug() << "Changing RX quad rate:" << d_decim_rate << "->" << d_quad_rate; + lock(); + ddc->set_decim_and_samp_rate(d_ddc_decim, d_decim_rate); + iq_resamp->set_rate(d_pref_quad_rate/d_quad_rate); + unlock(); + } + } } void receiver_base_cf::set_center_freq(double center_freq) @@ -79,31 +110,40 @@ void receiver_base_cf::set_center_freq(double center_freq) wav_sink->set_center_freq(center_freq); } -void receiver_base_cf::set_offset(double offset) +void receiver_base_cf::set_offset(int offset) { - d_offset = offset; + vfo_s::set_offset(offset); + ddc->set_center_freq(offset); wav_sink->set_offset(offset); } -void receiver_base_cf::set_rec_dir(std::string dir) +void receiver_base_cf::set_cw_offset(int offset) { - d_rec_dir = dir; + vfo_s::set_cw_offset(offset); +} + +void receiver_base_cf::set_audio_rec_dir(const std::string& dir) +{ + vfo_s::set_audio_rec_dir(dir); wav_sink->set_rec_dir(dir); } void receiver_base_cf::set_audio_rec_sql_triggered(bool enabled) { + vfo_s::set_audio_rec_sql_triggered(enabled); sql->set_impl(enabled ? rx_sql_cc::SQL_PWR : rx_sql_cc::SQL_SIMPLE); wav_sink->set_sql_triggered(enabled); } void receiver_base_cf::set_audio_rec_min_time(const int time_ms) { + vfo_s::set_audio_rec_min_time(time_ms); wav_sink->set_rec_min_time(time_ms); } void receiver_base_cf::set_audio_rec_max_gap(const int time_ms) { + vfo_s::set_audio_rec_max_gap(time_ms); wav_sink->set_rec_max_gap(time_ms); } @@ -117,116 +157,69 @@ bool receiver_base_cf::has_nb() return false; } -void receiver_base_cf::set_nb_on(int nbid, bool on) -{ - (void) nbid; - (void) on; -} - -void receiver_base_cf::set_nb_threshold(int nbid, float threshold) -{ - (void) nbid; - (void) threshold; -} - -bool receiver_base_cf::has_sql() -{ - return false; -} - void receiver_base_cf::set_sql_level(double level_db) { sql->set_threshold(level_db); + vfo_s::set_sql_level(level_db); } void receiver_base_cf::set_sql_alpha(double alpha) { sql->set_alpha(alpha); -} - -bool receiver_base_cf::has_agc() -{ - return false; + vfo_s::set_sql_alpha(alpha); } void receiver_base_cf::set_agc_on(bool agc_on) { agc->set_agc_on(agc_on); + vfo_s::set_agc_on(agc_on); } void receiver_base_cf::set_agc_target_level(int target_level) { agc->set_target_level(target_level); + vfo_s::set_agc_target_level(target_level); } void receiver_base_cf::set_agc_manual_gain(float gain) { agc->set_manual_gain(gain); + vfo_s::set_agc_manual_gain(gain); } void receiver_base_cf::set_agc_max_gain(int gain) { agc->set_max_gain(gain); + vfo_s::set_agc_max_gain(gain); } void receiver_base_cf::set_agc_attack(int attack_ms) { agc->set_attack(attack_ms); + vfo_s::set_agc_attack(attack_ms); } void receiver_base_cf::set_agc_decay(int decay_ms) { agc->set_decay(decay_ms); + vfo_s::set_agc_decay(decay_ms); } void receiver_base_cf::set_agc_hang(int hang_ms) { agc->set_hang(hang_ms); + vfo_s::set_agc_hang(hang_ms); } -float receiver_base_cf::get_agc_gain() -{ - return agc->get_current_gain(); -} - -bool receiver_base_cf::has_fm() -{ - return false; -} - -void receiver_base_cf::set_fm_maxdev(float maxdev_hz) -{ - (void) maxdev_hz; -} - -void receiver_base_cf::set_fm_deemph(double tau) -{ - (void) tau; -} - -bool receiver_base_cf::has_am() -{ - return false; -} - -void receiver_base_cf::set_am_dcr(bool enabled) -{ - (void) enabled; -} - -bool receiver_base_cf::has_amsync() -{ - return false; -} - -void receiver_base_cf::set_amsync_dcr(bool enabled) +void receiver_base_cf::set_agc_panning(int panning) { - (void) enabled; + agc->set_panning(panning); + vfo_s::set_agc_panning(panning); } -void receiver_base_cf::set_amsync_pll_bw(float pll_bw) +float receiver_base_cf::get_agc_gain() { - (void) pll_bw; + return agc->get_current_gain(); } void receiver_base_cf::get_rds_data(std::string &outbuff, int &num) @@ -257,6 +250,11 @@ int receiver_base_cf::start_audio_recording() return wav_sink->open_new(); } +bool receiver_base_cf::get_audio_recording() +{ + return wav_sink->is_active(); +} + void receiver_base_cf::stop_audio_recording() { wav_sink->close(); @@ -265,7 +263,7 @@ void receiver_base_cf::stop_audio_recording() //FIXME Reimplement wavfile_sink correctly to make this work as expected void receiver_base_cf::continue_audio_recording(receiver_base_cf_sptr from) { - if(from.get() == this) + if (from.get() == this) return; from->disconnect(from->agc, 0, from->wav_sink, 0); from->disconnect(from->agc, 1, from->wav_sink, 1); @@ -285,6 +283,16 @@ std::string receiver_base_cf::get_last_audio_filename() void receiver_base_cf::rec_event(receiver_base_cf * self, std::string filename, bool is_running) { self->d_audio_filename = filename; - if(self->d_rec_event) - self->d_rec_event(filename, is_running); + if (self->d_rec_event) + { + self->d_rec_event(self->get_index(), filename, is_running); + std::cerr<<"d_rec_event("<get_index()<<","< receiver_base_cf_sptr; #else @@ -48,75 +65,54 @@ typedef std::shared_ptr receiver_base_cf_sptr; * output audio (or other kind of float data). * */ -class receiver_base_cf : public gr::hier_block2 +class receiver_base_cf : public gr::hier_block2, public vfo_s { public: /*! \brief Public constructor. * \param src_name Descriptive name used in the constructor of gr::hier_block2 */ - typedef std::function rec_event_handler_t; - receiver_base_cf(std::string src_name, float pref_quad_rate, float quad_rate, int audio_rate); + typedef std::function rec_event_handler_t; + receiver_base_cf(std::string src_name, float pref_quad_rate, double quad_rate, int audio_rate); virtual ~receiver_base_cf(); virtual bool start() = 0; virtual bool stop() = 0; - virtual void set_quad_rate(float quad_rate); + virtual void set_quad_rate(double quad_rate); virtual void set_center_freq(double center_freq); - virtual void set_offset(double offset); - virtual void set_rec_dir(std::string dir); - virtual std::string get_rec_dir() { return d_rec_dir; } - virtual void set_audio_rec_sql_triggered(bool enabled); - virtual bool get_audio_rec_sql_triggered() { return wav_sink->get_sql_triggered(); } - virtual void set_audio_rec_min_time(const int time_ms); - virtual int get_audio_rec_min_time() { return wav_sink->get_min_time(); } - virtual void set_audio_rec_max_gap(const int time_ms); - virtual int get_audio_rec_max_gap() { return wav_sink->get_max_gap(); } - - virtual void set_filter(double low, double high, double tw) = 0; - virtual void set_cw_offset(double offset) = 0; + void set_offset(int offset) override; - virtual float get_signal_level(); + /* Audio recording */ + void set_audio_rec_dir(const std::string& dir) override; + void set_audio_rec_sql_triggered(bool enabled) override; + void set_audio_rec_min_time(const int time_ms) override; + void set_audio_rec_max_gap(const int time_ms) override; - virtual void set_demod(int demod) = 0; + virtual float get_signal_level(); - /* the rest is optional */ + void set_demod(Modulations::idx demod) override; /* Noise blanker */ virtual bool has_nb(); - virtual void set_nb_on(int nbid, bool on); - virtual void set_nb_threshold(int nbid, float threshold); /* Squelch parameter */ - virtual bool has_sql(); - virtual void set_sql_level(double level_db); - virtual void set_sql_alpha(double alpha); + void set_sql_level(double level_db) override; + void set_sql_alpha(double alpha) override; /* AGC */ - virtual bool has_agc(); - virtual void set_agc_on(bool agc_on); - virtual void set_agc_target_level(int target_level); - virtual void set_agc_manual_gain(float gain); - virtual void set_agc_max_gain(int gain); - virtual void set_agc_attack(int attack_ms); - virtual void set_agc_decay(int decay_ms); - virtual void set_agc_hang(int hang_ms); + void set_agc_on(bool agc_on) override; + void set_agc_target_level(int target_level) override; + void set_agc_manual_gain(float gain) override; + void set_agc_max_gain(int gain) override; + void set_agc_attack(int attack_ms) override; + void set_agc_decay(int decay_ms) override; + void set_agc_hang(int hang_ms) override; + void set_agc_panning(int panning) override; virtual float get_agc_gain(); - /* FM parameters */ - virtual bool has_fm(); - virtual void set_fm_maxdev(float maxdev_hz); - virtual void set_fm_deemph(double tau); - - /* AM parameters */ - virtual bool has_am(); - virtual void set_am_dcr(bool enabled); - - /* AM-Sync parameters */ - virtual bool has_amsync(); - virtual void set_amsync_dcr(bool enabled); - virtual void set_amsync_pll_bw(float pll_bw); + /* CW parameters */ + void set_cw_offset(int offset) override; virtual void get_rds_data(std::string &outbuff, int &num); virtual void start_rds_decoder(); @@ -126,29 +122,37 @@ class receiver_base_cf : public gr::hier_block2 virtual int start_audio_recording(); virtual void stop_audio_recording(); virtual void continue_audio_recording(receiver_base_cf_sptr from); + virtual bool get_audio_recording(); virtual std::string get_last_audio_filename(); template void set_rec_event_handler(T handler) { d_rec_event = handler; } + using vfo_s::restore_settings; + virtual void restore_settings(receiver_base_cf& from); + bool connected() { return d_connected; } + void connected(bool value) { d_connected = value; } protected: - float d_quad_rate; /*!< Input sample rate. */ - int d_audio_rate; /*!< Audio output rate. */ - double d_center_freq; - double d_offset; - std::string d_rec_dir; - std::string d_audio_filename; - + bool d_connected; + double d_decim_rate; /*!< Quadrature rate (before down-conversion) */ + double d_quad_rate; /*!< Quadrature rate (after down-conversion) */ + unsigned int d_ddc_decim; /*!< Down-conversion decimation. */ + int d_audio_rate; /*!< Audio output rate. */ + double d_center_freq; + std::string d_audio_filename; + + downconverter_cc_sptr ddc; /*!< Digital down-converter for demod chain. */ resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_meter_c_sptr meter; /*!< Signal strength. */ rx_agc_2f_sptr agc; /*!< Receiver AGC. */ rx_sql_cc_sptr sql; /*!< Squelch. */ - wavfile_sink_gqrx::sptr wav_sink; /*!< WAV file sink for recording. */ + wavfile_sink_gqrx::sptr wav_sink; /*!< WAV file sink for recording. */ private: float d_pref_quad_rate; rec_event_handler_t d_rec_event; static void rec_event(receiver_base_cf * self, std::string filename, bool is_running); + }; #endif // RECEIVER_BASE_H diff --git a/src/receivers/vfo.cpp b/src/receivers/vfo.cpp new file mode 100644 index 0000000000..2994ea16d4 --- /dev/null +++ b/src/receivers/vfo.cpp @@ -0,0 +1,244 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2022 vladisslav2011@gmail.com. + * + * Gqrx is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gqrx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#include "vfo.h" + +void vfo_s::set_offset(int offset) +{ + d_offset = offset; +} + +void vfo_s::set_filter_low(int low) +{ + d_filter_low = low; +} + +void vfo_s::set_filter_high(int high) +{ + d_filter_high = high; +} + +void vfo_s::set_filter_tw(int tw) +{ + d_filter_tw = tw; +} + +void vfo_s::set_filter(int low, int high, int tw) +{ + d_filter_low = low; + d_filter_high = high; + d_filter_tw = tw; +} + +void vfo_s::filter_adjust() +{ + Modulations::UpdateFilterRange(d_demod, d_filter_low, d_filter_high); + Modulations::UpdateTw(d_filter_low, d_filter_high, d_filter_tw); +} + +void vfo_s::set_demod(Modulations::idx demod) +{ + d_demod = demod; +} + +void vfo_s::set_index(int index) +{ + d_index = index; +} + +void vfo_s::set_freq_lock(bool on) +{ + d_locked = on; +} + +void vfo_s::set_sql_level(double level_db) +{ + d_level_db = level_db; +} + +void vfo_s::set_sql_alpha(double alpha) +{ + d_alpha = alpha; +} + +void vfo_s::set_agc_on(bool agc_on) +{ + d_agc_on = agc_on; +} + +void vfo_s::set_agc_target_level(int target_level) +{ + d_agc_target_level = target_level; +} + +void vfo_s::set_agc_manual_gain(float gain) +{ + d_agc_manual_gain = gain; +} + +void vfo_s::set_agc_max_gain(int gain) +{ + d_agc_max_gain = gain; +} + +void vfo_s::set_agc_attack(int attack_ms) +{ + d_agc_attack_ms = attack_ms; +} + +void vfo_s::set_agc_decay(int decay_ms) +{ + d_agc_decay_ms = decay_ms; +} + +void vfo_s::set_agc_hang(int hang_ms) +{ + d_agc_hang_ms = hang_ms; +} + +void vfo_s::set_agc_panning(int panning) +{ + d_agc_panning = panning; +} + +void vfo_s::set_agc_panning_auto(bool mode) +{ + d_agc_panning_auto = mode; +} + +void vfo_s::set_cw_offset(int offset) +{ + d_cw_offset = offset; +} + +void vfo_s::set_fm_maxdev(float maxdev_hz) +{ + d_fm_maxdev = maxdev_hz; +} + +void vfo_s::set_fm_deemph(double tau) +{ + d_fm_deemph = tau; +} + +void vfo_s::set_am_dcr(bool enabled) +{ + d_am_dcr = enabled; +} + +void vfo_s::set_amsync_dcr(bool enabled) +{ + d_amsync_dcr = enabled; +} + +void vfo_s::set_amsync_pll_bw(float pll_bw) +{ + d_amsync_pll_bw = pll_bw; +} + +void vfo_s::set_nb_on(int nbid, bool on) +{ + if (nbid - 1 < RECEIVER_NB_COUNT) + d_nb_on[nbid - 1] = on; +} + +bool vfo_s::get_nb_on(int nbid) +{ + if (nbid - 1 < RECEIVER_NB_COUNT) + return d_nb_on[nbid - 1]; + return false; +} + +void vfo_s::set_nb_threshold(int nbid, float threshold) +{ + if (nbid - 1 < RECEIVER_NB_COUNT) + d_nb_threshold[nbid - 1] = threshold; +} + +float vfo_s::get_nb_threshold(int nbid) +{ + if (nbid - 1 < RECEIVER_NB_COUNT) + return d_nb_threshold[nbid - 1]; + return 0.0; +} + +void vfo_s::set_audio_rec_dir(const std::string& dir) +{ + d_rec_dir = dir; +} + +void vfo_s::set_audio_rec_sql_triggered(bool enabled) +{ + d_rec_sql_triggered = enabled; +} + +void vfo_s::set_audio_rec_min_time(const int time_ms) +{ + d_rec_min_time = time_ms; +} + +void vfo_s::set_audio_rec_max_gap(const int time_ms) +{ + d_rec_max_gap = time_ms; +} + +void vfo_s::restore_settings(vfo_s& from, bool force) +{ + set_offset(from.get_offset()); + set_filter(from.get_filter_low(), from.get_filter_high(), from.get_filter_tw()); + set_freq_lock(from.get_freq_lock()); + set_demod(from.get_demod()); + set_sql_level(from.get_sql_level()); + set_sql_alpha(from.get_sql_alpha()); + + set_agc_on(from.get_agc_on()); + set_agc_target_level(from.get_agc_target_level()); + set_agc_manual_gain(from.get_agc_manual_gain()); + set_agc_max_gain(from.get_agc_max_gain()); + set_agc_attack(from.get_agc_attack()); + set_agc_decay(from.get_agc_decay()); + set_agc_hang(from.get_agc_hang()); + set_agc_panning(from.get_agc_panning()); + set_agc_panning_auto(from.get_agc_panning_auto()); + + set_cw_offset(from.get_cw_offset()); + + set_fm_maxdev(from.get_fm_maxdev()); + set_fm_deemph(from.get_fm_deemph()); + + set_am_dcr(from.get_am_dcr()); + set_amsync_dcr(from.get_amsync_dcr()); + set_amsync_pll_bw(from.get_amsync_pll_bw()); + + for (int k = 0; k < RECEIVER_NB_COUNT; k++) + { + set_nb_on(k + 1, from.get_nb_on(k + 1)); + set_nb_threshold(k + 1, from.get_nb_threshold(k + 1)); + } + if (force || (from.get_audio_rec_dir() != "")) + set_audio_rec_dir(from.get_audio_rec_dir()); + if (force || (from.get_audio_rec_min_time() > 0)) + set_audio_rec_min_time(from.get_audio_rec_min_time()); + if (force || (from.get_audio_rec_max_gap() > 0)) + set_audio_rec_max_gap(from.get_audio_rec_max_gap()); + set_audio_rec_sql_triggered(from.get_audio_rec_sql_triggered()); +} \ No newline at end of file diff --git a/src/receivers/vfo.h b/src/receivers/vfo.h new file mode 100644 index 0000000000..71c7ab2724 --- /dev/null +++ b/src/receivers/vfo.h @@ -0,0 +1,236 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2022 vladisslav2011@gmail.com. + * + * Gqrx is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * Gqrx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Gqrx; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef VFO_H +#define VFO_H + +#if GNURADIO_VERSION < 0x030900 + #include +#endif + +#include "receivers/defines.h" +#include "receivers/modulations.h" + + +class vfo_s; +typedef class vfo_s +{ + public: +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + static sptr make() + { + return sptr(new vfo_s()); + } + + vfo_s(): + d_offset(0), + d_filter_low(-5000), + d_filter_high(5000), + d_filter_tw(100), + d_demod(Modulations::MODE_OFF), + d_index(-1), + d_locked(false), + d_level_db(-150), + d_alpha(0.001), + d_agc_on(true), + d_agc_target_level(0), + d_agc_manual_gain(0), + d_agc_max_gain(100), + d_agc_attack_ms(20), + d_agc_decay_ms(500), + d_agc_hang_ms(0), + d_agc_panning(0), + d_agc_panning_auto(false), + d_cw_offset(700), + d_fm_maxdev(2500), + d_fm_deemph(7.5e-5), + d_am_dcr(true), + d_amsync_dcr(true), + d_amsync_pll_bw(0.01), + d_rec_dir(""), + d_rec_sql_triggered(false), + d_rec_min_time(0), + d_rec_max_gap(0) + { + for (int k = 0; k < RECEIVER_NB_COUNT; k++) + { + d_nb_on[k] = false; + d_nb_threshold[k] = 2; + } + } + + virtual ~vfo_s() + { + } + + struct comp + { + inline bool operator()(const sptr lhs, const sptr rhs) const + { + const vfo_s *a = lhs.get(); + const vfo_s *b = rhs.get(); + return (a->d_offset == b->d_offset) ? (a->d_index < b->d_index) : (a->d_offset < b->d_offset); + } + }; + typedef std::set set; + //getters + inline int get_offset() const { return d_offset; } + /* Filter parameter */ + inline int get_filter_low() const { return d_filter_low;} + inline int get_filter_high() const { return d_filter_high;} + inline int get_filter_tw() const { return d_filter_tw; } + inline void get_filter(int &low, int &high, int &tw) const + { + low = d_filter_low; + high = d_filter_high; + tw = d_filter_tw; + } + inline Modulations::idx get_demod() const { return d_demod; } + inline int get_index() const { return d_index; } + inline bool get_freq_lock() const { return d_locked; } + /* Squelch parameter */ + inline double get_sql_level() const { return d_level_db; } + inline double get_sql_alpha() const { return d_alpha; } + /* AGC */ + inline bool get_agc_on() const { return d_agc_on; } + inline int get_agc_target_level() const { return d_agc_target_level; } + inline float get_agc_manual_gain() const { return d_agc_manual_gain; } + inline int get_agc_max_gain() const { return d_agc_max_gain; } + inline int get_agc_attack() const { return d_agc_attack_ms; } + inline int get_agc_decay() const { return d_agc_decay_ms; } + inline int get_agc_hang() const { return d_agc_hang_ms; } + inline int get_agc_panning() const { return d_agc_panning; } + inline bool get_agc_panning_auto() const { return d_agc_panning_auto; } + /* CW parameters */ + inline int get_cw_offset() const { return d_cw_offset; } + /* FM parameters */ + inline float get_fm_maxdev() const { return d_fm_maxdev; } + inline double get_fm_deemph() const { return d_fm_deemph; } + /* AM parameters */ + inline bool get_am_dcr() const { return d_am_dcr; } + + /* AM-Sync parameters */ + inline bool get_amsync_dcr() const { return d_amsync_dcr; } + inline float get_amsync_pll_bw() const { return d_amsync_pll_bw; } + /* Noise blanker */ + bool get_nb_on(int nbid); + float get_nb_threshold(int nbid); + /* Audio recorder */ + inline const std::string& get_audio_rec_dir() const { return d_rec_dir; } + inline bool get_audio_rec_sql_triggered() const { return d_rec_sql_triggered; } + inline int get_audio_rec_min_time() const { return d_rec_min_time; } + inline int get_audio_rec_max_gap() const { return d_rec_max_gap; } + + //setters + virtual void set_offset(int offset); + /* Filter parameter */ + virtual void set_filter_low(int low); + virtual void set_filter_high(int high); + virtual void set_filter_tw(int tw); + virtual void set_filter(int low, int high, int tw); + virtual void filter_adjust(); + + virtual void set_demod(Modulations::idx demod); + virtual void set_index(int index); + virtual void set_freq_lock(bool on); + /* Squelch parameter */ + virtual void set_sql_level(double level_db); + virtual void set_sql_alpha(double alpha); + /* AGC */ + virtual void set_agc_on(bool agc_on); + virtual void set_agc_target_level(int target_level); + virtual void set_agc_manual_gain(float gain); + virtual void set_agc_max_gain(int gain); + virtual void set_agc_attack(int attack_ms); + virtual void set_agc_decay(int decay_ms); + virtual void set_agc_hang(int hang_ms); + virtual void set_agc_panning(int panning); + virtual void set_agc_panning_auto(bool mode); + /* CW parameters */ + virtual void set_cw_offset(int offset); + /* FM parameters */ + virtual void set_fm_maxdev(float maxdev_hz); + virtual void set_fm_deemph(double tau); + /* AM parameters */ + virtual void set_am_dcr(bool enabled); + /* AM-Sync parameters */ + virtual void set_amsync_dcr(bool enabled); + virtual void set_amsync_pll_bw(float pll_bw); + /* Noise blanker */ + virtual void set_nb_on(int nbid, bool on); + virtual void set_nb_threshold(int nbid, float threshold); + /* Audio recorder */ + virtual void set_audio_rec_dir(const std::string& dir); + virtual void set_audio_rec_sql_triggered(bool enabled); + virtual void set_audio_rec_min_time(const int time_ms); + virtual void set_audio_rec_max_gap(const int time_ms); + + virtual void restore_settings(vfo_s& from, bool force = true); + + protected: + int d_offset; + int d_filter_low; + int d_filter_high; + int d_filter_tw; + Modulations::idx d_demod; + int d_index; + bool d_locked; + + double d_level_db; + double d_alpha; + + bool d_agc_on; + int d_agc_target_level; + float d_agc_manual_gain; + int d_agc_max_gain; + int d_agc_attack_ms; + int d_agc_decay_ms; + int d_agc_hang_ms; + int d_agc_panning; + int d_agc_panning_auto; + + int d_cw_offset; /*!< CW offset */ + + float d_fm_maxdev; + double d_fm_deemph; + + bool d_am_dcr; + bool d_amsync_dcr; + float d_amsync_pll_bw; + + bool d_nb_on[RECEIVER_NB_COUNT]; + float d_nb_threshold[RECEIVER_NB_COUNT]; + + std::string d_rec_dir; + bool d_rec_sql_triggered; + int d_rec_min_time; + int d_rec_max_gap; + + +} vfo; + + +#endif //VFO_H \ No newline at end of file diff --git a/src/receivers/wfmrx.cpp b/src/receivers/wfmrx.cpp index b35aa78089..09b53f3178 100644 --- a/src/receivers/wfmrx.cpp +++ b/src/receivers/wfmrx.cpp @@ -26,33 +26,30 @@ #include #include "receivers/wfmrx.h" -#define PREF_QUAD_RATE 240e3 // Nominal channel spacing is 200 kHz - wfmrx_sptr make_wfmrx(float quad_rate, float audio_rate) { return gnuradio::get_initial_sptr(new wfmrx(quad_rate, audio_rate)); } wfmrx::wfmrx(float quad_rate, float audio_rate) - : receiver_base_cf("WFMRX", PREF_QUAD_RATE, quad_rate, audio_rate), - d_running(false), - d_demod(WFMRX_DEMOD_MONO) + : receiver_base_cf("WFMRX", WFM_PREF_QUAD_RATE, quad_rate, audio_rate), + d_running(false) { - filter = make_rx_filter(PREF_QUAD_RATE, -80000.0, 80000.0, 20000.0); - demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 75000.0, 0.0); - stereo = make_stereo_demod(PREF_QUAD_RATE, d_audio_rate, true); - stereo_oirt = make_stereo_demod(PREF_QUAD_RATE, d_audio_rate, true, true); - mono = make_stereo_demod(PREF_QUAD_RATE, d_audio_rate, false); + filter = make_rx_filter(WFM_PREF_QUAD_RATE, -80000.0, 80000.0, 20000.0); + demod_fm = make_rx_demod_fm(WFM_PREF_QUAD_RATE, 75000.0, 0.0); + stereo = make_stereo_demod(WFM_PREF_QUAD_RATE, d_audio_rate, true); + stereo_oirt = make_stereo_demod(WFM_PREF_QUAD_RATE, d_audio_rate, true, true); + mono = make_stereo_demod(WFM_PREF_QUAD_RATE, d_audio_rate, false); /* create rds blocks but dont connect them */ - rds = make_rx_rds(PREF_QUAD_RATE); + rds = make_rx_rds(WFM_PREF_QUAD_RATE); rds_decoder = gr::rds::decoder::make(0, 0); rds_parser = gr::rds::parser::make(0, 0, 0); rds_store = make_rx_rds_store(); rds_enabled = false; - connect(self(), 0, iq_resamp, 0); + connect(ddc, 0, iq_resamp, 0); connect(iq_resamp, 0, filter, 0); connect(filter, 0, meter, 0); connect(filter, 0, sql, 0); @@ -83,18 +80,19 @@ bool wfmrx::stop() return true; } -void wfmrx::set_filter(double low, double high, double tw) +void wfmrx::set_filter(int low, int high, int tw) { - filter->set_param(low, high, tw); + receiver_base_cf::set_filter(low, high, tw); + filter->set_param(double(low), double(high), double(tw)); } -void wfmrx::set_demod(int demod) +void wfmrx::set_demod(Modulations::idx demod) { /* check if new demodulator selection is valid */ - if ((demod < WFMRX_DEMOD_MONO) || (demod >= WFMRX_DEMOD_NUM)) + if ((demod < Modulations::MODE_WFM_MONO) || (demod > Modulations::MODE_WFM_STEREO_OIRT)) return; - if (demod == d_demod) { + if (demod == receiver_base_cf::get_demod()) { /* nothing to do */ return; } @@ -103,22 +101,22 @@ void wfmrx::set_demod(int demod) lock(); /* disconnect current demodulator */ - switch (d_demod) { + switch (receiver_base_cf::get_demod()) { - case WFMRX_DEMOD_MONO: + case Modulations::MODE_WFM_MONO: default: disconnect(demod_fm, 0, mono, 0); disconnect(mono, 0, agc, 0); // left channel disconnect(mono, 1, agc, 1); // right channel break; - case WFMRX_DEMOD_STEREO: + case Modulations::MODE_WFM_STEREO: disconnect(demod_fm, 0, stereo, 0); disconnect(stereo, 0, agc, 0); // left channel disconnect(stereo, 1, agc, 1); // right channel break; - case WFMRX_DEMOD_STEREO_UKW: + case Modulations::MODE_WFM_STEREO_OIRT: disconnect(demod_fm, 0, stereo_oirt, 0); disconnect(stereo_oirt, 0, agc, 0); // left channel disconnect(stereo_oirt, 1, agc, 1); // right channel @@ -127,41 +125,31 @@ void wfmrx::set_demod(int demod) switch (demod) { - case WFMRX_DEMOD_MONO: + case Modulations::MODE_WFM_MONO: default: connect(demod_fm, 0, mono, 0); connect(mono, 0, agc, 0); // left channel connect(mono, 1, agc, 1); // right channel break; - case WFMRX_DEMOD_STEREO: + case Modulations::MODE_WFM_STEREO: connect(demod_fm, 0, stereo, 0); connect(stereo, 0, agc, 0); // left channel connect(stereo, 1, agc, 1); // right channel break; - case WFMRX_DEMOD_STEREO_UKW: + case Modulations::MODE_WFM_STEREO_OIRT: connect(demod_fm, 0, stereo_oirt, 0); connect(stereo_oirt, 0, agc, 0); // left channel connect(stereo_oirt, 1, agc, 1); // right channel break; } - d_demod = (wfmrx_demod) demod; + receiver_base_cf::set_demod(demod); /* continue processing */ unlock(); } -void wfmrx::set_fm_maxdev(float maxdev_hz) -{ - demod_fm->set_max_dev(maxdev_hz); -} - -void wfmrx::set_fm_deemph(double tau) -{ - demod_fm->set_tau(tau); -} - void wfmrx::get_rds_data(std::string &outbuff, int &num) { rds_store->get_message(outbuff, num); diff --git a/src/receivers/wfmrx.h b/src/receivers/wfmrx.h index 2c05a12fc1..5b1864629c 100644 --- a/src/receivers/wfmrx.h +++ b/src/receivers/wfmrx.h @@ -53,52 +53,29 @@ class wfmrx : public receiver_base_cf { public: - /*! \brief Available demodulators. */ - enum wfmrx_demod { - WFMRX_DEMOD_MONO = 0, /*!< Mono. */ - WFMRX_DEMOD_STEREO = 1, /*!< FM stereo. */ - WFMRX_DEMOD_STEREO_UKW = 2, /*!< UKW stereo. */ - WFMRX_DEMOD_NUM = 3 /*!< Included for convenience. */ - }; wfmrx(float quad_rate, float audio_rate); ~wfmrx(); - bool start(); - bool stop(); + bool start() override; + bool stop() override; - void set_filter(double low, double high, double tw); - void set_cw_offset(double offset) { (void)offset; } + void set_filter(int low, int high, int tw) override; /* Noise blanker */ - bool has_nb() { return false; } - //void set_nb_on(int nbid, bool on); - //void set_nb_threshold(int nbid, float threshold); + bool has_nb() override { return false; } - /* Squelch parameter */ - bool has_sql() { return true; } + void set_demod(Modulations::idx demod) override; - /* AGC */ - bool has_agc() { return true; } - - void set_demod(int demod); - - /* FM parameters */ - bool has_fm() {return true; } - void set_fm_maxdev(float maxdev_hz); - void set_fm_deemph(double tau); - - void get_rds_data(std::string &outbuff, int &num); - void start_rds_decoder(); - void stop_rds_decoder(); - void reset_rds_parser(); - bool is_rds_decoder_active(); + void get_rds_data(std::string &outbuff, int &num) override; + void start_rds_decoder() override; + void stop_rds_decoder() override; + void reset_rds_parser() override; + bool is_rds_decoder_active() override; private: bool d_running; /*!< Whether receiver is running or not. */ - wfmrx_demod d_demod; /*!< Current demodulator. */ - rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */