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 29a4ce8c6b..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,14 +208,14 @@ 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; @@ -230,7 +241,7 @@ void rx_agc_2f::set_agc_on(bool agc_on) { if (agc_on != d_agc_on) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang); + set_parameters(d_sample_rate, agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, d_panning); #if GNURADIO_VERSION >= 0x030800 if(d_agc_on) declare_sample_delay(d_sample_rate * d_attack / 1000); @@ -251,7 +262,7 @@ void rx_agc_2f::set_sample_rate(double sample_rate) { if (sample_rate != d_sample_rate) { std::lock_guard lock(d_mutex); - set_parameters(sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang); + set_parameters(sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, d_panning); #if GNURADIO_VERSION >= 0x030800 if(d_agc_on) declare_sample_delay(d_sample_rate * d_attack / 1000); @@ -269,7 +280,7 @@ void rx_agc_2f::set_target_level(int target_level) { if ((target_level != d_target_level) && (target_level >= -160) && (target_level <= 0)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang); + set_parameters(d_sample_rate, d_agc_on, target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, d_panning); } } @@ -285,7 +296,7 @@ void rx_agc_2f::set_manual_gain(float gain) { if ((gain != d_manual_gain) && (gain >= -160.0) && (gain <= 160.0)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, d_target_level, gain, d_max_gain, d_attack, d_decay, d_hang); + set_parameters(d_sample_rate, d_agc_on, d_target_level, gain, d_max_gain, d_attack, d_decay, d_hang, d_panning); } } @@ -301,7 +312,7 @@ void rx_agc_2f::set_max_gain(int gain) { if ((gain != d_max_gain) && (gain >= 0) && (gain <= 160)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, gain, d_attack, d_decay, d_hang); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, gain, d_attack, d_decay, d_hang, d_panning); } } @@ -316,7 +327,7 @@ void rx_agc_2f::set_attack(int attack) { if ((attack != d_attack) && (attack >= 20) && (attack <= 5000)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, attack, d_decay, d_hang); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, attack, d_decay, d_hang, d_panning); #if GNURADIO_VERSION >= 0x030800 if(d_agc_on) declare_sample_delay(d_sample_rate * d_attack / 1000); @@ -332,7 +343,7 @@ void rx_agc_2f::set_decay(int decay) { if ((decay != d_decay) && (decay >= 20) && (decay <= 5000)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, decay, d_hang); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, decay, d_hang, d_panning); } } @@ -344,7 +355,15 @@ void rx_agc_2f::set_hang(int hang) { if ((hang != d_hang) && (hang >= 0) && (hang <= 5000)) { std::lock_guard lock(d_mutex); - set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, hang); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, hang, d_panning); + } +} + +void rx_agc_2f::set_panning(int panning) +{ + if((panning != d_panning) && (panning >=-100) && (panning <= 100)) { + std::lock_guard lock(d_mutex); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, panning); } } @@ -356,7 +375,7 @@ float rx_agc_2f::get_current_gain() void rx_agc_2f::set_parameters(double sample_rate, bool agc_on, int target_level, float manual_gain, int max_gain, int attack, - int decay, int hang, bool force) + int decay, int hang, int panning, bool force) { bool samp_rate_changed = false; bool agc_on_changed = false; @@ -366,6 +385,7 @@ void rx_agc_2f::set_parameters(double sample_rate, bool agc_on, int target_level bool attack_changed = false; bool decay_changed = false; bool hang_changed = false; + if (d_sample_rate != sample_rate || force) { d_sample_rate = sample_rate; @@ -418,18 +438,35 @@ void rx_agc_2f::set_parameters(double sample_rate, bool agc_on, int target_level d_hang = hang; hang_changed = true; } + if (d_panning != panning || force) + { + d_panning = panning; + d_gain_l = 1.0; + d_gain_r = 1.0; + d_delay_l = 0; + d_delay_r = 0; + if(panning < 0) + { + d_gain_r = exp10(panning / PANNING_GAIN_K); + d_delay_r = (sample_rate * -panning)/PANNING_DELAY_K; + } + if(panning > 0) + { + d_gain_l = exp10(-panning / PANNING_GAIN_K); + d_delay_l = (sample_rate * panning)/PANNING_DELAY_K; + } + } if (samp_rate_changed || attack_changed) { d_buf_samples = sample_rate * d_attack / 1000.0; + int buf_samples = d_buf_samples * AGC_AVG_BUF_SCALE; int buf_size = 1; for(unsigned int k = 0; k < sizeof(int) * 8; k++) { buf_size *= 2; - if(buf_size >= d_buf_samples) + if(buf_size >= buf_samples) break; } - if (d_buf_p >= d_buf_samples) - d_buf_p %= d_buf_samples; if(d_buf_size != buf_size) { d_buf_size = buf_size; @@ -440,6 +477,8 @@ void rx_agc_2f::set_parameters(double sample_rate, bool agc_on, int target_level if(d_agc_on && d_running) d_refill = true; } + if (d_buf_p >= d_buf_size) + d_buf_p %= d_buf_size; } if ((manual_gain_changed || agc_on_changed) && !agc_on) d_current_gain = exp10(TYPEFLOAT(d_manual_gain) / 20.0); diff --git a/src/dsp/rx_agc_xx.h b/src/dsp/rx_agc_xx.h index c3378be42b..be3b80cc1b 100644 --- a/src/dsp/rx_agc_xx.h +++ b/src/dsp/rx_agc_xx.h @@ -60,7 +60,7 @@ typedef std::shared_ptr rx_agc_2f_sptr; */ 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 decay, int hang, int panning); /** * \brief Experimental AGC block for analog voice modes (AM, SSB, CW). @@ -74,11 +74,12 @@ class rx_agc_2f : public gr::sync_block friend 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 hang, int panning); protected: 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); public: ~rx_agc_2f(); @@ -97,11 +98,13 @@ class rx_agc_2f : public gr::sync_block void set_attack(int attack); void set_decay(int decay); void set_hang(int hang); + void set_panning(int panning); + float get_current_gain(); private: void set_parameters(double sample_rate, bool agc_on, int target_level, float manual_gain, int max_gain, int attack, int decay, - int hang, bool force = false); + int hang, int panning, bool force = false); std::mutex d_mutex; /*! Used to lock internal data while processing or setting parameters. */ @@ -113,6 +116,7 @@ class rx_agc_2f : public gr::sync_block int d_attack; /*! Current AGC attack (20...5000 ms). */ int d_decay; /*! Current AGC decay (20...5000 ms). */ int d_hang; /*! Current AGC hang (0...5000 ms). */ + int d_panning; /*! Current AGC panning (-100...100). */ private: float get_peak(); void update_buffer(int p); @@ -130,6 +134,10 @@ class rx_agc_2f : public gr::sync_block TYPEFLOAT d_decay_step; TYPEFLOAT d_attack_step; TYPEFLOAT d_floor; + TYPEFLOAT d_gain_l; + TYPEFLOAT d_gain_r; + int d_delay_l; + int d_delay_r; std::vector d_mag_buf; bool d_refill; diff --git a/src/dsp/rx_demod_am.cpp b/src/dsp/rx_demod_am.cpp index 7066ca60bf..26266d501b 100644 --- a/src/dsp/rx_demod_am.cpp +++ b/src/dsp/rx_demod_am.cpp @@ -86,20 +86,16 @@ void rx_demod_am::set_dcr(bool dcr) if (d_dcr_enabled) { // Switching from ON to OFF - lock(); disconnect(d_demod, 0, d_dcr, 0); disconnect(d_dcr, 0, self(), 0); connect(d_demod, 0, self(), 0); - unlock(); } else { // Switching from OFF to ON - lock(); disconnect(d_demod, 0, self(), 0); connect(d_demod, 0, d_dcr, 0); connect(d_dcr, 0, self(), 0); - unlock(); } d_dcr_enabled = dcr; @@ -164,20 +160,16 @@ void rx_demod_amsync::set_dcr(bool dcr) if (d_dcr_enabled) { // Switching from ON to OFF - lock(); disconnect(d_demod2, 0, d_dcr, 0); disconnect(d_dcr, 0, self(), 0); connect(d_demod2, 0, self(), 0); - unlock(); } else { // Switching from OFF to ON - lock(); disconnect(d_demod2, 0, self(), 0); connect(d_demod2, 0, d_dcr, 0); connect(d_dcr, 0, self(), 0); - unlock(); } d_dcr_enabled = dcr; diff --git a/src/dsp/rx_filter.h b/src/dsp/rx_filter.h index f34db9653d..6d6c6b8fa9 100644 --- a/src/dsp/rx_filter.h +++ b/src/dsp/rx_filter.h @@ -33,8 +33,7 @@ #include #endif - -#define RX_FILTER_MIN_WIDTH 100 /*! Minimum width of filter */ +#include "receivers/defines.h" class rx_filter; class rx_xlating_filter; diff --git a/src/interfaces/wav_sink.cpp b/src/interfaces/wav_sink.cpp index 158bb66633..ce53d9f73d 100644 --- a/src/interfaces/wav_sink.cpp +++ b/src/interfaces/wav_sink.cpp @@ -418,7 +418,7 @@ void wavfile_sink_gqrx::writeout(const int offset, const int writecount, const i int nwritten = 0; int bp = 0; int errnum; - while(nwritten < writecount) + while (nwritten < writecount) { for (bp = 0; (nwritten < writecount) && (bp < s_items_size); nwritten++, bp++) { @@ -445,9 +445,13 @@ void wavfile_sink_gqrx::writeout(const int offset, const int writecount, const i void wavfile_sink_gqrx::set_sql_triggered(const bool enabled) { - std::unique_lock 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. */