diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a4521b83a..d21d3c397 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,7 +91,7 @@ jobs: sudo chown -R runner:admin /usr/local/ brew update brew install --HEAD librtlsdr - brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf libserialport portaudio pybind11 six soapyremote uhd qt@6 || true + brew install airspy airspyhf boost dylibbundler gnuradio hackrf libbladerf libserialport portaudio pybind11 six soapyremote uhd libsndfile qt@6 || true cd /tmp git clone https://github.com/analogdevicesinc/libiio.git diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab24e68e8..504ec2442 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: # for https://github.com/actions/runner-images/issues/9272 sudo chown -R runner:admin /usr/local/ brew update - brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 six uhd qt@6 || true + brew install airspy boost gnuradio hackrf libbladerf librtlsdr pybind11 six uhd libsndfile qt@6 || true cd /tmp git clone https://gitea.osmocom.org/sdr/gr-osmosdr.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9322eb77a..9e29ce94a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,6 +138,7 @@ endif() include(FindPkgConfig) find_package(Gnuradio-osmosdr REQUIRED) +find_package(SNDFILE REQUIRED) set(GR_REQUIRED_COMPONENTS RUNTIME ANALOG AUDIO BLOCKS DIGITAL FILTER FFT PMT) find_package(Gnuradio REQUIRED COMPONENTS analog audio blocks digital filter fft network) diff --git a/README.md b/README.md index 7344f7645..3a62528c1 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ To compile gqrx from source you need the following dependencies: - Network - Widgets - Svg (runtime-only) +- libsndfile - cmake version >= 3.2.0 Gqrx can be compiled from within Qt Creator or in a terminal: diff --git a/cmake/Modules/FindSNDFILE.cmake b/cmake/Modules/FindSNDFILE.cmake new file mode 100644 index 000000000..14f362d4f --- /dev/null +++ b/cmake/Modules/FindSNDFILE.cmake @@ -0,0 +1,34 @@ +find_package(PkgConfig) +PKG_CHECK_MODULES(PC_SNDFILE "sndfile") + +FIND_PATH(SNDFILE_INCLUDE_DIRS + NAMES sndfile.h + HINTS ${PC_SNDFILE_INCLUDE_DIR} + ${CMAKE_INSTALL_PREFIX}/include + PATHS + /usr/local/include + /usr/include +) + +FIND_LIBRARY(SNDFILE_LIBRARIES + NAMES sndfile ${SNDFILE_LIBRARY_NAME} + HINTS ${PC_SNDFILE_LIBDIR} + ${CMAKE_INSTALL_PREFIX}/lib + ${CMAKE_INSTALL_PREFIX}/lib64 + PATHS + ${SNDFILE_INCLUDE_DIRS}/../lib + /usr/local/lib + /usr/lib +) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SNDFILE DEFAULT_MSG SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS) +MARK_AS_ADVANCED(SNDFILE_LIBRARIES SNDFILE_INCLUDE_DIRS) + +if (SNDFILE_FOUND AND NOT TARGET sndfile::sndfile) + add_library(sndfile::sndfile INTERFACE IMPORTED) + set_target_properties(sndfile::sndfile PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${SNDFILE_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${SNDFILE_LIBRARIES}" + ) +endif() diff --git a/resources/icons.qrc b/resources/icons.qrc index 8f535b099..71d341c7e 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 000000000..de7746458 --- /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 000000000..b84551c88 --- /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 000000000..3280a3594 --- /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/CMakeLists.txt b/src/CMakeLists.txt index cdb69568a..376c878f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,7 @@ target_link_libraries(${PROJECT_NAME} ${PULSEAUDIO_LIBRARY} ${PULSE-SIMPLE} ${PORTAUDIO_LIBRARIES} + ${SNDFILE_LIBRARIES} ) if(NOT Gnuradio_VERSION VERSION_LESS "3.10") diff --git a/src/applications/gqrx/mainwindow.cpp b/src/applications/gqrx/mainwindow.cpp index 72681f13a..c298ca377 100644 --- a/src/applications/gqrx/mainwindow.cpp +++ b/src/applications/gqrx/mainwindow.cpp @@ -68,6 +68,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_fftWindowType(0), d_fftNormalizeEnergy(false), @@ -108,11 +109,14 @@ 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); rx->set_rf_freq(144500000.0); + rx->set_audio_rec_event_handler(std::bind(audio_rec_event, this, + std::placeholders::_1, + std::placeholders::_2)); // remote controller remote = new RemoteControl(); @@ -219,7 +223,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))); @@ -237,11 +248,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))); @@ -249,25 +261,38 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockRxOpt, SIGNAL(amSyncDcrToggled(bool)), this, SLOT(setAmSyncDcr(bool))); connect(uiDockRxOpt, SIGNAL(amSyncPllBwSelected(float)), this, SLOT(setAmSyncPllBw(float))); connect(uiDockRxOpt, SIGNAL(agcToggled(bool)), this, SLOT(setAgcOn(bool))); - connect(uiDockRxOpt, SIGNAL(agcHangToggled(bool)), this, SLOT(setAgcHang(bool))); - connect(uiDockRxOpt, SIGNAL(agcThresholdChanged(int)), this, SLOT(setAgcThreshold(int))); - connect(uiDockRxOpt, SIGNAL(agcSlopeChanged(int)), this, SLOT(setAgcSlope(int))); - connect(uiDockRxOpt, SIGNAL(agcGainChanged(int)), this, SLOT(setAgcGain(int))); + connect(uiDockRxOpt, SIGNAL(agcTargetLevelChanged(int)), this, SLOT(setAgcTargetLevel(int))); + connect(uiDockRxOpt, SIGNAL(agcMaxGainChanged(int)), this, SLOT(setAgcMaxGain(int))); + 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(audioGainChanged(float)), remote, SLOT(setAudioGain(float))); - connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool))); + connect(uiDockAudio, SIGNAL(audioMuteChanged(bool,bool)), this, SLOT(setAudioMute(bool,bool))); + connect(uiDockAudio, SIGNAL(udpHostChanged(const QString)), this, SLOT(audioStreamHostChanged(const QString))); + connect(uiDockAudio, SIGNAL(udpPortChanged(int)), this, SLOT(audioStreamPortChanged(int))); + connect(uiDockAudio, SIGNAL(udpStereoChanged(bool)), this, SLOT(audioStreamStereoChanged(bool))); + connect(uiDockAudio, SIGNAL(audioStreamingStarted()), this, SLOT(startAudioStream())); connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming())); - connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString))); - connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), remote, SLOT(startAudioRecorder(QString))); - connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec())); - connect(uiDockAudio, SIGNAL(audioRecStopped()), remote, SLOT(stopAudioRecorder())); + connect(uiDockAudio, SIGNAL(audioRecStart()), this, SLOT(startAudioRec())); + connect(uiDockAudio, SIGNAL(audioRecStart()), remote, SLOT(startAudioRecorder())); + connect(uiDockAudio, SIGNAL(audioRecStop()), this, SLOT(stopAudioRec())); + connect(uiDockAudio, SIGNAL(audioRecStop()), remote, SLOT(stopAudioRecorder())); connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString))); connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback())); + connect(uiDockAudio, SIGNAL(recDirChanged(QString)), this, SLOT(recDirChanged(QString))); + connect(uiDockAudio, SIGNAL(recSquelchTriggeredChanged(bool)), this, SLOT(recSquelchTriggeredChanged(bool))); + 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())); // FFT Dock connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int))); @@ -310,7 +335,10 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(ui->plotter, SIGNAL(markerSelectB(qint64)), this, SLOT(setMarkerB(qint64))); // 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())); @@ -331,14 +359,14 @@ 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(remote, SIGNAL(newAudioGain(float)), uiDockAudio, SLOT(setAudioGainDb(float))); + connect(remote, SIGNAL(newAudioGain(float)), this, SLOT(setAudioGain(float))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double))); - connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder())); - connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder())); + connect(remote, SIGNAL(startAudioRecorderEvent()), this, SLOT(startAudioRec())); + connect(remote, SIGNAL(stopAudioRecorderEvent()), this, SLOT(stopAudioRec())); connect(ui->plotter, SIGNAL(newFilterFreq(int, int)), remote, SLOT(setPassband(int, int))); connect(remote, SIGNAL(newPassband(int)), this, SLOT(setPassband(int))); connect(remote, SIGNAL(gainChanged(QString, double)), uiDockInputCtl, SLOT(setGain(QString,double))); @@ -351,6 +379,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) rds_timer = new QTimer(this); connect(rds_timer, SIGNAL(timeout()), this, SLOT(rdsTimeout())); + connect(this, SIGNAL(sigAudioRecEvent(QString, bool)), this, SLOT(audioRecEvent(QString, bool)), Qt::QueuedConnection); // enable frequency tooltips on FFT plot ui->plotter->setTooltipsEnabled(true); @@ -447,6 +476,7 @@ MainWindow::~MainWindow() delete rx; delete remote; delete qsvg_dummy; + delete rxSpinBox; } /** @@ -474,6 +504,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; @@ -525,6 +557,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) @@ -660,26 +693,25 @@ 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, actual_rate); + if (ver < 4) { // Center frequency for FFT plotter int64_val = m_settings->value("fft/fft_center", 0).toLongLong(&conv_ok); @@ -687,18 +719,23 @@ bool MainWindow::loadConfig(const QString& cfgfile, bool check_crash, if (conv_ok) { ui->plotter->setFftCenterFreq(int64_val); } - } - - { 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) + if (conv_ok && uiDockRxOpt->currentDemod() != Modulations::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); /* @@ -782,30 +819,379 @@ void MainWindow::storeSession() { if (m_settings) { - m_settings->setValue("input/frequency", ui->freqCtrl->getFrequency()); m_settings->setValue("fft/fft_center", ui->plotter->getFftCenterFreq()); + 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(); + m_settings->setValue("offset", offs); + + 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"); + //noise blanker + for (int j = 1; j < RECEIVER_NB_COUNT + 1; j++) + { + if(rx->get_nb_on(j)) + m_settings->setValue(QString("nb%1on").arg(j), true); + else + m_settings->remove(QString("nb%1on").arg(j)); + m_settings->setValue(QString("nb%1thr").arg(j), rx->get_nb_threshold(j)); + } + //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", QString::fromStdString(rx->get_audio_rec_dir())); + 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"); + + if (rx->get_udp_host() != "127.0.0.1") + m_settings->setValue("udp_host", QString::fromStdString(rx->get_udp_host())); + else + m_settings->remove("udp_host"); + + if (rx->get_udp_stereo() != false) + m_settings->setValue("udp_stereo", true); + else + m_settings->remove("udp_stereo"); + + int_val = rx->get_udp_port(); + if (int_val != 7355) + m_settings->setValue("udp_port", int_val); + else + m_settings->remove("udp_port"); + + 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, double actual_rate) +{ + bool conv_ok; + int int_val; + double dbl_val; + int i = 0; + qint64 offs = 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); + + bool isLocked = m_settings->value("freq_locked", false).toBool(); + rx->set_freq_lock(isLocked); + + offs = m_settings->value("offset", 0).toInt(&conv_ok); + if (conv_ok) + { + if(!isLocked || ver < 4) + if(std::abs(offs) > actual_rate / 2) + offs = (offs > 0) ? (actual_rate / 2) : (-actual_rate / 2); + rx->set_filter_offset(offs); + } + + 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); + + for (int j = 1; j < RECEIVER_NB_COUNT + 1; j++) + { + rx->set_nb_on(j, m_settings->value(QString("nb%1on").arg(j), false).toBool()); + float thr = m_settings->value(QString("nb%1thr").arg(j), 2.0).toFloat(&conv_ok); + if (conv_ok) + rx->set_nb_threshold(j, thr); + } + + 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); + + QString udp_host = m_settings->value("udp_host", "127.0.0.1").toString(); + rx->set_udp_host(udp_host.toStdString()); + + int_val = m_settings->value("udp_port", 7355).toInt(&conv_ok); + if (!conv_ok) + int_val = 7355; + rx->set_udp_port(int_val); + + bool udp_stereo = m_settings->value("udp_stereo", false).toBool(); + rx->set_udp_stereo(udp_stereo); + + 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); + if(int_val >= rx->get_rx_count()) + int_val = 0; + 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); + offs = rx->get_filter_offset(); + if(std::abs(offs) > actual_rate / 2) + rx->set_filter_offset((offs > 0) ? (actual_rate / 2) : (-actual_rate / 2)); + loadRxToGUI(); + ui->plotter->updateOverlay(); +} + /** * @brief Update hardware RF frequency range. * @param ignore_limits Whether ignore the hardware specd and allow DC-to-light @@ -901,19 +1287,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(); + } + } } // Update delta and center (of marker span) when markers are updated @@ -1032,6 +1551,7 @@ void MainWindow::setFilterOffset(qint64 freq_hz) if (rx->is_rds_decoder_active()) { rx->reset_rds_parser(); } + ui->plotter->updateOverlay(); } /** @@ -1144,15 +1664,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); @@ -1166,137 +1692,178 @@ 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(); + ui->plotter->updateOverlay(); +} +/** + * @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); - rx->set_am_dcr(uiDockRxOpt->currentAmDcr()); - 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); - rx->set_amsync_dcr(uiDockRxOpt->currentAmsyncDcr()); - rx->set_amsync_pll_bw(uiDockRxOpt->currentAmsyncPll()); - 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; } @@ -1305,14 +1872,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()); + 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); } @@ -1385,37 +1950,51 @@ void MainWindow::setAmSyncPllBw(float pll_bw) */ void MainWindow::setAudioGain(float value) { - rx->set_af_gain(value); + rx->set_agc_manual_gain(value); +} + +/** + * @brief Audio mute changed. + * @param mute New state. + * @param global Set global or this VFO mute. + */ +void MainWindow::setAudioMute(bool mute, bool global) +{ + if (global) + rx->set_mute(mute); + else + rx->set_agc_mute(mute); } /** Set AGC ON/OFF. */ void MainWindow::setAgcOn(bool agc_on) { rx->set_agc_on(agc_on); + uiDockAudio->setGainEnabled(!agc_on); } /** AGC hang ON/OFF. */ -void MainWindow::setAgcHang(bool use_hang) +void MainWindow::setAgcHang(int hang) { - rx->set_agc_hang(use_hang); + rx->set_agc_hang(hang); } /** AGC threshold changed. */ -void MainWindow::setAgcThreshold(int threshold) +void MainWindow::setAgcTargetLevel(int targetLevel) { - rx->set_agc_threshold(threshold); + rx->set_agc_target_level(targetLevel); } /** AGC slope factor changed. */ -void MainWindow::setAgcSlope(int factor) +void MainWindow::setAgcAttack(int attack) { - rx->set_agc_slope(factor); + rx->set_agc_attack(attack); } -/** AGC manual gain changed. */ -void MainWindow::setAgcGain(int gain) +/** AGC maximum gain changed. */ +void MainWindow::setAgcMaxGain(int gain) { - rx->set_agc_manual_gain(gain); + rx->set_agc_max_gain(gain); } /** AGC decay changed. */ @@ -1424,6 +2003,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. @@ -1453,8 +2044,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 = (double)rx->get_signal_pwr() + 3.0; if (level > -10.0) // avoid 0 dBFS level = uiDockRxOpt->getSqlLevel(); @@ -1463,6 +2056,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() { @@ -1471,6 +2072,10 @@ void MainWindow::meterTimeout() level = rx->get_signal_pwr(); ui->sMeter->setLevel(level); remote->setSignalLevel(level); + if(uiDockRxOpt->getAgcOn()) + { + uiDockAudio->setAudioGain(rx->get_agc_gain() * 10.f); + } } /** Baseband FFT plot timeout. */ @@ -1543,11 +2148,47 @@ void MainWindow::rdsTimeout() } } +/** + * @brief Set audio recording directory. + * @param dir The directory, where audio files should be created. + */ +void MainWindow::recDirChanged(const QString dir) +{ + rx->set_audio_rec_dir(dir.toStdString()); +} + +/** + * @brief Set audio recording squelch triggered mode. + * @param enabled New state. + */ +void MainWindow::recSquelchTriggeredChanged(const bool enabled) +{ + rx->set_audio_rec_sql_triggered(enabled); +} + +/** + * @brief Set audio recording squelch triggered minimum time. + * @param time_ms New time in milliseconds. + */ +void MainWindow::recMinTimeChanged(const int time_ms) +{ + rx->set_audio_rec_min_time(time_ms); +} + +/** + * @brief Set audio recording squelch triggered maximum gap time. + * @param time_ms New time in milliseconds. + */ +void MainWindow::recMaxGapChanged(const int time_ms) +{ + rx->set_audio_rec_max_gap(time_ms); +} + /** * @brief Start audio recorder. * @param filename The file name into which audio should be recorded. */ -void MainWindow::startAudioRec(const QString& filename) +void MainWindow::startAudioRec() { if (!d_have_audio) { @@ -1559,17 +2200,11 @@ void MainWindow::startAudioRec(const QString& filename) msg_box.exec(); uiDockAudio->setAudioRecButtonState(false); } - else if (rx->start_audio_recording(filename.toStdString())) + else if (rx->start_audio_recording()) { ui->statusBar->showMessage(tr("Error starting audio recorder")); - - /* reset state of record button */ uiDockAudio->setAudioRecButtonState(false); } - else - { - ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename)); - } } /** Stop audio recorder. */ @@ -1579,15 +2214,25 @@ void MainWindow::stopAudioRec() { /* okay, this one would be weird if it really happened */ ui->statusBar->showMessage(tr("Error stopping audio recorder")); - - uiDockAudio->setAudioRecButtonState(true); } - else +} + +/** Audio recording is started or stopped. */ +void MainWindow::audioRecEvent(const QString filename, bool is_running) +{ + if (is_running) { + ui->statusBar->showMessage(tr("Recording audio to %1").arg(filename)); + uiDockAudio->audioRecStarted(QString(filename)); + } + else + { + /* reset state of record button */ + uiDockAudio->audioRecStopped(); ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000); - } -} + } +} /** Start playback of audio file. */ void MainWindow::startAudioPlayback(const QString& filename) @@ -1621,16 +2266,46 @@ 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()); + } +} + +void MainWindow::audioStreamHostChanged(const QString udp_host) +{ + std::string host = udp_host.toStdString(); + rx->set_udp_host(host);//TODO: handle errors +} + +void MainWindow::audioStreamPortChanged(const int udp_port) +{ + rx->set_udp_port(udp_port);//TODO: handle errors +} + +void MainWindow::audioStreamStereoChanged(const bool udp_stereo) +{ + rx->set_udp_stereo(udp_stereo);//TODO: handle errors +} + /** Start streaming audio over UDP. */ -void MainWindow::startAudioStream(const QString& udp_host, int udp_port, bool stereo) +void MainWindow::startAudioStream() { - rx->start_udp_streaming(udp_host.toStdString(), udp_port, stereo); + rx->set_udp_streaming(true); + uiDockAudio->setAudioStreamButtonState(rx->get_udp_streaming()); } /** Stop streaming audio over UDP. */ void MainWindow::stopAudioStreaming() { - rx->stop_udp_streaming(); + rx->set_udp_streaming(false); + uiDockAudio->setAudioStreamButtonState(rx->get_udp_streaming()); } /** Start I/Q recording. */ @@ -1742,6 +2417,7 @@ void MainWindow::startIqPlayback(const QString& filename, float samprate, qint64 uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate)); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); + ui->plotter->updateOverlay(); if (std::abs(current_offset) > actual_rate / 2) on_plotter_newDemodFreq(center_freq, 0); else @@ -1782,6 +2458,7 @@ void MainWindow::stopIqPlayback() uiDockRxOpt->setFilterOffsetRange((qint64)(actual_rate)); ui->plotter->setSampleRate(actual_rate); ui->plotter->setSpanFreq((quint32)actual_rate); + ui->plotter->updateOverlay(); remote->setBandwidth(sr); // not needed as long as we are not recording in iq_tool @@ -1796,7 +2473,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()) @@ -2108,6 +2785,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 */ @@ -2116,6 +2829,7 @@ void MainWindow::on_plotter_newFilterFreq(int low, int high) /* Update filter range of plotter, in case this slot is triggered by * switching to a bookmark */ ui->plotter->setHiLowCutFrequencies(low, high); + ui->plotter->updateOverlay(); if (retcode == receiver::STATUS_OK) uiDockRxOpt->setFilterParam(low, high); @@ -2268,33 +2982,29 @@ 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(); + ui->plotter->updateOverlay(); +} - 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) @@ -2304,18 +3014,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; } @@ -2325,6 +3035,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() { @@ -2459,7 +3174,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'. { @@ -2492,6 +3209,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) { @@ -2507,13 +3229,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(); @@ -2524,11 +3246,142 @@ 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(); } } +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); + ui->plotter->updateOverlay(); +} + +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); + ui->plotter->updateOverlay(); +} + +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(); + ui->plotter->updateOverlay(); +} + +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.f); + + 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()); + uiDockAudio->setAudioMute(rx->get_agc_mute()); + + //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(); + uiDockAudio->setAudioStreamState(rx->get_udp_host(), rx->get_udp_port(), rx->get_udp_stereo(), rx->get_udp_streaming()); + 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); + } +} + void MainWindow::updateClusterSpots() { ui->plotter->updateOverlay(); @@ -2555,3 +3408,15 @@ void MainWindow::toggleMarkers() enableMarkers(!d_show_markers); uiDockFft->setMarkersEnabled(d_show_markers); } + +/** Called from GNU Radio thread */ +void MainWindow::audioRecEventEmitter(std::string filename, bool is_running) +{ + emit sigAudioRecEvent(QString(filename.data()), is_running); +} + +/** Called from GNU Radio thread */ +void MainWindow::audio_rec_event(MainWindow *self, std::string filename, bool is_running) +{ + self->audioRecEventEmitter(filename, is_running); +} diff --git a/src/applications/gqrx/mainwindow.h b/src/applications/gqrx/mainwindow.h index 5151bb2c1..7675f50c0 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" @@ -55,12 +56,16 @@ class MainWindow : public QMainWindow { Q_OBJECT +signals: + void sigAudioRecEvent(const QString filename, bool is_running); + public: explicit MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent = nullptr); ~MainWindow() override; bool loadConfig(const QString& cfgfile, bool check_crash, bool restore_mainwindow); bool saveConfig(const QString& cfgfile); + void readRXSettings(int ver, double actual_rate); void storeSession(); bool configOk; /*!< Main app uses this flag to know whether we should abort or continue. */ @@ -87,7 +92,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::vector d_iqFftData; float d_fftAvg; /*!< FFT averaging parameter set by user (not the true gain). */ float d_fps; @@ -128,6 +136,7 @@ public slots: std::map devList; + QSpinBox *rxSpinBox; // dummy widget to enforce linking to QtSvg QSvgWidget *qsvg_dummy; @@ -145,6 +154,9 @@ public slots: void rxOffsetZeroShortcut(); void toggleFreezeShortcut(); void toggleMarkers(); + 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 */ @@ -165,8 +177,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); @@ -174,24 +188,39 @@ private slots: void setAmSyncDcr(bool enabled); void setAmSyncPllBw(float pll_bw); void setAgcOn(bool agc_on); - void setAgcHang(bool use_hang); - void setAgcThreshold(int threshold); - void setAgcSlope(int factor); + void setAgcHang(int hang); + void setAgcTargetLevel(int targetLevel); + void setAgcAttack(int attack); 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, bool global); void setPassband(int bandwidth); + void setFreqLock(bool lock, bool all); /* audio recording and playback */ - void startAudioRec(const QString& filename); + void recDirChanged(const QString dir); + void recSquelchTriggeredChanged(const bool enabled); + void recMinTimeChanged(const int time_ms); + void recMaxGapChanged(const int time_ms); + void startAudioRec(); void stopAudioRec(); + 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); + /* audio UDP streaming */ + void audioStreamHostChanged(const QString udp_host); + void audioStreamPortChanged(const int udp_port); + void audioStreamStereoChanged(const bool udp_stereo); + void startAudioStream(); void stopAudioStreaming(); /* I/Q playback and recording*/ @@ -214,14 +243,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(); @@ -244,6 +277,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); /* markers*/ void on_setMarkerButtonA_clicked(); diff --git a/src/applications/gqrx/mainwindow.ui b/src/applications/gqrx/mainwindow.ui index 4790b522a..f9d1264e3 100644 --- a/src/applications/gqrx/mainwindow.ui +++ b/src/applications/gqrx/mainwindow.ui @@ -364,8 +364,8 @@ 0 0 - 800 - 35 + 521 + 23 @@ -452,6 +452,8 @@ + + @@ -732,6 +734,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 9bdf979ff..477f1064b 100644 --- a/src/applications/gqrx/receiver.cpp +++ b/src/applications/gqrx/receiver.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -46,9 +47,6 @@ #include #endif -#define DEFAULT_AUDIO_GAIN -6.0 -#define WAV_FILE_GAIN 0.5 -#define TARGET_QUAD_RATE 1e6 /** * @brief Public constructor. @@ -59,20 +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_demod(RX_DEMOD_OFF) + d_mute(false) { tb = gr::make_top_block("gqrx"); @@ -109,21 +106,28 @@ 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(DEFAULT_FFT_SIZE, d_decim_rate, gr::fft::window::WIN_HANN); audio_fft = make_rx_fft_f(DEFAULT_FFT_SIZE, d_audio_rate, gr::fft::window::WIN_HANN); - audio_gain0 = gr::blocks::multiply_const_ff::make(0); - audio_gain1 = gr::blocks::multiply_const_ff::make(0); - set_af_gain(DEFAULT_AUDIO_GAIN); - 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"); @@ -141,11 +145,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(); + } receiver::~receiver() @@ -199,25 +205,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 @@ -231,20 +232,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(); @@ -269,10 +260,16 @@ void receiver::set_output_device(const std::string device) tb->lock(); - if (d_demod != RX_DEMOD_OFF) + if (d_active > 0) { - tb->disconnect(audio_gain0, 0, audio_snk, 0); - tb->disconnect(audio_gain1, 0, audio_snk, 1); + try + { + tb->disconnect(mc0, 0, audio_snk, 0); + tb->disconnect(mc1, 1, audio_snk, 1); + } + catch(std::exception &x) + { + } } audio_snk.reset(); @@ -285,10 +282,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) + if (d_active > 0) { - tb->connect(audio_gain0, 0, audio_snk, 0); - tb->connect(audio_gain1, 0, audio_snk, 1); + tb->connect(mc0, 0, audio_snk, 0); + tb->connect(mc1, 0, audio_snk, 1); } tb->unlock(); @@ -357,11 +354,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(); @@ -414,11 +409,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) @@ -491,7 +484,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); } /** @@ -539,6 +532,8 @@ receiver::status receiver::set_rf_freq(double freq_hz) d_rf_freq = freq_hz; src->set_center_freq(d_rf_freq); + for (auto& rxc : rx) + rxc->set_center_freq(d_rf_freq);//to generate audio filename // FIXME: read back frequency? return STATUS_OK; @@ -638,6 +633,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.size() - 1); + 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. @@ -654,8 +799,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); + 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; } @@ -667,50 +819,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; } @@ -731,7 +884,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. */ @@ -769,41 +922,68 @@ int receiver::get_audio_fft_data(float* fftPoints) 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) +receiver::status receiver::set_sql_level(float level_db) +{ + rx[d_current]->set_sql_level(level_db); + + return STATUS_OK; // FIXME +} + +receiver::status receiver::set_sql_level(float level_offset, bool global, bool relative) { - if (rx->has_sql()) - rx->set_sql_level(level_db); + if (global) + for (auto& rxc: rx) + rxc->set_sql_level((relative ? rxc->get_signal_level() : 0.f) + level_offset); + else + rx[d_current]->set_sql_level((relative ? rx[d_current]->get_signal_level() : 0.f) + 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. * @@ -811,125 +991,305 @@ 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) +{ + 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) +{ + rx[d_current]->set_agc_target_level(target_level); return STATUS_OK; // FIXME } -/** Enable/disable AGC hang. */ -receiver::status receiver::set_agc_hang(bool use_hang) +int receiver::get_agc_target_level() { - if (rx->has_agc()) - rx->set_agc_hang(use_hang); + 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) +{ + rx[d_current]->set_agc_manual_gain(gain); return STATUS_OK; // FIXME } -/** Set AGC threshold. */ -receiver::status receiver::set_agc_threshold(int threshold) +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_threshold(threshold); + rx[d_current]->set_agc_max_gain(gain); return STATUS_OK; // FIXME } -/** Set AGC slope. */ -receiver::status receiver::set_agc_slope(int slope) +int receiver::get_agc_max_gain() { - if (rx->has_agc()) - rx->set_agc_slope(slope); + return rx[d_current]->get_agc_max_gain(); +} + +/** Set AGC attack. */ +receiver::status receiver::set_agc_attack(int 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 } -/** Set fixed gain used when AGC is OFF. */ -receiver::status receiver::set_agc_manual_gain(int gain) +int receiver::get_agc_decay() +{ + return rx[d_current]->get_agc_decay(); +} + +/** Set AGC panning. */ +receiver::status receiver::set_agc_panning(int panning) { - if (rx->has_agc()) - rx->set_agc_manual_gain(gain); + rx[d_current]->set_agc_panning(panning); return STATUS_OK; // FIXME } -receiver::status receiver::set_demod(rx_demod demod, bool force) +int receiver::get_agc_panning() { - status ret = STATUS_OK; + return rx[d_current]->get_agc_panning(); +} - if (!force && (demod == d_demod)) - return ret; +/** 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); - // tb->lock() seems to hang occasionally - if (d_running) - { - tb->stop(); - tb->wait(); - } + return STATUS_OK; // FIXME +} - tb->disconnect_all(); +bool receiver::get_agc_panning_auto() +{ + return rx[d_current]->get_agc_panning_auto(); +} - switch (demod) - { - case RX_DEMOD_OFF: - connect_all(RX_CHAIN_NONE); - break; +/** Set AGC mute */ +receiver::status receiver::set_agc_mute(bool agc_mute) +{ + rx[d_current]->set_agc_mute(agc_mute); - case RX_DEMOD_NONE: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_NONE); - break; + return STATUS_OK; // FIXME +} - case RX_DEMOD_AM: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_AM); - break; +bool receiver::get_agc_mute() +{ + return rx[d_current]->get_agc_mute(); +} - case RX_DEMOD_AMSYNC: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_AMSYNC); - break; +/** Get AGC current gain. */ +float receiver::get_agc_gain() +{ + return rx[d_current]->get_agc_gain(); +} - case RX_DEMOD_NFM: - connect_all(RX_CHAIN_NBRX); - rx->set_demod(nbrx::NBRX_DEMOD_FM); - break; +/** Set audio mute. */ +receiver::status receiver::set_mute(bool mute) +{ + if (d_mute == mute) + return STATUS_OK; + d_mute = mute; + if (d_mute) + { + mc0->set_k(0); + mc1->set_k(0); + } + else + { + float mul_k = get_rx_count() ? 1.f / float(get_rx_count()) : 1.f; + mc0->set_k(mul_k); + mc1->set_k(mul_k); + } + return STATUS_OK; +} - case RX_DEMOD_WFM_M: - connect_all(RX_CHAIN_WFMRX); - rx->set_demod(wfmrx::WFMRX_DEMOD_MONO); - break; +/** Get audio mute. */ +bool receiver::get_mute() +{ + return d_mute; +} + +receiver::status receiver::set_demod_locked(Modulations::idx demod, int old_idx) +{ + status ret = STATUS_OK; + rx_chain rxc = RX_CHAIN_NONE; + if (old_idx == -1) + { + background_rx(); + disconnect_rx(); + } - case RX_DEMOD_WFM_S: - connect_all(RX_CHAIN_WFMRX); - rx->set_demod(wfmrx::WFMRX_DEMOD_STEREO); + switch (demod) + { + 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()); + rx[d_current]->set_offset(old_rx->get_offset()); + // 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(); @@ -937,63 +1297,116 @@ 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; } -receiver::status receiver::set_af_gain(float gain_db) +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) { - float k; + //FIXME is it a global option, that should be set with for-loop? + rx[d_current]->set_audio_rec_dir(dir); + return STATUS_OK; +} - /* convert dB to factor */ - k = powf(10.0f, gain_db / 20.0f); - //std::cout << "G:" << gain_db << "dB / K:" << k << std::endl; - audio_gain0->set_k(k); - audio_gain1->set_k(k); +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[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[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[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. @@ -1004,9 +1417,9 @@ receiver::status receiver::set_af_gain(float gain_db) * file names does not work with WAV files (the initial /tmp/gqrx.wav will not be stopped * because the wav file can not be empty). See https://github.com/gqrx-sdr/gqrx/issues/36 */ -receiver::status receiver::start_audio_recording(const std::string filename) +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; @@ -1021,43 +1434,16 @@ receiver::status receiver::start_audio_recording(const std::string filename) return STATUS_ERROR; } - wav_gain0 = gr::blocks::multiply_const_ff::make(WAV_FILE_GAIN); - wav_gain1 = gr::blocks::multiply_const_ff::make(WAV_FILE_GAIN); - - // if this fails, we don't want to go and crash now, do we - try { -#if GNURADIO_VERSION < 0x030900 - wav_sink = gr::blocks::wavfile_sink::make(filename.c_str(), 2, - (unsigned int) d_audio_rate, - 16); -#else - wav_sink = gr::blocks::wavfile_sink::make(filename.c_str(), 2, - (unsigned int) d_audio_rate, - gr::blocks::FORMAT_WAV, gr::blocks::FORMAT_PCM_16); -#endif - } - catch (std::runtime_error &e) { - std::cout << "Error opening " << filename << ": " << e.what() << std::endl; + if (rx[d_current]->start_audio_recording() == 0) + return STATUS_OK; + else return STATUS_ERROR; - } - - tb->lock(); - tb->connect(rx, 0, wav_gain0, 0); - tb->connect(rx, 1, wav_gain1, 0); - tb->connect(wav_gain0, 0, wav_sink, 0); - tb->connect(wav_gain1, 0, wav_sink, 1); - tb->unlock(); - d_recording_wav = true; - - std::cout << "Recording audio to " << filename << std::endl; - - return STATUS_OK; } /** 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; @@ -1070,31 +1456,17 @@ receiver::status receiver::stop_audio_recording() return STATUS_ERROR; } - - // not strictly necessary to lock but I think it is safer - tb->lock(); - wav_sink->close(); - tb->disconnect(rx, 0, wav_gain0, 0); - tb->disconnect(rx, 1, wav_gain1, 0); - tb->disconnect(wav_gain0, 0, wav_sink, 0); - tb->disconnect(wav_gain1, 0, wav_sink, 1); - - // Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 - tb->disconnect(ddc, 0, rx, 0); - tb->connect(ddc, 0, rx, 0); - // End temporary workaround - - tb->unlock(); - wav_gain0.reset(); - wav_gain1.reset(); - wav_sink.reset(); - d_recording_wav = false; - - std::cout << "Audio recorder stopped" << std::endl; + rx[d_current]->stop_audio_recording(); return STATUS_OK; } +/** get last recorded audio file name. */ +std::string receiver::get_last_audio_filename() +{ + return rx[d_current]->get_last_audio_filename(); +} + /** Start audio playback. */ receiver::status receiver::start_audio_playback(const std::string filename) { @@ -1136,18 +1508,17 @@ receiver::status receiver::start_audio_playback(const std::string filename) stop(); /* route demodulator output to null sink */ - tb->disconnect(rx, 0, audio_gain0, 0); - tb->disconnect(rx, 1, audio_gain1, 0); - 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? */ - tb->connect(wav_src, 0, audio_gain0, 0); - tb->connect(wav_src, 1, audio_gain1, 0); + if (d_active > 0) + { + tb->disconnect(mc0, 0, audio_snk, 0); + tb->disconnect(mc1, 0, audio_snk, 1); + } + tb->disconnect(rx[d_current], 0, audio_fft, 0); + 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); start(); std::cout << "Playing audio from " << filename << std::endl; @@ -1160,18 +1531,17 @@ receiver::status receiver::stop_audio_playback() { /* disconnect wav source and reconnect receiver */ stop(); - tb->disconnect(wav_src, 0, audio_gain0, 0); - tb->disconnect(wav_src, 1, audio_gain1, 0); + 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); - tb->connect(rx, 0, audio_gain0, 0); - tb->connect(rx, 1, audio_gain1, 0); - 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->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(mc0, 0, audio_snk, 0); + tb->connect(mc1, 0, audio_snk, 1); + } + tb->connect(rx[d_current], 0, audio_fft, 0); /** FIXME: other channel? */ start(); /* delete wav_src since we can not change file name */ @@ -1180,18 +1550,45 @@ receiver::status receiver::stop_audio_playback() return STATUS_OK; } -/** Start UDP streaming of audio. */ -receiver::status receiver::start_udp_streaming(const std::string host, int port, bool stereo) +/** UDP streaming of audio. */ +receiver::status receiver::set_udp_host(std::string host) { - audio_udp_sink->start_streaming(host, port, stereo); - return STATUS_OK; + return rx[d_current]->set_udp_host(host) ? STATUS_OK : STATUS_ERROR; } -/** Stop UDP streaming of audio. */ -receiver::status receiver::stop_udp_streaming() +std::string receiver::get_udp_host() { - audio_udp_sink->stop_streaming(); - return STATUS_OK; + return rx[d_current]->get_udp_host(); +} + +receiver::status receiver::set_udp_port(int port) +{ + return rx[d_current]->set_udp_port(port) ? STATUS_OK : STATUS_ERROR; +} + +int receiver::get_udp_port() +{ + return rx[d_current]->get_udp_port(); +} + +receiver::status receiver::set_udp_stereo(bool stereo) +{ + return rx[d_current]->set_udp_stereo(stereo) ? STATUS_OK : STATUS_ERROR; +} + +bool receiver::get_udp_stereo() +{ + return rx[d_current]->get_udp_stereo(); +} + +receiver::status receiver::set_udp_streaming(bool streaming) +{ + return rx[d_current]->set_udp_streaming(streaming) ? STATUS_OK : STATUS_ERROR; +} + +bool receiver::get_udp_streaming() +{ + return rx[d_current]->get_udp_streaming(); } /** @@ -1290,7 +1687,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; @@ -1309,12 +1706,12 @@ 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); - // End temporary workaround + 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); tb->unlock(); @@ -1333,7 +1730,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; @@ -1364,101 +1761,233 @@ 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(); +} + +void receiver::connect_rx() +{ + connect_rx(d_current); +} - // RX demod chain - switch (type) +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); + 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); + 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); + } + } +} - // Audio path (if there is a receiver) - if (type != RX_CHAIN_NONE) +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) { - 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); - tb->connect(rx, 0, audio_gain0, 0); - tb->connect(rx, 1, audio_gain1, 0); - tb->connect(audio_gain0, 0, audio_snk, 0); - tb->connect(audio_gain1, 0, audio_snk, 1); - } - - // Recorders and sniffers - if (d_recording_wav) + 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); + } + } + else { - tb->connect(rx, 0, wav_gain0, 0); - tb->connect(rx, 1, wav_gain1, 0); - tb->connect(wav_gain0, 0, wav_sink, 0); - tb->connect(wav_gain1, 0, wav_sink, 1); + if (d_active > 0) + { + std::cerr<<"disconnect_rx MODE_OFF d_active > 0"<disconnect(null_src, 0, add0, n); + tb->disconnect(null_src, 0, add1, n); + } } + rx[n]->connected(false); +} - if (d_sniffer_active) +void receiver::background_rx() +{ + std::cerr<<"background_rx "<get_demod()<get_demod() != Modulations::MODE_OFF) + { + tb->disconnect(rx[d_current], 0, audio_fft, 0); + if (d_sniffer_active) + { + 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); + if (d_sniffer_active) + { + tb->connect(rx[d_current], 0, sniffer_rr, 0); + tb->connect(sniffer_rr, 0, sniffer, 0); + } + } + else { - tb->connect(rx, 0, sniffer_rr, 0); - tb->connect(sniffer_rr, 0, sniffer, 0); + + if (d_sniffer_active) + { + d_sniffer_active = false; + + /* delete resampler */ + 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) @@ -1470,3 +1999,15 @@ std::string receiver::escape_filename(std::string filename) ss2 << std::quoted(ss1.str(), '\'', '\\'); return ss2.str(); } + +void receiver::audio_rec_event(receiver * self, int idx, std::string filename, bool is_running) +{ + if (is_running) + std::cout << "Recording audio to " << filename << std::endl; + else + std::cout << "Audio recorder stopped" << std::endl; + + 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 d3e8834c7..54e3a7644 100644 --- a/src/applications/gqrx/receiver.h +++ b/src/applications/gqrx/receiver.h @@ -23,17 +23,26 @@ #ifndef RECEIVER_H #define RECEIVER_H +#if GNURADIO_VERSION < 0x030800 +#include +#include +#else +#include +#include +#endif + #include #include #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" @@ -78,19 +87,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. */ @@ -99,11 +95,9 @@ 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; static const unsigned int DEFAULT_FFT_SIZE = 8192; @@ -153,11 +147,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); @@ -169,54 +179,103 @@ 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(float level_db); + status set_sql_level(float 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); - status set_agc_hang(bool use_hang); - status set_agc_threshold(int threshold); - status set_agc_slope(int slope); + 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); - status set_agc_manual_gain(int gain); - - status set_demod(rx_demod demod, bool force=false); + 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(); + status set_agc_mute(bool agc_mute); + bool get_agc_mute(); + float get_agc_gain(); + + /* Mute */ + status set_mute(bool mute); + bool get_mute(); + + /* 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_af_gain(float gain_db); - status start_audio_recording(const std::string filename); + 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(); status start_audio_playback(const std::string filename); status stop_audio_playback(); - status start_udp_streaming(const std::string host, int port, bool stereo); - status stop_udp_streaming(); + /* UDP streaming */ + status set_udp_host(std::string host); + std::string get_udp_host(); + status set_udp_port(int port); + int get_udp_port(); + status set_udp_stereo(bool stereo); + bool get_udp_stereo(); + status set_udp_streaming(bool streaming); + bool get_udp_streaming(); /* I/Q recording and playback */ 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 */ @@ -228,38 +287,52 @@ class receiver /* utility functions */ static std::string escape_filename(std::string filename); + template void set_audio_rec_event_handler(T handler) + { + d_audio_rec_event_handler = handler; + } 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. */ @@ -267,21 +340,12 @@ 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::multiply_const_ff::sptr audio_gain0; /*!< Audio gain block. */ - gr::blocks::multiply_const_ff::sptr audio_gain1; /*!< Audio gain block. */ - gr::blocks::multiply_const_ff::sptr wav_gain0; /*!< WAV file gain block. */ - gr::blocks::multiply_const_ff::sptr wav_gain1; /*!< WAV file gain block. */ - gr::blocks::file_sink::sptr iq_sink; /*!< I/Q file sink. */ - gr::blocks::wavfile_sink::sptr wav_sink; /*!< WAV file sink for recording. */ 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. */ gr::blocks::null_sink::sptr audio_null_sink1; /*!< Audio null sink used during playback. */ - udp_sink_f_sptr audio_udp_sink; /*!< UDP sink to stream audio over the network. */ sniffer_f_sptr sniffer; /*!< Sample sniffer for data decoders. */ resampler_ff_sptr sniffer_rr; /*!< Sniffer resampler. */ @@ -292,9 +356,11 @@ class receiver #else gr::audio::sink::sptr audio_snk; /*!< gr audio sink */ #endif - + 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, int idx, std::string filename, + bool is_running); }; #endif // RECEIVER_H diff --git a/src/applications/gqrx/remote_control.cpp b/src/applications/gqrx/remote_control.cpp index 70a3cf687..38a9f8b72 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"; @@ -312,11 +312,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; } @@ -374,9 +374,9 @@ void RemoteControl::setAudioMuted(bool muted) } /*! \brief Start audio recorder (from mainwindow). */ -void RemoteControl::startAudioRecorder(QString unused) +void RemoteControl::startAudioRecorder() { - if (rc_mode > 0) + if (rc_mode > Modulations::MODE_OFF) audio_recorder_status = true; } @@ -456,69 +456,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) @@ -527,51 +527,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; @@ -630,7 +630,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(); @@ -790,7 +790,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"); } @@ -902,7 +902,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 d16c1fd07..cb6ed66de 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,12 +91,12 @@ 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 setAudioGain(float gain); void setAudioMuted(bool muted); - void startAudioRecorder(QString unused); + void startAudioRecorder(); void stopAudioRecorder(); bool setGain(QString name, double gain); void setRDSstatus(bool enabled); @@ -106,7 +108,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 newAudioGain(float gain); @@ -133,7 +135,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/CMakeLists.txt b/src/dsp/CMakeLists.txt index 3f8e38e80..68205690b 100644 --- a/src/dsp/CMakeLists.txt +++ b/src/dsp/CMakeLists.txt @@ -17,8 +17,6 @@ add_source_files(SRCS_LIST rds/parser_impl.h rds/parser.h rds/tmc_events.h - agc_impl.cpp - agc_impl.h correct_iq_cc.cpp correct_iq_cc.h downconverter.cpp @@ -49,4 +47,6 @@ add_source_files(SRCS_LIST sniffer_f.h stereo_demod.cpp stereo_demod.h + rx_squelch.cpp + rx_squelch.h ) diff --git a/src/dsp/agc_impl.cpp b/src/dsp/agc_impl.cpp deleted file mode 100644 index 86a094b2e..000000000 --- a/src/dsp/agc_impl.cpp +++ /dev/null @@ -1,317 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// agc_impl.cpp: implementation of the CAgc class. -// -// This class implements an automatic gain function. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-09-24 Adapted for gqrx -////////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== - -#include -#include - -////////////////////////////////////////////////////////////////////// -// Local Defines -////////////////////////////////////////////////////////////////////// - -//signal delay line time delay in seconds. -//adjust to cover the impulse response time of filter -#define DELAY_TIMECONST .015f - -//Peak Detector window time delay in seconds. -#define WINDOW_TIMECONST .018f - -//attack time constant in seconds -//just small enough to let attackave charge up within the DELAY_TIMECONST time -#define ATTACK_RISE_TIMECONST .002f -#define ATTACK_FALL_TIMECONST .005f - -#define DECAY_RISEFALL_RATIO .3f //ratio between rise and fall times of Decay time constants -//adjust for best action with SSB - -// hang timer release decay time constant in seconds -#define RELEASE_TIMECONST .05f - -//limit output to about 3db of max -#define AGC_OUTSCALE 0.7f - -// keep max in and out the same -#define MAX_AMPLITUDE 1.0f //32767.0 -#define MAX_MANUAL_AMPLITUDE 1.0f //32767.0 - -#define LOG_MAX_AMPL log10f(MAX_AMPLITUDE) - -#define MIN_CONSTANT 1e-8f // const for calc log() so that a value of 0 magnitude == -8 - // corresponding to -160dB. - // K = 10^(-8 + log(MAX_AMP)) - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -CAgc::CAgc() -{ - m_AgcOn = true; - m_UseHang = false; - m_Threshold = 0; - m_ManualGain = 0; - m_SlopeFactor = 0; - m_Decay = 0; - m_SampleRate = 100.0; - m_SigDelayBuf_r = (float*)(&m_SigDelayBuf); - m_ManualAgcGain = 0.f; - m_DecayAve = 0.f; - m_AttackAve = 0.f; - m_AttackRiseAlpha = 0.f; - m_AttackFallAlpha = 0.f; - m_DecayRiseAlpha = 0.f; - m_DecayFallAlpha = 0.f; - m_FixedGain = 0.f; - m_Knee = 0.f; - m_GainSlope = 0.f; - m_Peak = 0.f; - m_SigDelayPtr = 0; - m_MagBufPos = 0; - m_DelaySamples = 0; - m_WindowSamples = 0; - m_HangTime = 0; - m_HangTimer = 0; -} - -CAgc::~CAgc() -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// Sets and calculates various AGC parameters -// "On" switches between AGC on and off. -// "Threshold" specifies AGC Knee in dB if AGC is active.( nominal range -160 to 0dB) -// "ManualGain" specifies AGC manual gain in dB if AGC is not active.(nominal range 0 to 100dB) -// "SlopeFactor" specifies dB reduction in output at knee from maximum output level(nominal range 0 to 10dB) -// "Decay" is AGC decay value in milliseconds ( nominal range 20 to 5000 milliSeconds) -// "SampleRate" is current sample rate of AGC data -//////////////////////////////////////////////////////////////////////////////// -void CAgc::SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, - int SlopeFactor, int Decay, float SampleRate) -{ - if((AgcOn == m_AgcOn) && (UseHang == m_UseHang) && - (Threshold == m_Threshold) && (ManualGain == m_ManualGain) && - (SlopeFactor == m_SlopeFactor) && (Decay == m_Decay) && - (SampleRate == m_SampleRate)) - { - return; //just return if no parameter changed - } - - m_AgcOn = AgcOn; - m_UseHang = UseHang; - m_Threshold = Threshold; - m_ManualGain = ManualGain; - m_SlopeFactor = SlopeFactor; - m_Decay = Decay; - - if (m_SampleRate != SampleRate) - { - //clear out delay buffer and init some things if sample rate changes - m_SampleRate = SampleRate; - m_DelaySamples = (int)(m_SampleRate * DELAY_TIMECONST); - m_WindowSamples = (int)(m_SampleRate * WINDOW_TIMECONST); - for (int i = 0; i < MAX_DELAY_BUF; i++) - { - m_SigDelayBuf[i] = 0.0; - m_MagBuf[i] = -16.0; - } - m_SigDelayPtr = 0; - m_HangTimer = 0; - m_Peak = -16.0; - m_DecayAve = -5.0; - m_AttackAve = -5.0; - m_MagBufPos = 0; - - m_MagDeque.clear(); - m_MagDeque.push_back(m_WindowSamples - 1); - } - - // convert m_ThreshGain to linear manual gain value - m_ManualAgcGain = MAX_MANUAL_AMPLITUDE * powf(10.0f, (float)m_ManualGain / 20.0f); - - // calculate parameters for AGC gain as a function of input magnitude - m_Knee = (float)m_Threshold / 20.0f; - m_GainSlope = m_SlopeFactor / 100.0f; - - // fixed gain value used below knee threshold - m_FixedGain = AGC_OUTSCALE * powf(10.0f, m_Knee * (m_GainSlope - 1.0f) ); - - // calculate fast and slow filter values. - m_AttackRiseAlpha = (1.0f - expf(-1.0f / (m_SampleRate * ATTACK_RISE_TIMECONST))); - m_AttackFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * ATTACK_FALL_TIMECONST))); - - // make rise time DECAY_RISEFALL_RATIO of fall - m_DecayRiseAlpha = (1.0f - expf(-1.0f / (m_SampleRate * (float)m_Decay * 0.001f * DECAY_RISEFALL_RATIO))); - m_HangTime = (int)(m_SampleRate * (float)m_Decay * .001f); - - if (m_UseHang) - m_DecayFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * RELEASE_TIMECONST))); - else - m_DecayFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * (float)m_Decay * 0.001f))); - - // clamp Delay samples within buffer limit - if (m_DelaySamples >= MAX_DELAY_BUF - 1) - m_DelaySamples = MAX_DELAY_BUF - 1; -} - - - -////////////////////////////////////////////////////////////////////// -// Automatic Gain Control calculator for COMPLEX data -////////////////////////////////////////////////////////////////////// -void CAgc::ProcessData(int Length, const TYPECPX * pInData, TYPECPX * pOutData) -{ - float gain; - float mag; - TYPECPX delayedin; - - if (m_AgcOn) - { - for (int i = 0; i < Length; i++) - { - // get latest input sample - TYPECPX in = pInData[i]; - - // Get delayed sample of input signal - delayedin = m_SigDelayBuf[m_SigDelayPtr]; - - // put new input sample into signal delay buffer - m_SigDelayBuf[m_SigDelayPtr++] = in; - - // deal with delay buffer wrap around - if (m_SigDelayPtr >= m_DelaySamples) - m_SigDelayPtr = 0; - - mag = fabsf(in.real()); - float mim = fabsf(in.imag()); - if (mim > mag) - mag = mim; - mag = log10f(mag + MIN_CONSTANT) - LOG_MAX_AMPL; - - // create a sliding window of 'm_WindowSamples' magnitudes and output the peak value within the sliding window - if (m_MagDeque.front() == m_MagBufPos) - m_MagDeque.pop_front(); - - while ((!m_MagDeque.empty()) && mag >= m_MagBuf[m_MagDeque.back()]) - m_MagDeque.pop_back(); - - m_MagDeque.push_back(m_MagBufPos); - - m_Peak = m_MagBuf[m_MagDeque.front()]; - - m_MagBuf[m_MagBufPos++] = mag; // put latest mag sample in buffer; - if (m_MagBufPos >= m_WindowSamples) // deal with magnitude buffer wrap around - m_MagBufPos = 0; - - if (m_UseHang) - { - // using hang timer mode - if (m_Peak > m_AttackAve) - // if power is rising (use m_AttackRiseAlpha time constant) - m_AttackAve = (1.0f - m_AttackRiseAlpha) * m_AttackAve + - m_AttackRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_AttackFallAlpha time constant) - m_AttackAve = (1.0f - m_AttackFallAlpha) * m_AttackAve + - m_AttackFallAlpha * m_Peak; - - if (m_Peak > m_DecayAve) - { - // if magnitude is rising (use m_DecayRiseAlpha time constant) - m_DecayAve = (1.0f - m_DecayRiseAlpha) * m_DecayAve + - m_DecayRiseAlpha * m_Peak; - // reset hang timer - m_HangTimer = 0; - } - else - { // here if decreasing signal - if (m_HangTimer < m_HangTime) - m_HangTimer++; // just inc and hold current m_DecayAve - else // else decay with m_DecayFallAlpha which is RELEASE_TIMECONST - m_DecayAve = (1.0f - m_DecayFallAlpha) * m_DecayAve + - m_DecayFallAlpha * m_Peak; - } - } - else - { - // using exponential decay mode - // perform average of magnitude using 2 averagers each with separate rise and fall time constants - if (m_Peak > m_AttackAve) //if magnitude is rising (use m_AttackRiseAlpha time constant) - m_AttackAve = (1.0f - m_AttackRiseAlpha) * m_AttackAve + - m_AttackRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_AttackFallAlpha time constant) - m_AttackAve = (1.0f - m_AttackFallAlpha) * m_AttackAve + - m_AttackFallAlpha * m_Peak; - - if (m_Peak > m_DecayAve) - // if magnitude is rising (use m_DecayRiseAlpha time constant) - m_DecayAve = (1.0f - m_DecayRiseAlpha) * m_DecayAve + - m_DecayRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_DecayFallAlpha time constant) - m_DecayAve = (1.0f - m_DecayFallAlpha) * m_DecayAve + - m_DecayFallAlpha * m_Peak; - } - - // use greater magnitude of attack or Decay Averager - if (m_AttackAve > m_DecayAve) - mag = m_AttackAve; - else - mag = m_DecayAve; - - // calc gain depending on which side of knee the magnitude is on - if (mag <= m_Knee) - // use fixed gain if below knee - gain = m_FixedGain; - else - // use variable gain if above knee - gain = AGC_OUTSCALE * powf(10.0f, mag * (m_GainSlope - 1.0f)); - - pOutData[i] = delayedin * gain; - } - } - else - { - // manual gain just multiply by m_ManualGain - for (int i = 0; i < Length; i++) - { - pOutData[i] = m_ManualAgcGain * pInData[i]; - } - } -} diff --git a/src/dsp/agc_impl.h b/src/dsp/agc_impl.h deleted file mode 100644 index 6ec6391c5..000000000 --- a/src/dsp/agc_impl.h +++ /dev/null @@ -1,79 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// agc_impl.h: interface for the CAgc class. -// -// This class implements an automatic gain function. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-09-24 Adapted for gqrx -////////////////////////////////////////////////////////////////////// -#ifndef AGC_IMPL_H -#define AGC_IMPL_H - -#include -#include - -#define MAX_DELAY_BUF 2048 - -/* -typedef struct _dCplx -{ - double re; - double im; -} tDComplex; - -#define TYPECPX tDComplex -*/ - -#define TYPECPX std::complex - - -class CAgc -{ -public: - CAgc(); - virtual ~CAgc(); - void SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, int Slope, int Decay, float SampleRate); - void ProcessData(int Length, const TYPECPX * pInData, TYPECPX * pOutData); - -private: - bool m_AgcOn; - bool m_UseHang; - int m_Threshold; - int m_ManualGain; - int m_Decay; - - float m_SampleRate; - - float m_SlopeFactor; - float m_ManualAgcGain; - - float m_DecayAve; - float m_AttackAve; - - float m_AttackRiseAlpha; - float m_AttackFallAlpha; - float m_DecayRiseAlpha; - float m_DecayFallAlpha; - - float m_FixedGain; - float m_Knee; - float m_GainSlope; - float m_Peak; - - int m_SigDelayPtr; - int m_MagBufPos; - int m_DelaySamples; - int m_WindowSamples; - int m_HangTime; - int m_HangTimer; - - TYPECPX m_SigDelayBuf[MAX_DELAY_BUF]; - float* m_SigDelayBuf_r; - - float m_MagBuf[MAX_DELAY_BUF]; - std::deque m_MagDeque; -}; - -#endif // AGC_IMPL_H diff --git a/src/dsp/rx_agc_xx.cpp b/src/dsp/rx_agc_xx.cpp index ad5f9744f..972f7b95d 100644 --- a/src/dsp/rx_agc_xx.cpp +++ b/src/dsp/rx_agc_xx.cpp @@ -24,41 +24,91 @@ #include #include #include +#include -rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, bool use_hang) +#define NO_AGC_DEBUG + +#define exp10f(K) powf(10.0,(K)) +#define exp10(K) pow(10.0,(K)) + +#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 panning) { - return gnuradio::get_initial_sptr(new rx_agc_cc(sample_rate, agc_on, threshold, - manual_gain, slope, decay, - use_hang)); + return gnuradio::get_initial_sptr(new rx_agc_2f(sample_rate, agc_on, target_level, + manual_gain, max_gain, attack, decay, + hang, panning)); } /** * \brief Create receiver AGC object. * - * Use make_rx_agc_cc() instead. + * Use make_rx_agc_2f() instead. */ -rx_agc_cc::rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, bool use_hang) - : gr::sync_block ("rx_agc_cc", - gr::io_signature::make(1, 1, sizeof(gr_complex)), - gr::io_signature::make(1, 1, sizeof(gr_complex))), +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 panning) + : gr::sync_block ("rx_agc_2f", + gr::io_signature::make(2, 2, sizeof(float)), + gr::io_signature::make(4, 4, sizeof(float))), d_agc_on(agc_on), d_sample_rate(sample_rate), - d_threshold(threshold), + d_target_level(target_level), d_manual_gain(manual_gain), - d_slope(slope), + d_max_gain(max_gain), + d_attack(attack), d_decay(decay), - d_use_hang(use_hang) + d_hang(hang), + d_panning(panning), + d_mute(false), + d_target_mag(1), + d_hang_samp(0), + d_buf_samples(0), + d_buf_size(0), + d_max_idx(0), + d_buf_p(0), + d_hang_counter(0), + d_max_gain_mag(1.0), + d_current_gain(1.0), + d_target_gain(1.0), + 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, d_panning, true); + set_history(MAX_SAMPLE_RATE + 1 + MAX_SAMPLE_RATE * 100 / PANNING_DELAY_K); + set_tag_propagation_policy(TPP_DONT); +} + +rx_agc_2f::~rx_agc_2f() +{ +} + +bool rx_agc_2f::start() { - d_agc = new CAgc(); - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + d_running = true; + return gr::sync_block::start(); } -rx_agc_cc::~rx_agc_cc() +bool rx_agc_2f::stop() { - delete d_agc; + d_running = false; + return gr::sync_block::stop(); } /** @@ -67,15 +117,139 @@ rx_agc_cc::~rx_agc_cc() * \param input_items * \param output_items */ -int rx_agc_cc::work(int noutput_items, +int rx_agc_2f::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { - const gr_complex *in = (const gr_complex *) input_items[0]; - gr_complex *out = (gr_complex *) output_items[0]; + const float *in0 = (const float *) input_items[0]; + const float *in1 = (const float *) input_items[1]; + float *out0 = (float *) output_items[0]; + float *out1 = (float *) output_items[1]; + float *out2 = (float *) output_items[2]; + float *out3 = (float *) output_items[3]; std::lock_guard lock(d_mutex); - d_agc->ProcessData(noutput_items, in, out); + + int k; + TYPEFLOAT max_out = 0; + TYPEFLOAT mag_in = 0; + if (d_agc_on) + { + std::vector work_tags; + get_tags_in_window(work_tags, 0, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(0, tag.offset + d_buf_samples, tag.key, tag.value); + get_tags_in_window(work_tags, 1, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(1, tag.offset + d_buf_samples, tag.key, tag.value); + if (d_refill) + { + d_refill = false; + int p = history() - 1 - d_buf_size; + for (k = 0; k < d_buf_size; k++, p++) + { + float sample_in0 = in0[p]; + float sample_in1 = in1[p]; + mag_in = std::max(fabs(sample_in0),fabs(sample_in1)); + d_mag_buf[k] = mag_in; + update_buffer(k); + } + } + for (k = 0; k < noutput_items; k++) + { + int k_hist = k + history() - 1; + 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]; + + 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_size) + buf_p_next = 0; + + if (max_out > d_floor) + { + float new_target = d_target_mag / max_out; + if (new_target < d_target_gain) + { + if (d_current_gain > d_target_gain) + d_hang_counter = d_buf_samples + d_hang_samp; + d_target_gain = new_target; + } + else + if (!d_hang_counter) + d_target_gain = new_target; + } + else + { + d_target_gain = d_max_gain_mag; + d_hang_counter = 0; + } + if (d_current_gain > d_target_gain) + { + //attack, decrease gain one step per sample + d_current_gain *= d_attack_step; + } + else + { + if (d_hang_counter <= 0) + { + //decay, increase gain one step per sample until we reach d_max_gain + if (d_current_gain < d_target_gain) + d_current_gain *= d_decay_step; + if (d_current_gain > d_target_gain) + d_current_gain = d_target_gain; + } + } + if (d_hang_counter > 0) + d_hang_counter--; + if (d_current_gain < MIN_GAIN) + d_current_gain = MIN_GAIN; + if (d_mute) + { + out2[k] = 0.0; + out3[k] = 0.0; + }else{ + out2[k] = in0[k_hist - d_buf_samples - d_delay_l] * d_current_gain * d_gain_l; + out3[k] = in1[k_hist - d_buf_samples - d_delay_r] * d_current_gain * d_gain_r; + } + out0[k] = sample_out0 * d_current_gain; + out1[k] = sample_out1 * d_current_gain; + d_buf_p = buf_p_next; + } + } + else{ + std::vector work_tags; + get_tags_in_window(work_tags, 0, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(0, tag.offset, tag.key, tag.value); + get_tags_in_window(work_tags, 1, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(1, tag.offset, tag.key, tag.value); + if (d_mute) + { + std::memset(out2, 0, sizeof(float) * noutput_items); + std::memset(out3, 0, sizeof(float) * noutput_items); + }else{ + volk_32f_s32f_multiply_32f((float *)out2, (float *)&in0[history() - 1 - d_delay_l], d_current_gain * d_gain_l, noutput_items); + volk_32f_s32f_multiply_32f((float *)out3, (float *)&in1[history() - 1 - d_delay_r], d_current_gain * d_gain_r, noutput_items); + } + 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); + } + #ifdef AGC_DEBUG2 + static TYPEFLOAT d_prev_dbg = 0.0; + if(d_prev_dbg != d_target_gain) + { + std::cerr<<"------ d_target_gain="<= -160) && (threshold <= 0)) { + if ((target_level != d_target_level) && (target_level >= -160) && (target_level <= 0)) { std::lock_guard lock(d_mutex); - d_threshold = threshold; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, d_panning); } } /** * \brief Set new manual gain. - * \param gain The new manual gain between 0 and 100dB. + * \param gain The new manual gain between -160 and 160dB. * * The manual gain is used when AGC is switched off. * * \sa set_agc_on() */ -void rx_agc_cc::set_manual_gain(int gain) +void rx_agc_2f::set_manual_gain(float gain) { - if ((gain != d_manual_gain) && (gain >= 0) && (gain <= 100)) { - std::lock_guard lock(d_mutex); + if(d_agc_on) + { d_manual_gain = gain; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + return; + } + if ((gain != d_manual_gain) && (gain >= -160.f) && (gain <= 160.f)) { + 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, d_panning); } } /** - * \brief Set AGC slope factor. - * \param slope The new slope factor between 0 and 10dB. + * \brief Set new max gain. + * \param gain The new max gain between 0 and 100dB. * - * The slope factor specifies dB reduction in output at knee from maximum output level + * Limits maximum AGC gain to reduce noise. + * + * \sa set_agc_on() */ -void rx_agc_cc::set_slope(int slope) +void rx_agc_2f::set_max_gain(int gain) { - if ((slope != d_slope) && (slope >= 0) && (slope <= 10)) { + if ((gain != d_max_gain) && (gain >= 0) && (gain <= 160)) { std::lock_guard lock(d_mutex); - d_slope = slope; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, gain, d_attack, d_decay, d_hang, d_panning); + } +} + +/** + * \brief Set AGC attack time. + * \param decay The new AGC attack time between 20 to 5000 ms. + * + * Sets length of the delay buffer + * + */ +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, d_panning); } } @@ -169,26 +355,203 @@ void rx_agc_cc::set_slope(int slope) * \brief Set AGC decay time. * \param decay The new AGC decay time between 20 to 5000 ms. */ -void rx_agc_cc::set_decay(int decay) +void rx_agc_2f::set_decay(int decay) { if ((decay != d_decay) && (decay >= 20) && (decay <= 5000)) { std::lock_guard lock(d_mutex); - d_decay = decay; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, decay, d_hang, d_panning); + } +} + +/** + * \brief Set AGC hang time between 0 to 5000 ms. + * \param hang Time to keep AGC gain at constant level after the peak. + */ +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, 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); } } /** - * \brief Enable/disable AGC hang. - * \param use_hang Whether to use hang or not. + * \brief Set mute enabled or disabled (audio output only). + * \param mute New mute state. */ -void rx_agc_cc::set_use_hang(bool use_hang) +void rx_agc_2f::set_mute(bool mute) { - if (use_hang != d_use_hang) { + if (mute != d_mute) { std::lock_guard lock(d_mutex); - d_use_hang = use_hang; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + d_mute = mute; + } +} + +float rx_agc_2f::get_current_gain() +{ + std::lock_guard lock(d_mutex); + return 20.f * log10f(d_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, int panning, bool force) +{ + bool samp_rate_changed = false; + bool agc_on_changed = false; + bool target_level_changed = false; + bool manual_gain_changed = false; + bool max_gain_changed = false; + bool attack_changed = false; + bool decay_changed = false; + bool hang_changed = false; + + if (d_sample_rate != sample_rate || force) + { + d_sample_rate = sample_rate; + samp_rate_changed = true; + } + if (d_agc_on != agc_on || force) + { + d_agc_on = agc_on; + agc_on_changed = true; + if(d_agc_on && d_running) + d_refill = true; + } + if (d_target_level != target_level || force) + { + d_target_level = target_level; + d_target_mag = exp10f(TYPEFLOAT(d_target_level) / 20.f) * 32767.f / 32768.f; + target_level_changed = true; + } + if (d_manual_gain != manual_gain || force) + { + d_manual_gain = manual_gain; + manual_gain_changed = true; + } + if (d_max_gain != max_gain || force) + { + d_max_gain = max_gain; + if(d_max_gain < 1) + d_max_gain = 1; + d_max_gain_mag = exp10f(TYPEFLOAT(d_max_gain) / 20.f); + max_gain_changed = true; + } + if (d_attack != attack || force) + { + d_attack = attack; + attack_changed = true; + } + if (d_decay != decay || force) + { + d_decay = decay; + decay_changed = true; + } + if (d_hang != hang || force) + { + 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 >= buf_samples) + break; + } + if(d_buf_size != buf_size) + { + d_buf_size = buf_size; + d_mag_buf.clear(); + d_mag_buf.resize(d_buf_size * 2, 0); + d_buf_p = 0; + d_max_idx = d_buf_size * 2 - 2; + 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 = exp10f(TYPEFLOAT(d_manual_gain) / 20.f); + if (max_gain_changed || attack_changed || samp_rate_changed) + d_attack_step = 1.f / exp10f(std::max(TYPEFLOAT(d_max_gain), - MIN_GAIN_DB) / TYPEFLOAT(d_buf_samples) / 20.f); + if (max_gain_changed || decay_changed || samp_rate_changed) + d_decay_step = exp10f(TYPEFLOAT(d_max_gain) / TYPEFLOAT(sample_rate * d_decay / 1000.0) / 20.f); + if (hang_changed || samp_rate_changed) + d_hang_samp = sample_rate * d_hang / 1000.0; + + if (target_level_changed || max_gain_changed) + d_floor = exp10f(TYPEFLOAT(d_target_level - d_max_gain) / 20.f); + #ifdef AGC_DEBUG + std::cerr< 1) + { + float max_p = std::max(d_mag_buf[ofs + p], d_mag_buf[ofs + (p ^ 1)]); + p = p >> 1; + ofs += base; + if(d_mag_buf[ofs + p] != max_p) + d_mag_buf[ofs + p] = max_p; + else + break; + base = base >> 1; + } + } diff --git a/src/dsp/rx_agc_xx.h b/src/dsp/rx_agc_xx.h index b8ad21f4e..d6d97a7d9 100644 --- a/src/dsp/rx_agc_xx.h +++ b/src/dsp/rx_agc_xx.h @@ -26,36 +26,41 @@ #include #include #include -#include -class rx_agc_cc; +#define TYPECPX std::complex +#define TYPEFLOAT float + +class rx_agc_2f; #if GNURADIO_VERSION < 0x030900 -typedef boost::shared_ptr rx_agc_cc_sptr; +typedef boost::shared_ptr rx_agc_2f_sptr; #else -typedef std::shared_ptr rx_agc_cc_sptr; +typedef std::shared_ptr rx_agc_2f_sptr; #endif /** * \brief Return a shared_ptr to a new instance of rx_agc_cc. - * \param sample_rate The sample rate (default = 96000). - * \param agc_on Whether AGC should be ON (default = true). - * \param threshold AGC Knee in dB if AGC is active. Range -160 to 0 dB. - * \param manual_gain Manual gain when AGC is OFF. Range 0 to 100 dB. - * \param slope AGC slope factor. Specifies dB reduction in output at - * knee from maximum output level. Range 0 to 10 dB - * \param decay AGC decay time in milliseconds. Range 20 to 5000. This - * parameter determines whether AGC is fast, slow or medium. - * \param use_hang Whether AGC should "hang" before starting to decay. + * \param sample_rate The sample rate (default = 96000). + * \param agc_on Whether AGC should be ON (default = true). + * \param target_level Target output level in dB if AGC is active. Range -160 to 0 dB. + * \param manual_gain Manual gain when AGC is OFF. Range -160 to 160 dB. + * \param max_gain Maximum gain when AGC is ON. Range 0 to 100 dB. + * \param attack AGC maximum attack time in milliseconds. Range 20 to 5000. This + * parameter determines whether AGC is fast, slow or medium. + * It is recommenfded to set it below 1000 ms to reduce audio lag. + * \param decay AGC decay time in milliseconds. Range 20 to 5000. This + * parameter determines whether AGC is fast, slow or medium. + * \param hang The time AGC should "hang" before starting to decay in + * milliseconds. Range 0 to 5000. * * This is effectively the public constructor for a new AGC block. * To avoid accidental use of raw pointers, the rx_agc_cc constructor is private. * make_rx_agc_cc is the public interface for creating new instances. */ -rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, - bool use_hang); +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 panning); /** * \brief Experimental AGC block for analog voice modes (AM, SSB, CW). @@ -64,42 +69,81 @@ rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, * This block performs automatic gain control. * To be written... */ -class rx_agc_cc : public gr::sync_block +class rx_agc_2f : public gr::sync_block { - friend rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, - int threshold, int manual_gain, - int slope, int decay, bool use_hang); + 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 panning); protected: - rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, - int slope, int decay, bool use_hang); + 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 panning); public: - ~rx_agc_cc(); + ~rx_agc_2f(); + bool start() override; + bool stop() override; int work(int noutput_items, gr_vector_const_void_star &input_items, - gr_vector_void_star &output_items); + gr_vector_void_star &output_items) override; void set_agc_on(bool agc_on); void set_sample_rate(double sample_rate); - void set_threshold(int threshold); - void set_manual_gain(int gain); - void set_slope(int slope); + void set_target_level(int target_level); + void set_manual_gain(float gain); + void set_max_gain(int gain); + void set_attack(int attack); void set_decay(int decay); - void set_use_hang(bool use_hang); + void set_hang(int hang); + void set_panning(int panning); + void set_mute(bool mute); + float get_current_gain(); private: - CAgc *d_agc; + void set_parameters(double sample_rate, bool agc_on, int target_level, + float manual_gain, int max_gain, int attack, int decay, + int hang, int panning, bool force = false); + std::mutex d_mutex; /*! Used to lock internal data while processing or setting parameters. */ bool d_agc_on; /*! Current AGC status (true/false). */ double d_sample_rate; /*! Current sample rate. */ - int d_threshold; /*! Current AGC threshold (-160...0 dB). */ - int d_manual_gain; /*! Current gain when AGC is OFF. */ - int d_slope; /*! Current AGC slope (0...10 dB). */ + int d_target_level; /*! SGC target level (-160...0 dB). */ + float d_manual_gain; /*! Current gain when AGC is OFF. */ + int d_max_gain; /*! Maximum gain when AGC is ON. */ + int d_attack; /*! Current AGC attack (20...5000 ms). */ int d_decay; /*! Current AGC decay (20...5000 ms). */ - bool d_use_hang; /*! Current AGC hang status (true/false). */ + int d_hang; /*! Current AGC hang (0...5000 ms). */ + int d_panning; /*! Current AGC panning (-100...100). */ + int d_mute; /*! Current AGC mute state. */ +private: + float get_peak(); + void update_buffer(int p); + + TYPEFLOAT d_target_mag; + int d_hang_samp; + int d_buf_samples; + int d_buf_size; + int d_max_idx; + int d_buf_p; + int d_hang_counter; + TYPEFLOAT d_max_gain_mag; + TYPEFLOAT d_current_gain; + TYPEFLOAT d_target_gain; + 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; + bool d_running; }; #endif /* RX_AGC_XX_H */ diff --git a/src/dsp/rx_demod_am.cpp b/src/dsp/rx_demod_am.cpp index 54a4831e7..90e09678b 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 ff25be4e3..94b5c90f7 100644 --- a/src/dsp/rx_filter.h +++ b/src/dsp/rx_filter.h @@ -27,8 +27,7 @@ #include #include - -#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/dsp/rx_squelch.cpp b/src/dsp/rx_squelch.cpp new file mode 100644 index 000000000..0bc2bb3df --- /dev/null +++ b/src/dsp/rx_squelch.cpp @@ -0,0 +1,122 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2012-2013 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. + */ +#include +#include +#include +#include +#include "dsp/rx_squelch.h" + +rx_sql_cc_sptr make_rx_sql_cc(double db, double alpha) +{ + return gnuradio::get_initial_sptr(new rx_sql_cc(db, alpha)); +} + + +rx_sql_cc::rx_sql_cc(double db, double alpha) + : gr::hier_block2 ("rx_sql_cc", + gr::io_signature::make(1, 1, sizeof(gr_complex)), + gr::io_signature::make(1, 1, sizeof(gr_complex))) +{ + d_impl = SQL_SIMPLE; + sql_pwr = gr::analog::pwr_squelch_cc::make(db, alpha); + sql_simple = gr::analog::simple_squelch_cc::make(db, alpha); + connect(self(), 0, sql_simple, 0); + connect(sql_simple, 0, self(), 0); +} + +rx_sql_cc::~rx_sql_cc() +{ +} + +double rx_sql_cc::threshold() +{ + switch (d_impl) + { + case SQL_SIMPLE: + return sql_simple->threshold(); + case SQL_PWR: + return sql_pwr->threshold(); + } + return 0.0; +} + +void rx_sql_cc::set_threshold(double db) +{ + sql_simple->set_threshold(db); + sql_pwr->set_threshold(db); +} + +void rx_sql_cc::set_alpha(double alpha) +{ + sql_simple->set_alpha(alpha); + sql_pwr->set_alpha(alpha); +} + +bool rx_sql_cc::unmuted() +{ + switch (d_impl) + { + case SQL_SIMPLE: + return sql_simple->unmuted(); + case SQL_PWR: + return sql_pwr->unmuted(); + } + return false; +} + +void rx_sql_cc::set_impl(rx_sql_cc::sql_impl_t impl) +{ + if(d_impl == impl) + return; + lock(); + switch (d_impl) + { + case SQL_SIMPLE: + disconnect(self(), 0, sql_simple, 0); + disconnect(sql_simple, 0, self(), 0); + break; + case SQL_PWR: + disconnect(self(), 0, sql_pwr, 0); + disconnect(sql_pwr, 0, self(), 0); + break; + } + switch (impl) + { + case SQL_SIMPLE: + connect(self(), 0, sql_simple, 0); + connect(sql_simple, 0, self(), 0); + break; + case SQL_PWR: + connect(self(), 0, sql_pwr, 0); + connect(sql_pwr, 0, self(), 0); + break; + } + unlock(); + d_impl = impl; +} + +rx_sql_cc::sql_impl_t rx_sql_cc::get_impl() +{ + return d_impl; +} + diff --git a/src/dsp/rx_squelch.h b/src/dsp/rx_squelch.h new file mode 100644 index 000000000..95f27efe9 --- /dev/null +++ b/src/dsp/rx_squelch.h @@ -0,0 +1,78 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2012-2013 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 RX_SQUELCH_CC_H +#define RX_SQUELCH_CC_H + +#include +#include +#include +#include + + +class rx_sql_cc; + +#if GNURADIO_VERSION < 0x030900 +typedef boost::shared_ptr rx_sql_cc_sptr; +#else +typedef std::shared_ptr rx_sql_cc_sptr; +#endif + +/*! \brief Return a shared_ptr to a new instance of rx_sql_cc. + * \param db threshold (in dB) for squelch + * \param alpha Gain of averaging filter. Defaults to 0.0001. + */ +rx_sql_cc_sptr make_rx_sql_cc(double db, double alpha = 0.0001); + +/*! \brief Squelch implementation switching block. + * \ingroup DSP + * + * This block allows to select between simple_squelch and pwr_squelch blocks + */ +class rx_sql_cc : public gr::hier_block2 +{ + friend rx_sql_cc_sptr make_rx_sql_cc(double db, double alpha); + +protected: + rx_sql_cc(double db, double alpha); + +public: + typedef enum{ + SQL_SIMPLE = 0, + SQL_PWR + } sql_impl_t; + + ~rx_sql_cc(); + double threshold(); + void set_threshold(double db); + void set_alpha(double alpha); + bool unmuted(); + void set_impl(sql_impl_t impl); + sql_impl_t get_impl(); + +private: + gr::analog::pwr_squelch_cc::sptr sql_pwr; /*!< Pwr Squelch (squelch-triggered recording mode). */ + gr::analog::simple_squelch_cc::sptr sql_simple; /*!< Simple Squelch (generic mode). */ + sql_impl_t d_impl; +}; + +#endif /* RX_SQUELCH_CC_H */ diff --git a/src/interfaces/CMakeLists.txt b/src/interfaces/CMakeLists.txt index 8038698b6..4ae338484 100644 --- a/src/interfaces/CMakeLists.txt +++ b/src/interfaces/CMakeLists.txt @@ -3,4 +3,6 @@ add_source_files(SRCS_LIST udp_sink_f.cpp udp_sink_f.h + wav_sink.cpp + wav_sink.h ) diff --git a/src/interfaces/wav_sink.cpp b/src/interfaces/wav_sink.cpp new file mode 100644 index 000000000..ce53d9f73 --- /dev/null +++ b/src/interfaces/wav_sink.cpp @@ -0,0 +1,529 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2008,2009,2013 Free Software Foundation, Inc. + * 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 "wav_sink.h" +#include +#include +#include +#include +#include +#include + +static const int SQL_REC_MIN_TIME = 10; /* Minimum squelch recorder time, seconds. */ +static const int SQL_REC_MAX_GAP = 10; /* Maximum squelch recorder gap, seconds. */ + +wavfile_sink_gqrx::sptr wavfile_sink_gqrx::make(const char* filename, + int n_channels, + unsigned int sample_rate, + wavfile_format_t format, + wavfile_subformat_t subformat, + bool append) +{ + return gnuradio::get_initial_sptr(new wavfile_sink_gqrx( + filename, n_channels, sample_rate, format, subformat, append)); +} + +wavfile_sink_gqrx::wavfile_sink_gqrx(const char* filename, + int n_channels, + unsigned int sample_rate, + wavfile_format_t format, + wavfile_subformat_t subformat, + bool append) + : sync_block("wavfile_sink_gqrx", + gr::io_signature::make(1, n_channels, sizeof(float)), + gr::io_signature::make(0, 0, 0)), + d_h{}, // Init with zeros + d_append(append), + d_fp(nullptr), + d_new_fp(nullptr), + d_updated(false), + d_center_freq(0), + d_offset(0), + d_rec_dir(""), + d_squelch_triggered(false), + d_min_time_ms(0), + d_max_gap_ms(0), + d_min_time_samp(0), + d_max_gap_samp(0), + d_prev_action(ACT_NONE), + d_prev_roffset(0) +{ + int bits_per_sample = 16; + + if (n_channels > s_max_channels) + throw std::runtime_error("Number of channels greater than " + + std::to_string(s_max_channels) + " not supported."); + + d_h.sample_rate = sample_rate; + d_h.nchans = n_channels; + d_h.format = format; + d_h.subformat = subformat; + switch (subformat) { + case FORMAT_PCM_S8: + bits_per_sample = 8; + break; + case FORMAT_PCM_16: + bits_per_sample = 16; + break; + case FORMAT_PCM_24: + bits_per_sample = 24; + break; + case FORMAT_PCM_32: + bits_per_sample = 32; + break; + case FORMAT_PCM_U8: + bits_per_sample = 8; + break; + case FORMAT_FLOAT: + bits_per_sample = 32; + break; + case FORMAT_DOUBLE: + bits_per_sample = 64; + break; + case FORMAT_VORBIS: + bits_per_sample = 32; + break; + } + set_bits_per_sample_unlocked(bits_per_sample); + d_h.bytes_per_sample = d_bytes_per_sample_new; + + set_max_noutput_items(s_items_size); + d_buffer.resize(s_items_size * d_h.nchans); + + if (filename) + if (!open(filename)) + throw std::runtime_error("Can't open WAV file."); + //FIXME Make this configurable? + d_sob_key = pmt::intern("squelch_sob"); + d_eob_key = pmt::intern("squelch_eob"); + set_history(1 + sample_rate * (SQL_REC_MIN_TIME + SQL_REC_MAX_GAP)); +} + +void wavfile_sink_gqrx::set_center_freq(double center_freq) +{ + std::unique_lock guard(d_mutex); + d_center_freq = center_freq; +} + +void wavfile_sink_gqrx::set_offset(double offset) +{ + std::unique_lock guard(d_mutex); + d_offset = offset; +} + +void wavfile_sink_gqrx::set_rec_dir(std::string dir) +{ + std::unique_lock guard(d_mutex); + d_rec_dir = dir; +} + + +bool wavfile_sink_gqrx::open(const char* filename) +{ + std::unique_lock guard(d_mutex); + return open_unlocked(filename); +} + +bool wavfile_sink_gqrx::open_unlocked(const char* filename) +{ + SF_INFO sfinfo; + + if (d_new_fp) { // if we've already got a new one open, close it + sf_close(d_new_fp); + d_new_fp = nullptr; + } + + if (d_append) { + // We are appending to an existing file, be extra careful here. + sfinfo.format = 0; + if (!(d_new_fp = sf_open(filename, SFM_RDWR, &sfinfo))) { + std::cerr << "sf_open failed: " << filename << " " << strerror(errno) << std::endl; + return false; + } + if (d_h.sample_rate != sfinfo.samplerate || d_h.nchans != sfinfo.channels || + d_h.format != (sfinfo.format & SF_FORMAT_TYPEMASK) || + d_h.subformat != (sfinfo.format & SF_FORMAT_SUBMASK)) { + std::cerr << "Existing WAV file is incompatible with configured options."< guard(d_mutex); + return open_new_unlocked(); +} + +int wavfile_sink_gqrx::open_new_unlocked() +{ + // FIXME: option to use local time + // use toUTC() function compatible with older versions of Qt. + QString file_name = QDateTime::currentDateTime().toUTC().toString("gqrx_yyyyMMdd_hhmmss"); + QString filename = QString("%1/%2_%3.wav").arg(QString(d_rec_dir.data())).arg(file_name).arg(qint64(d_center_freq + d_offset)); + if (open_unlocked(filename.toStdString().data())) + { + if (d_rec_event) + d_rec_event(d_filename = filename.toStdString(), true); + return 0; + } + return 1; +} + +void wavfile_sink_gqrx::close() +{ + std::unique_lock guard(d_mutex); + + if (!d_fp) + return; + close_wav(); +} + +void wavfile_sink_gqrx::close_wav() +{ + sf_write_sync(d_fp); + sf_close(d_fp); + d_fp = nullptr; + if (d_rec_event) + d_rec_event(d_filename, false); +} + +wavfile_sink_gqrx::~wavfile_sink_gqrx() +{ + set_rec_event_handler(nullptr); + if (d_new_fp) { + sf_close(d_new_fp); + d_new_fp = nullptr; + } + close(); + +} + +bool wavfile_sink_gqrx::stop() +{ + if (d_fp) + sf_write_sync(d_fp); + return true; +} + +int wavfile_sink_gqrx::work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) +{ + auto in = (float**)&input_items[0]; + int n_in_chans = input_items.size(); + int hist = history() - 1; + int nwritten = hist; + int writecount = noutput_items; + std::vector work_tags; + std::unique_lock guard(d_mutex); // hold mutex for duration of this block + int roffset = 0; /** relative offset*/ + + + if (d_squelch_triggered) + { + uint64_t abs_N = nitems_read(0); + get_tags_in_window(work_tags, 0, 0, noutput_items); + for (const auto& tag : work_tags) + { + roffset = (tag.offset - abs_N); + if (tag.key == d_sob_key) + { + if (d_prev_action == ACT_CLOSE) + { + if (roffset + hist - d_prev_roffset <= d_max_gap_samp) + { + if (d_fp) + { + writeout(d_prev_roffset, roffset + hist - d_prev_roffset, n_in_chans, in); + nwritten = roffset + hist; + writecount = noutput_items - roffset; + } + d_prev_action = ACT_NONE; + } + else + { + if (d_fp) + close_wav(); + } + } + d_prev_roffset = roffset + hist; + if (!d_fp) + d_prev_action = ACT_OPEN; + } + if (tag.key == d_eob_key) + { + if (d_prev_action == ACT_OPEN) + { + if (!d_fp && (roffset + hist - d_prev_roffset >= d_min_time_samp)) + { + open_new_unlocked(); + do_update(); + if (d_fp) + writeout(d_prev_roffset, roffset + hist - d_prev_roffset, n_in_chans, in); + } + } + if (d_fp) + d_prev_action = ACT_CLOSE; + else + d_prev_action = ACT_NONE; + d_prev_roffset = roffset + hist; + } + } + } + switch(d_prev_action) + { + case ACT_NONE: + do_update(); // update: d_fp is read + if (d_fp && writecount) + writeout(nwritten, writecount, n_in_chans, in); + break; + case ACT_OPEN: + if (hist - d_prev_roffset >= d_min_time_samp) + { + d_prev_action = ACT_NONE; + if (!d_fp) + { + open_new_unlocked(); + do_update(); + if (d_fp) + writeout(d_prev_roffset, hist - d_prev_roffset + writecount, n_in_chans, in); + } + } + break; + case ACT_CLOSE: + if (hist - d_prev_roffset >= d_max_gap_samp) + { + if (d_fp) + { + close_wav(); + } + d_prev_action = ACT_NONE; + } + break; + } + d_prev_roffset -= noutput_items; + if (d_prev_roffset < 0) + d_prev_roffset = 0; + return noutput_items; +} + +void wavfile_sink_gqrx::writeout(const int offset, const int writecount, const int n_in_chans, float** in) +{ + int nchans = d_h.nchans; + int nwritten = 0; + int bp = 0; + int errnum; + while (nwritten < writecount) + { + for (bp = 0; (nwritten < writecount) && (bp < s_items_size); nwritten++, bp++) + { + for (int chan = 0; chan < nchans; chan++) + { + // Write zeros to channels which are in the WAV file + // but don't have any inputs here + if (chan < n_in_chans) + d_buffer[chan + (bp * nchans)] = in[chan][nwritten + offset]; + else + d_buffer[chan + (bp * nchans)] = 0; + } + } + sf_write_float(d_fp, &d_buffer[0], nchans * bp); + + errnum = sf_error(d_fp); + if (errnum) { + std::cerr << "sf_error: " << sf_error_number(errnum) << std::endl; + close(); + throw std::runtime_error("File I/O error."); + } + } +} + +void wavfile_sink_gqrx::set_sql_triggered(const bool enabled) +{ + 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) +{ + std::unique_lock guard(d_mutex); + set_bits_per_sample_unlocked(bits_per_sample); +} + +void wavfile_sink_gqrx::set_bits_per_sample_unlocked(int bits_per_sample) +{ + d_bytes_per_sample_new = bits_per_sample / 8; +} + +void wavfile_sink_gqrx::set_append(bool append) +{ + std::unique_lock guard(d_mutex); + d_append = append; +} + +void wavfile_sink_gqrx::set_sample_rate(unsigned int sample_rate) +{ + std::unique_lock guard(d_mutex); + d_h.sample_rate = sample_rate; +} + +int wavfile_sink_gqrx::bits_per_sample() { return d_bytes_per_sample_new; } + +unsigned int wavfile_sink_gqrx::sample_rate() { return d_h.sample_rate; } + +void wavfile_sink_gqrx::do_update() +{ + if (!d_updated) + return; + + if (d_fp) + close_wav(); + + d_fp = d_new_fp; // install new file pointer + d_new_fp = nullptr; + + d_h.bytes_per_sample = d_bytes_per_sample_new; + // Avoid deadlock. + set_bits_per_sample_unlocked(8 * d_bytes_per_sample_new); + d_updated = false; +} + +void wavfile_sink_gqrx::set_rec_min_time(int min_time_ms) +{ + std::unique_lock guard(d_mutex); + d_min_time_ms = min_time_ms; + d_min_time_samp = d_min_time_ms * d_h.sample_rate / 1000; +/* int new_history = 1 + (d_min_time_ms + d_max_gap_ms) * d_h.sample_rate / 1000; + if (int(history()) < new_history) + set_history(new_history);*/ +} + +void wavfile_sink_gqrx::set_rec_max_gap(int max_gap_ms) +{ + std::unique_lock guard(d_mutex); + d_max_gap_ms = max_gap_ms; + d_max_gap_samp = max_gap_ms * d_h.sample_rate / 1000; +/* int new_history = 1 + (d_min_time_ms + d_max_gap_ms) * d_h.sample_rate / 1000; + if (int(history()) < new_history) + set_history(new_history);*/ +} + +int wavfile_sink_gqrx::get_min_time() +{ + return d_min_time_ms; +} + +int wavfile_sink_gqrx::get_max_gap() +{ + return d_max_gap_ms; +} diff --git a/src/interfaces/wav_sink.h b/src/interfaces/wav_sink.h new file mode 100644 index 000000000..a9b5d5151 --- /dev/null +++ b/src/interfaces/wav_sink.h @@ -0,0 +1,193 @@ +/* -*- c++ -*- */ +/* + * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt + * https://gqrx.dk/ + * + * Copyright 2008,2009,2013 Free Software Foundation, Inc. + * 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 GQRX_WAVFILE_SINK_C_H +#define GQRX_WAVFILE_SINK_C_H + +#include +#include // for SNDFILE +#include +#include + +class wavfile_sink_gqrx : virtual public gr::sync_block +{ +public: + typedef std::function rec_event_handler_t; +#if GNURADIO_VERSION < 0x030900 + typedef boost::shared_ptr sptr; +#else + typedef std::shared_ptr sptr; +#endif + + enum wavfile_format_t { + FORMAT_WAV = 0x010000, + FORMAT_FLAC = 0x170000, + FORMAT_OGG = 0x200000, + FORMAT_RF64 = 0x220000, + }; + + enum wavfile_subformat_t { + FORMAT_PCM_S8 = 1, + FORMAT_PCM_16, + FORMAT_PCM_24, + FORMAT_PCM_32, + FORMAT_PCM_U8, + FORMAT_FLOAT, + FORMAT_DOUBLE, + FORMAT_VORBIS = 0x0060, + }; + +private: + //! WAV file header information. + struct wav_header_info { + + //! sample rate [S/s] + int sample_rate; + + //! Number of channels + int nchans; + + //! Bytes per sample + int bytes_per_sample; + + //! Number of samples per channel + long long samples_per_chan; + + //! sndfile format + int format; + + //! sndfile format + int subformat; + }; + + + typedef enum{ + ACT_NONE=0, + ACT_OPEN, + ACT_CLOSE + } sql_action; + wav_header_info d_h; + int d_bytes_per_sample_new; + bool d_append; + + std::vector d_buffer; + + SNDFILE* d_fp; + SNDFILE* d_new_fp; + bool d_updated; + std::mutex d_mutex; + double d_center_freq; + double d_offset; + std::string d_rec_dir; + std::string d_filename; + + static constexpr int s_items_size = 8192; + static constexpr int s_max_channels = 24; + + rec_event_handler_t d_rec_event; + bool d_squelch_triggered; + pmt::pmt_t d_sob_key, d_eob_key; + int d_min_time_ms; + int d_max_gap_ms; + int d_min_time_samp; + int d_max_gap_samp; + sql_action d_prev_action; + int d_prev_roffset; + + /*! + * \brief If any file changes have occurred, update now. This is called + * internally by work() and thus doesn't usually need to be called by + * hand. + */ + void do_update(); + + /*! + * \brief Implementation of set_bits_per_sample without mutex lock. + */ + void set_bits_per_sample_unlocked(int bits_per_sample); + + /*! + * \brief Writes information to the WAV header which is not available + * a-priori (chunk size etc.) and closes the file. Not thread-safe and + * assumes d_fp is a valid file pointer, should thus only be called by + * other methods. + */ + void close_wav(); + +protected: + bool stop() override; + +public: + static sptr make(const char* filename, + int n_channels, + unsigned int sample_rate, + wavfile_format_t format, + wavfile_subformat_t subformat, + bool append = false); + wavfile_sink_gqrx(const char* filename, + int n_channels, + unsigned int sample_rate, + wavfile_format_t format, + wavfile_subformat_t subformat, + bool append = false); + ~wavfile_sink_gqrx() override; + + virtual void set_center_freq(double center_freq); + virtual void set_offset(double offset); + virtual void set_rec_dir(std::string dir); + template void set_rec_event_handler(T handler) + { + d_rec_event = handler; + } + bool open(const char* filename); + int open_new(); + void close(); + + void set_sample_rate(unsigned int sample_rate); + void set_bits_per_sample(int bits_per_sample); + + void set_append(bool append); + + int bits_per_sample(); + unsigned int sample_rate(); + + int work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) override; + void set_sql_triggered(const bool enabled); + bool get_sql_triggered() { return d_squelch_triggered; } + void set_rec_min_time(int min_time_ms); + int get_rec_min_time() { return d_min_time_ms; } + void set_rec_max_gap(int max_gap_ms); + 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(); + void writeout(const int offset, const int writecount, const int n_in_chans, float** in); +}; + +#endif /* GQRX_WAVFILE_SINK_C_H */ diff --git a/src/qtgui/agc_options.cpp b/src/qtgui/agc_options.cpp index 7d599c774..3412aa6c7 100644 --- a/src/qtgui/agc_options.cpp +++ b/src/qtgui/agc_options.cpp @@ -49,10 +49,10 @@ void CAgcOptions::closeEvent(QCloseEvent *event) event->ignore(); } -/*! \brief Get current gain slider value. */ -int CAgcOptions::gain() +/*! \brief Get current max gain slider value. */ +int CAgcOptions::maxGain() { - return ui->gainSlider->value(); + return ui->maxGainSlider->value(); } /*! \brief Set AGC preset. */ @@ -61,37 +61,39 @@ void CAgcOptions::setPreset(agc_preset_e preset) switch (preset) { case AGC_FAST: + setAttack(20); setDecay(100); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_MEDIUM: + setAttack(50); setDecay(500); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_SLOW: + setAttack(100); setDecay(2000); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_USER: + enableAttack(true); enableDecay(true); - enableSlope(true); - enableGain(false); + enableHang(true); break; case AGC_OFF: - enableGain(true); break; default: @@ -101,62 +103,50 @@ void CAgcOptions::setPreset(agc_preset_e preset) } } -/*! \brief Set new gain slider value. */ -void CAgcOptions::setGain(int value) +/*! \brief Set new max gain slider value. */ +void CAgcOptions::setMaxGain(int value) { - ui->gainSlider->setValue(value); - ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); + ui->maxGainSlider->setValue(value); + ui->maxGainLabel->setText(QString("%1 dB").arg(ui->maxGainSlider->value())); } -/*! \brief Enable or disable gain slider. - * \param enabled Whether the slider should be enabled or not. - * - * The gain slider is enabled when AGC is OFF to provide manual gain - * control. It is disabled when AGC is ON. - */ -void CAgcOptions::enableGain(bool enabled) +/*! \brief Get current AGC target level. */ +int CAgcOptions::targetLevel() { - ui->gainLabel->setEnabled(enabled); - ui->gainSlider->setEnabled(enabled); - ui->label1->setEnabled(enabled); + return ui->targetLevelSlider->value(); } -/*! \brief Get current AGC threshold. */ -int CAgcOptions::threshold() +/*! \brief Set new AGC target level. */ +void CAgcOptions::setTargetLevel(int value) { - return ui->thresholdSlider->value(); + ui->targetLevelSlider->setValue(value); + ui->targetLevelLabel->setText(QString("%1 dB").arg(ui->targetLevelSlider->value())); } -/*! \brief Set new AGC threshold. */ -void CAgcOptions::setThreshold(int value) -{ - ui->thresholdSlider->setValue(value); - ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); -} -/*! \brief Get current AGC slope. */ -int CAgcOptions::slope() +/*! \brief Get current attack value. */ +int CAgcOptions::attack() { - return ui->slopeSlider->value(); + return ui->attackSlider->value(); } -/*! \brief Set new AGC slope. */ -void CAgcOptions::setSlope(int value) +/*! \brief Set new attack value. */ +void CAgcOptions::setAttack(int value) { - ui->slopeSlider->setValue(value); - ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); + ui->attackSlider->setValue(value); + ui->attackLabel->setText(QString("%1 ms").arg(ui->attackSlider->value())); } -/*! \brief Enable or disable AGC slope slider. +/*! \brief Enable or disable AGC attack slider. * \param enabled Whether the slider should be enabled or not. * - * The slope slider is enabled when AGC is in user mode. + * The attack slider is enabled when AGC is in user mode. */ -void CAgcOptions::enableSlope(bool enabled) +void CAgcOptions::enableAttack(bool enabled) { - ui->slopeSlider->setEnabled(enabled); - ui->slopeLabel->setEnabled(enabled); - ui->label3->setEnabled(enabled); + ui->attackSlider->setEnabled(enabled); + ui->attackLabel->setEnabled(enabled); + ui->attackTitle->setEnabled(enabled); } /*! \brief Get current decay value. */ @@ -181,54 +171,108 @@ void CAgcOptions::enableDecay(bool enabled) { ui->decaySlider->setEnabled(enabled); ui->decayLabel->setEnabled(enabled); - ui->label4->setEnabled(enabled); + ui->decayTitle->setEnabled(enabled); +} + +/*! \brief Get current hang value. */ +int CAgcOptions::hang() +{ + return ui->hangSlider->value(); +} + +/*! \brief Set new hang value. */ +void CAgcOptions::setHang(int value) +{ + ui->hangSlider->setValue(value); + ui->hangLabel->setText(QString("%1 ms").arg(ui->hangSlider->value())); +} + +/*! \brief Enable or disable AGC hang slider. + * \param enabled Whether the slider should be enabled or not. + * + * The hang slider is enabled when AGC is in user mode. + */ +void CAgcOptions::enableHang(bool enabled) +{ + ui->hangSlider->setEnabled(enabled); + ui->hangLabel->setEnabled(enabled); + ui->hangTitle->setEnabled(enabled); } -/*! \brief Get current state of AGC hang button. */ -bool CAgcOptions::hang() +/*! \brief Get panning fixed position. */ +int CAgcOptions::panning() { - return ui->hangButton->isChecked(); + return ui->panningSlider->value(); } -/*! \brief Set state og AGC hang button. */ -void CAgcOptions::setHang(bool checked) +/*! \brief Set panning fixed position. */ +void CAgcOptions::setPanning(int value) { - ui->hangButton->setChecked(checked); + 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. */ -void CAgcOptions::on_gainSlider_valueChanged(int gain) +/*! \brief AGC max gain slider value has changed. */ +void CAgcOptions::on_maxGainSlider_valueChanged(int value) { - ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); - emit gainChanged(gain); + ui->maxGainLabel->setText(QString("%1 dB").arg(ui->maxGainSlider->value())); + emit maxGainChanged(value); } -/*! \brief AGC threshold slider value has changed. */ -void CAgcOptions::on_thresholdSlider_valueChanged(int threshold) +/*! \brief AGC target level slider value has changed. */ +void CAgcOptions::on_targetLevelSlider_valueChanged(int value) { - ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); - emit thresholdChanged(threshold); + ui->targetLevelLabel->setText(QString("%1 dB").arg(ui->targetLevelSlider->value())); + emit targetLevelChanged(value); } -/*! \brief AGC slope slider value has changed. */ -void CAgcOptions::on_slopeSlider_valueChanged(int slope) +/*! \brief AGC attack slider value has changed. */ +void CAgcOptions::on_attackSlider_valueChanged(int value) { - ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); - emit slopeChanged(slope); + ui->attackLabel->setText(QString("%1 ms").arg(ui->attackSlider->value())); + emit attackChanged(value); } /*! \brief AGC decay slider value has changed. */ -void CAgcOptions::on_decaySlider_valueChanged(int decay) +void CAgcOptions::on_decaySlider_valueChanged(int value) { ui->decayLabel->setText(QString("%1 ms").arg(ui->decaySlider->value())); - emit decayChanged(decay); + emit decayChanged(value); +} + +/*! \brief AGC hang slider value has changed. */ +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 AGC hang button has been toggled. */ -void CAgcOptions::on_hangButton_toggled(bool checked) +/*! \brief Panning auto checkbox state changed. */ +void CAgcOptions::on_panningAutoCheckBox_stateChanged(int state) { - ui->hangButton->setText(checked ? tr("Enabled") : tr("Disabled")); - emit hangChanged(checked); + 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 6714a695c..cee12a6af 100644 --- a/src/qtgui/agc_options.h +++ b/src/qtgui/agc_options.h @@ -52,23 +52,28 @@ class CAgcOptions : public QDialog void closeEvent(QCloseEvent *event); - int gain(); - void setGain(int value); - void enableGain(bool enabled); + int maxGain(); + void setMaxGain(int value); - int threshold(); - void setThreshold(int value); + int targetLevel(); + void setTargetLevel(int value); - int slope(); - void setSlope(int value); - void enableSlope(bool enabled); + int attack(); + void setAttack(int value); + void enableAttack(bool enabled); int decay(); void setDecay(int value); void enableDecay(bool enabled); - bool hang(); - void setHang(bool checked); + int hang(); + void setHang(int value); + void enableHang(bool enabled); + + int panning(); + void setPanning(int value); + bool panningAuto(); + void setPanningAuto(bool value); enum agc_preset_e { @@ -82,18 +87,22 @@ class CAgcOptions : public QDialog void setPreset(agc_preset_e preset); signals: - void gainChanged(int gain); - void thresholdChanged(int threshold); - void slopeChanged(int slope); + void maxGainChanged(int gain); + void targetLevelChanged(int level); + void attackChanged(int decay); void decayChanged(int decay); - void hangChanged(bool on); + void hangChanged(int hang); + void panningChanged(int panning); + void panningAutoChanged(bool panningAuto); private slots: - void on_gainSlider_valueChanged(int gain); - void on_thresholdSlider_valueChanged(int threshold); - void on_slopeSlider_valueChanged(int slope); - void on_decaySlider_valueChanged(int decay); - void on_hangButton_toggled(bool checked); + void on_maxGainSlider_valueChanged(int value); + void on_targetLevelSlider_valueChanged(int value); + 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 737eb6524..f34f42cd9 100644 --- a/src/qtgui/agc_options.ui +++ b/src/qtgui/agc_options.ui @@ -6,8 +6,8 @@ 0 0 - 263 - 197 + 293 + 299 @@ -33,23 +33,23 @@ 5 - - - - false - + + - Slope + Target Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + false + - Threshold + Decay Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -57,7 +57,7 @@ - + false @@ -65,26 +65,38 @@ Qt::StrongFocus - AGC slope + AGC decay time. Time to change from min gain to max gain. Smaller gain changes are faster. + + + 50 + 5000 + + 10 - 1 + 50 - 0 + 500 + + + 500 Qt::Horizontal - - + + + + false + - -100 dB + 500 ms Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -92,55 +104,71 @@ - + + + Hang + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + - false + true - Decay + Max Gain Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + true + + + Qt::StrongFocus + - Enable / disable AGC hang + Maximum automatic gain. - - Disabled + + 0 - - true + + 150 - - false + + 100 - - false + + Qt::Horizontal - + - false + true Manual gain. Used when AGC is switched off - 0 dB + 100 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + false @@ -148,13 +176,13 @@ Qt::StrongFocus - AGC decay time + AGC attack time. Time to change from max gain to min gain. Smaller gain changes are faster. - 50 + 20 - 5000 + 1000 10 @@ -173,8 +201,21 @@ - - + + + + false + + + Attack + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + false @@ -186,8 +227,8 @@ - - + + false @@ -195,34 +236,33 @@ Qt::StrongFocus - Manual gain. Used when AGC is switched off + AGC hang time. Time to wait before trying to increase the gain after the peak. + + + 0 - 100 + 5000 - - Qt::Horizontal + + 10 - - - - - - false + + 50 - - Gain + + 500 - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + 500 + + + Qt::Horizontal - - - - false - + + 0 dB @@ -231,38 +271,108 @@ - - - - Hang + + + + Enable auto stereo panning mode depending on offset frequency. - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + Auto panning - + Qt::StrongFocus - AGC threshold (aka. knee) + Target output level. - -160 + -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 + + + + + + + false + + + 500 ms + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -270,10 +380,7 @@ gainSlider - thresholdSlider - slopeSlider decaySlider - hangButton diff --git a/src/qtgui/audio_options.cpp b/src/qtgui/audio_options.cpp index 403e467b3..e0e8db029 100644 --- a/src/qtgui/audio_options.cpp +++ b/src/qtgui/audio_options.cpp @@ -96,6 +96,20 @@ void CAudioOptions::setUdpStereo(bool stereo) ui->udpStereo->setChecked(stereo); } +void CAudioOptions::setSquelchTriggered(bool value) +{ + ui->squelchTriggered->setChecked(value); +} + +void CAudioOptions::setRecMinTime(int time_ms) +{ + ui->recMinTime->setValue(time_ms); +} + +void CAudioOptions::setRecMaxGap(int time_ms) +{ + ui->recMaxGap->setValue(time_ms); +} void CAudioOptions::setFftSplit(int pct_2d) { @@ -212,6 +226,30 @@ void CAudioOptions::on_recDirEdit_textChanged(const QString &dir) } } +/** + * Slot called when the squelch-triggered mode gets disabled/enabled + */ +void CAudioOptions::on_squelchTriggered_stateChanged(int state) +{ + emit newSquelchTriggered(state == Qt::Checked); +} + +/** + * Slot called when the squelch-triggered recording min time gets changed + */ +void CAudioOptions::on_recMinTime_valueChanged(int value) +{ + emit newRecMinTime(value); +} + +/** + * Slot called when the squelch-triggered recording max gap gets changed + */ +void CAudioOptions::on_recMaxGap_valueChanged(int value) +{ + emit newRecMaxGap(value); +} + /** Slot called when the user clicks on the "Select" button. */ void CAudioOptions::on_recDirButton_clicked() { @@ -242,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 114103146..c5768bbe5 100644 --- a/src/qtgui/audio_options.h +++ b/src/qtgui/audio_options.h @@ -47,6 +47,9 @@ class CAudioOptions : public QDialog void setUdpHost(const QString &host); void setUdpPort(int port); void setUdpStereo(bool stereo); + void setSquelchTriggered(bool value); + void setRecMinTime(int time_ms); + void setRecMaxGap(int time_ms); void setFftSplit(int pct_2d); int getFftSplit(void) const; @@ -74,6 +77,10 @@ public slots: void newUdpHost(const QString text); void newUdpPort(int port); void newUdpStereo(bool enabled); + void newSquelchTriggered(bool enabled); + void newRecMinTime(int time_ms); + void newRecMaxGap(int time_ms); + void copyRecSettingsToAllVFOs(); private slots: void on_fftSplitSlider_valueChanged(int value); @@ -85,6 +92,10 @@ private slots: void on_udpHost_textChanged(const QString &text); void on_udpPort_valueChanged(int port); void on_udpStereo_stateChanged(int state); + 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 ab5841350..f495d93ef 100644 --- a/src/qtgui/audio_options.ui +++ b/src/qtgui/audio_options.ui @@ -7,7 +7,7 @@ 0 0 315 - 182 + 242 @@ -21,7 +21,7 @@ - 0 + 1 @@ -167,6 +167,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -206,17 +219,93 @@ - - - Qt::Vertical + + + Squelch-triggered recording mode - - - 20 - 40 - + + Squelch triggered - + + + + + + + + Min time + + + + + + + + 0 + 0 + + + + Minimum squelch open time to triggere recording start + + + ms + + + 10000 + + + 100 + + + + + + + Max gap + + + + + + + + 0 + 0 + + + + Maximum squelch closed time to keep recording running + + + ms + + + 10000 + + + 100 + + + + + + + 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 d4593e5ac..d50c29d31 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"); @@ -69,6 +70,12 @@ void Bookmarks::remove(int index) save(); } +void Bookmarks::remove(const BookmarkInfo &info) +{ + m_BookmarkList.removeOne(info); + save(); +} + bool Bookmarks::load() { QFile file(m_bookmarksFile); @@ -119,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(","); @@ -130,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-6f * 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; @@ -175,19 +241,44 @@ 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; iTagname); } - - stream << line << '\n'; + line.append( + "; " + QVariant(info.get_freq_lock()).toString().rightJustified(16) + + "; " + info.modulation.leftJustified(20)+ + "; " + QString::number(info.get_filter_low()).rightJustified(16) + + "; " + QString::number(info.get_filter_high()).rightJustified(16) + + "; " + QString::number(info.get_filter_tw()).rightJustified(16) + + "; " + QVariant(info.get_agc_on()).toString().rightJustified(16) + + "; " + QString::number(info.get_agc_target_level()).rightJustified(16) + + "; " + QString::number(info.get_agc_manual_gain()).rightJustified(16) + + "; " + QString::number(info.get_agc_max_gain()).rightJustified(16) + + "; " + QString::number(info.get_agc_attack()).rightJustified(16) + + "; " + QString::number(info.get_agc_decay()).rightJustified(16) + + "; " + QString::number(info.get_agc_hang()).rightJustified(16) + + "; " + QString::number(info.get_agc_panning()).rightJustified(16) + + "; " + QVariant(info.get_agc_panning_auto()).toString().rightJustified(16) + + "; " + QString::number(info.get_cw_offset()).rightJustified(16) + + "; " + QString::number(info.get_fm_maxdev()).rightJustified(16) + + "; " + QString::number(1.0e6 * info.get_fm_deemph()).rightJustified(16) + + "; " + QVariant(info.get_am_dcr()).toString().rightJustified(16) + + "; " + QVariant(info.get_amsync_dcr()).toString().rightJustified(16) + + "; " + QString::number(info.get_amsync_pll_bw()).rightJustified(16) + + "; " + QVariant(info.get_nb_on(1)).toString().rightJustified(16) + + "; " + QString::number(info.get_nb_threshold(1)).rightJustified(16) + + "; " + QVariant(info.get_nb_on(2)).toString().rightJustified(16) + + "; " + QString::number(info.get_nb_threshold(2)).rightJustified(16) + + "; " + QString::fromStdString(info.get_audio_rec_dir()).rightJustified(16) + + "; " + QVariant(info.get_audio_rec_sql_triggered()).toString().rightJustified(16) + + "; " + QString::number(info.get_audio_rec_min_time()).rightJustified(16) + + "; " + QString::number(info.get_audio_rec_max_gap()).rightJustified(16)); + stream << line << "\n"; } emit BookmarksChanged(); @@ -207,10 +326,10 @@ bool Bookmarks::save() return false; } -QList 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); @@ -220,7 +339,7 @@ QList Bookmarks::getBookmarksInRange(qint64 low, qint64 high) while (lb != ub) { const BookmarkInfo& info = *lb; - if(info.IsActive()) + if (!autoAdded || lb->get_freq_lock()) { found.append(info); } @@ -231,6 +350,11 @@ QList Bookmarks::getBookmarksInRange(qint64 low, qint64 high) } +int Bookmarks::find(const BookmarkInfo &info) +{ + return m_BookmarkList.indexOf(info); +} + TagInfo::sptr Bookmarks::findOrAddTag(QString tagName) { tagName = tagName.trimmed(); @@ -280,8 +404,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 8e3fee6ea..50386b93b 100644 --- a/src/qtgui/bookmarks.h +++ b/src/qtgui/bookmarks.h @@ -31,6 +31,7 @@ #include #include #include +#include "receivers/vfo.h" struct TagInfo { @@ -58,18 +59,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 ) @@ -84,6 +88,10 @@ struct BookmarkInfo { return frequency < other.frequency; } + bool operator==(const BookmarkInfo &other) const + { + return frequency == other.frequency; + } /* void setTags(QString tagString); QString getTagString(); @@ -93,6 +101,11 @@ struct BookmarkInfo const QColor GetColor() const; bool IsActive() const; + + qint64 frequency; + QString name; + QString modulation; + QList tags; }; class Bookmarks : public QObject @@ -105,11 +118,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); diff --git a/src/qtgui/bookmarkstablemodel.cpp b/src/qtgui/bookmarkstablemodel.cpp index 4fc5cc239..943e01fd1 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; iTagactive) { - bActive = true; + m_Bookmarks.append(iBookmark); break; } } - if(bActive) - { - m_mapRowToBookmarksIndex[iRow]=iBookmark; - m_Bookmarks.append(&info); - ++iRow; - } } - - emit layoutChanged(); + sort(m_sortCol, m_sortDir); } -BookmarkInfo *BookmarksTableModel::getBookmarkAtRow(int row) +BookmarkInfo *BookmarksTableModel::getBookmarkAtRow(int row) const { - return m_Bookmarks[row]; + return & Bookmarks::Get().getBookmark(m_Bookmarks[row]); } int BookmarksTableModel::GetBookmarksIndexForRow(int iRow) { - return m_mapRowToBookmarksIndex[iRow]; + return m_Bookmarks[iRow]; +} + +int BookmarksTableModel::GetRowForBookmarkIndex(int index) +{ + return m_Bookmarks.indexOf(index); +} + +bool BookmarksTableModel::bmCompare(const int a, const int b, int column, int order) +{ + switch (column) + { + case COL_FREQUENCY://LongLong + if (order) + return dataFromBookmark(Bookmarks::Get().getBookmark(a), column).toLongLong() >= + 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 f8b2e6274..6a7ad2cf1 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; + 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); + 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 794e29907..4962690ea 100644 --- a/src/qtgui/demod_options.cpp +++ b/src/qtgui/demod_options.cpp @@ -192,26 +192,6 @@ double CDemodOptions::getEmph(void) const return tau_from_index(ui->emphSelector->currentIndex()); } -void CDemodOptions::setDcr(bool enabled) -{ - ui->dcrCheckBox->setChecked(enabled); -} - -bool CDemodOptions::getDcr(void) const -{ - return ui->dcrCheckBox->isChecked(); -} - -void CDemodOptions::setSyncDcr(bool enabled) -{ - ui->syncdcrCheckBox->setChecked(enabled); -} - -bool CDemodOptions::getSyncDcr(void) const -{ - return ui->syncdcrCheckBox->isChecked(); -} - void CDemodOptions::on_maxdevSelector_activated(int index) { emit fmMaxdevSelected(maxdev_from_index(index)); @@ -251,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 e3ce365cd..b6bf17018 100644 --- a/src/qtgui/demod_options.h +++ b/src/qtgui/demod_options.h @@ -70,11 +70,8 @@ class CDemodOptions : public QDialog void setPllBw(float pll_bw); float getPllBw(void) const; - void setDcr(bool enabled); - bool getDcr(void) const; - - void setSyncDcr(bool enabled); - bool getSyncDcr(void) const; + void setAmDcr(bool on); + void setAmSyncDcr(bool on); signals: /*! \brief Signal emitted when new FM deviation is selected. */ diff --git a/src/qtgui/dockaudio.cpp b/src/qtgui/dockaudio.cpp index ae3622200..05f7061d8 100644 --- a/src/qtgui/dockaudio.cpp +++ b/src/qtgui/dockaudio.cpp @@ -38,15 +38,29 @@ DockAudio::DockAudio(QWidget *parent) : { ui->setupUi(this); + muteButtonMenu = new QMenu(this); + // MenuItem Global mute + { + muteAllAction = new QAction("Global Mute", this); + muteAllAction->setCheckable(true); + muteButtonMenu->addAction(muteAllAction); + connect(muteAllAction, SIGNAL(toggled(bool)), this, SLOT(menuMuteAll(bool))); + } + ui->audioMuteButton->setContextMenuPolicy(Qt::CustomContextMenu); + 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(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))); @@ -113,14 +127,6 @@ void DockAudio::setAudioGain(int gain) ui->audioGainSlider->setValue(gain); } -/*! \brief Set new audio gain. - * \param gain the new audio gain in dB - */ -void DockAudio::setAudioGainDb(float gain) -{ - ui->audioGainSlider->setValue(int(std::round(gain*10.0f))); -} - /*! \brief Set audio muted * \param muted true if audio should be muted */ @@ -130,7 +136,6 @@ void DockAudio::setAudioMuted(bool muted) ui->audioMuteButton->click(); } - /*! \brief Get current audio gain. * \returns The current audio gain in tens of dB (0 dB = 10). */ @@ -139,6 +144,14 @@ int DockAudio::audioGain() return ui->audioGainSlider->value(); } +/*! \brief Set audio gain slider state. + * \param state new slider state. + */ +void DockAudio::setGainEnabled(bool state) +{ + ui->audioGainSlider->setEnabled(state); +} + /*! Set FFT plot color. */ void DockAudio::setFftColor(QColor color) { @@ -151,32 +164,38 @@ void DockAudio::setFftFill(bool enabled) ui->audioSpectrum->enableFftFill(enabled); } -/*! Public slot to trig audio recording by external events (e.g. satellite AOS). - * - * If a recording is already in progress we ignore the event. - */ -void DockAudio::startAudioRecorder(void) +bool DockAudio::getSquelchTriggered() { - if (ui->audioRecButton->isChecked()) - { - qDebug() << __func__ << "An audio recording is already in progress"; - return; - } + return squelch_triggered; +} - // emulate a button click - ui->audioRecButton->click(); +void DockAudio::setSquelchTriggered(bool mode) +{ + squelch_triggered = mode; + audioOptions->setSquelchTriggered(mode); } -/*! Public slot to stop audio recording by external events (e.g. satellite LOS). - * - * The event is ignored if no audio recording is in progress. - */ -void DockAudio::stopAudioRecorder(void) +void DockAudio::setRecDir(const QString &dir) { - if (ui->audioRecButton->isChecked()) - ui->audioRecButton->click(); // emulate a button click - else - qDebug() << __func__ << "No audio recording in progress"; + 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); +} + +void DockAudio::setAudioMute(bool on) +{ + ui->audioMuteButton->setChecked(on); } /*! Public slot to set new RX frequency in Hz. */ @@ -199,8 +218,7 @@ void DockAudio::on_audioGainSlider_valueChanged(int value) // update dB label ui->audioGainDbLabel->setText(QString("%1 dB").arg((double)gain, 5, 'f', 1)); - if (!ui->audioMuteButton->isChecked()) - emit audioGainChanged(gain); + emit audioGainChanged(gain); } /*! \brief Streaming button clicked. @@ -209,7 +227,7 @@ void DockAudio::on_audioGainSlider_valueChanged(int value) void DockAudio::on_audioStreamButton_clicked(bool checked) { if (checked) - emit audioStreamingStarted(udp_host, udp_port, udp_stereo); + emit audioStreamingStarted(); else emit audioStreamingStopped(); } @@ -223,25 +241,12 @@ void DockAudio::on_audioStreamButton_clicked(bool checked) void DockAudio::on_audioRecButton_clicked(bool checked) { if (checked) { - // FIXME: option to use local time - // use toUTC() function compatible with older versions of Qt. - QString file_name = QDateTime::currentDateTime().toUTC().toString("gqrx_yyyyMMdd_hhmmss"); - last_audio = QString("%1/%2_%3.wav").arg(rec_dir).arg(file_name).arg(rx_freq); - QFileInfo info(last_audio); // emit signal and start timer - emit audioRecStarted(last_audio); - - ui->audioRecLabel->setText(info.fileName()); - ui->audioRecButton->setToolTip(tr("Stop audio recorder")); - ui->audioPlayButton->setEnabled(false); /* prevent playback while recording */ + emit audioRecStart(); } else { - ui->audioRecLabel->setText("DSP"); - ui->audioRecButton->setToolTip(tr("Start audio recorder")); - emit audioRecStopped(); - - ui->audioPlayButton->setEnabled(true); + emit audioRecStop(); } } @@ -287,17 +292,12 @@ void DockAudio::on_audioConfButton_clicked() /*! \brief Mute audio. */ void DockAudio::on_audioMuteButton_clicked(bool checked) { - if (checked) - { - emit audioGainChanged(-INFINITY); - } - else - { - int value = ui->audioGainSlider->value(); - float gain = float(value) / 10.0f; - emit audioGainChanged(gain); - } - emit audioMuted(checked); + emit audioMuteChanged(checked, false); +} + +void DockAudio::on_audioMuteButton_customContextMenuRequested(const QPoint& pos) +{ + muteButtonMenu->popup(ui->audioMuteButton->mapToGlobal(pos)); } /*! \brief Set status of audio record button. */ @@ -317,6 +317,30 @@ void DockAudio::setAudioRecButtonState(bool checked) //ui->audioRecConfButton->setEnabled(!isChecked); } +void DockAudio::setAudioStreamState(const std::string & host,int port,bool stereo, bool running) +{ + audioOptions->setUdpHost(udp_host = QString::fromStdString(host)); + audioOptions->setUdpPort(udp_port = port); + audioOptions->setUdpStereo(udp_stereo = stereo); + setAudioStreamButtonState(running); +} + +/*! \brief Set status of audio record button. */ +void DockAudio::setAudioStreamButtonState(bool checked) +{ + if (checked == ui->audioStreamButton->isChecked()) { + /* nothing to do */ + return; + } + + // toggle the button and set the state of the other buttons accordingly + ui->audioStreamButton->toggle(); + bool isChecked = ui->audioStreamButton->isChecked(); + + ui->audioStreamButton->setToolTip(isChecked ? tr("Stop audio streaming") : tr("Start audio streaming")); + //TODO: disable host/port controls +} + /*! \brief Set status of audio record button. */ void DockAudio::setAudioPlayButtonState(bool checked) { @@ -343,8 +367,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); @@ -376,26 +398,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 - settings->setValue("udp_host", udp_host); - - if (udp_port != 7355) - settings->setValue("udp_port", udp_port); - else - settings->remove("udp_port"); - - if (udp_stereo != false) - settings->setValue("udp_stereo", udp_stereo); - else - settings->remove("udp_stereo"); - settings->endGroup(); } @@ -409,10 +411,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); @@ -436,30 +434,15 @@ 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); - if (!conv_ok) - udp_port = 7355; - udp_stereo = settings->value("udp_stereo", false).toBool(); - - audioOptions->setUdpHost(udp_host); - audioOptions->setUdpPort(udp_port); - audioOptions->setUdpStereo(udp_stereo); - 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); } @@ -467,32 +450,76 @@ 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"; else udp_host = host; + emit udpHostChanged(udp_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; + emit udpPortChanged(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; + emit udpStereoChanged(enabled); +} + +/*! \brief Slot called when audio recording is started after clicking rec or being triggered by squelch. */ +void DockAudio::audioRecStarted(const QString filename) +{ + last_audio = filename; + QFileInfo info(last_audio); + ui->audioRecLabel->setText(info.fileName()); + ui->audioRecButton->setToolTip(tr("Stop audio recorder")); + ui->audioPlayButton->setEnabled(false); /* prevent playback while recording */ + setAudioRecButtonState(true); +} + +void DockAudio::audioRecStopped() +{ + ui->audioRecLabel->setText("DSP"); + ui->audioRecButton->setToolTip(tr("Start audio recorder")); + ui->audioPlayButton->setEnabled(true); + setAudioRecButtonState(false); +} + + +void DockAudio::squelchTriggered_changed(bool enabled) +{ + squelch_triggered = enabled; + ui->audioRecButton->setStyleSheet(enabled?"color: rgb(255,0,0)":""); + emit recSquelchTriggeredChanged(enabled); +} + +void DockAudio::recMinTime_changed(int time_ms) +{ + recMinTime = time_ms; + emit recMinTimeChanged(time_ms); +} + +void DockAudio::recMaxGap_changed(int time_ms) +{ + recMaxGap = time_ms; + emit recMaxGapChanged(time_ms); } + void DockAudio::recordToggleShortcut() { ui->audioRecButton->click(); } @@ -502,9 +529,22 @@ void DockAudio::muteToggleShortcut() { } void DockAudio::increaseAudioGainShortcut() { - ui->audioGainSlider->triggerAction(QSlider::SliderPageStepAdd); + if(ui->audioGainSlider->isEnabled()) + ui->audioGainSlider->triggerAction(QSlider::SliderPageStepAdd); } void DockAudio::decreaseAudioGainShortcut() { - ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub); + if(ui->audioGainSlider->isEnabled()) + ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub); +} + +void DockAudio::copyRecSettingsToAllVFOs_clicked() +{ + emit copyRecSettingsToAllVFOs(); +} + +void DockAudio::menuMuteAll(bool checked) +{ + emit audioMuteChanged(checked, true); + ui->audioMuteButton->setStyleSheet(checked?"color: rgb(255,0,0)":""); } diff --git a/src/qtgui/dockaudio.h b/src/qtgui/dockaudio.h index becd50b85..d813dce5b 100644 --- a/src/qtgui/dockaudio.h +++ b/src/qtgui/dockaudio.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "audio_options.h" namespace Ui { @@ -57,39 +58,58 @@ class DockAudio : public QDockWidget void setAudioGain(int gain); int audioGain(); + void setGainEnabled(bool state); void setAudioRecButtonState(bool checked); + void setAudioStreamState(const std::string & host,int port,bool stereo, bool running); + void setAudioStreamButtonState(bool checked); void setAudioPlayButtonState(bool checked); void setFftColor(QColor color); 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 setAudioMute(bool on); + void saveSettings(QSettings *settings); void readSettings(QSettings *settings); public slots: - void startAudioRecorder(void); - void stopAudioRecorder(void); void setRxFrequency(qint64 freq); void setWfColormap(const QString &cmap); - void setAudioGainDb(float gain); void setAudioMuted(bool muted); + void audioRecStarted(const QString filename); + void audioRecStopped(); signals: /*! \brief Signal emitted when audio gain has changed. Gain is in dB. */ void audioGainChanged(float gain); + /*! \brief Audio streaming UDP host changed. */ + void udpHostChanged(const QString host); + + /*! \brief Audio streaming UDP port changed. */ + void udpPortChanged(int port); + + /*! \brief Audio streaming stereo setting changed. */ + void udpStereoChanged(bool stereo); + /*! \brief Audio streaming over UDP has started. */ - void audioStreamingStarted(const QString host, int port, bool stereo); + void audioStreamingStarted(); /*! \brief Audio streaming stopped. */ void audioStreamingStopped(); /*! \brief Signal emitted when audio recording is started. */ - void audioRecStarted(const QString filename); + void audioRecStart(); /*! \brief Signal emitted when audio recording is stopped. */ - void audioRecStopped(); + void audioRecStop(); /*! \brief Signal emitted when audio playback is started. */ void audioPlayStarted(const QString filename); @@ -103,6 +123,24 @@ public slots: /*! \brief Audio mute chenged. */ void audioMuted(bool muted); + /*! \brief Signal emitted when audio mute has changed. */ + void audioMuteChanged(bool mute, bool global); + + /*! \brief Signal emitted when recording directory has changed. */ + void recDirChanged(const QString dir); + + /*! \brief Signal emitted when squelch triggered recording mode is changed. */ + void recSquelchTriggeredChanged(const bool enabled); + + /*! \brief Signal emitted when squelch triggered recording min time is changed. */ + void recMinTimeChanged(int time_ms); + + /*! \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); @@ -110,12 +148,18 @@ 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 on_audioMuteButton_customContextMenuRequested(const QPoint& pos); + 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(); + void menuMuteAll(bool checked); private: @@ -127,6 +171,9 @@ private slots: QString udp_host; /*! UDP client host name. */ int udp_port; /*! UDP client port number. */ bool udp_stereo; /*! Enable stereo streaming for UDP. */ + bool squelch_triggered; /*! Enable squelch-triggered recording */ + int recMinTime; /*! Minimum squelch-triggered recording time */ + int recMaxGap; /*! Maximum gap time in squelch-triggered mode*/ bool autoSpan; /*! Whether to allow mode-dependent auto span. */ @@ -136,6 +183,8 @@ private slots: void muteToggleShortcut(); void increaseAudioGainShortcut(); void decreaseAudioGainShortcut(); + QMenu *muteButtonMenu; + QAction *muteAllAction; }; #endif // DOCKAUDIO_H diff --git a/src/qtgui/dockaudio.ui b/src/qtgui/dockaudio.ui index 814fb5dc6..4f2e23b9d 100644 --- a/src/qtgui/dockaudio.ui +++ b/src/qtgui/dockaudio.ui @@ -113,10 +113,10 @@ Audio gain - -800 + -1000 - 500 + 1000 -60 @@ -129,16 +129,22 @@ - + 0 0 + + + 60 + 0 + + -6.0 dB - Qt::AlignCenter + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 diff --git a/src/qtgui/dockbookmarks.cpp b/src/qtgui/dockbookmarks.cpp index ff518588d..4e2b4de7a 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() @@ -195,6 +233,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)); @@ -216,9 +284,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; @@ -228,7 +296,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); } @@ -285,3 +353,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 baf3818b0..7d426b93d 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 d2518859b..d2f9859fc 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 c9a8fb47a..1d93b34b9 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 ece2d7d4a..3cbcba55d 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 d7944c2b9..de31b6128 100644 --- a/src/qtgui/dockrds.cpp +++ b/src/qtgui/dockrds.cpp @@ -116,17 +116,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() @@ -143,7 +139,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 d4451fca2..46cf57ee6 100644 --- a/src/qtgui/dockrds.h +++ b/src/qtgui/dockrds.h @@ -34,7 +34,7 @@ public slots: void radiotextChanged(QString); 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 02f7e2709..a505929f4 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); ui->filterFreq->setup(7, -filterOffsetRange/2, filterOffsetRange/2, 1, FCTL_UNIT_KHZ); @@ -108,11 +84,13 @@ DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : // AGC options dialog agcOpt = new CAgcOptions(this); - connect(agcOpt, SIGNAL(gainChanged(int)), this, SLOT(agcOpt_gainChanged(int))); - connect(agcOpt, SIGNAL(thresholdChanged(int)), this, SLOT(agcOpt_thresholdChanged(int))); + connect(agcOpt, SIGNAL(maxGainChanged(int)), this, SLOT(agcOpt_maxGainChanged(int))); + connect(agcOpt, SIGNAL(targetLevelChanged(int)), this, SLOT(agcOpt_targetLevelChanged(int))); + connect(agcOpt, SIGNAL(attackChanged(int)), this, SLOT(agcOpt_attackChanged(int))); connect(agcOpt, SIGNAL(decayChanged(int)), this, SLOT(agcOpt_decayChanged(int))); - connect(agcOpt, SIGNAL(slopeChanged(int)), this, SLOT(agcOpt_slopeChanged(int))); - connect(agcOpt, SIGNAL(hangChanged(bool)), this, SLOT(agcOpt_hangToggled(bool))); + 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); @@ -176,7 +154,9 @@ DockRxOpt::~DockRxOpt() */ void DockRxOpt::setFilterOffset(qint64 freq_hz) { + ui->filterFreq->blockSignals(true); ui->filterFreq->setFrequency(freq_hz); + ui->filterFreq->blockSignals(false); } /** @@ -237,19 +217,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); } /** @@ -264,6 +233,7 @@ void DockRxOpt::setFilterParam(int lo, int hi) { int filter_index = filterIdxFromLoHi(lo, hi); + ui->filterCombo->blockSignals(true); ui->filterCombo->setCurrentIndex(filter_index); if (filter_index == FILTER_PRESET_USER) { @@ -272,6 +242,7 @@ void DockRxOpt::setFilterParam(int lo, int hi) ui->filterCombo->setItemText(FILTER_PRESET_USER, QString("User (%1 k)") .arg((double)width_f)); } + ui->filterCombo->blockSignals(false); } /** @@ -280,7 +251,9 @@ void DockRxOpt::setFilterParam(int lo, int hi) */ void DockRxOpt::setCurrentFilter(int index) { + ui->filterCombo->blockSignals(true); ui->filterCombo->setCurrentIndex(index); + ui->filterCombo->blockSignals(false); } /** @@ -295,7 +268,9 @@ int DockRxOpt::currentFilter() const /** Select filter shape */ void DockRxOpt::setCurrentFilterShape(int index) { + ui->filterCombo->blockSignals(true); ui->filterShapeCombo->setCurrentIndex(index); + ui->filterCombo->blockSignals(false); } int DockRxOpt::currentFilterShape() const @@ -307,11 +282,13 @@ 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->blockSignals(true); ui->modeSelector->setCurrentIndex(demod); + ui->modeSelector->blockSignals(false); updateDemodOptPage(demod); } } @@ -320,14 +297,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 @@ -363,219 +340,156 @@ double DockRxOpt::currentSquelchLevel() const return ui->sqlSpinBox->value(); } -bool DockRxOpt::currentAmDcr() const +int DockRxOpt::getCwOffset() const { - return demodOpt->getDcr(); + return demodOpt->getCwOffset(); } -bool DockRxOpt::currentAmsyncDcr() const +void DockRxOpt::setCwOffset(int offset) { - return demodOpt->getSyncDcr(); + demodOpt->setCwOffset(offset); } -float DockRxOpt::currentAmsyncPll() const +/** Get agc settings */ +bool DockRxOpt::getAgcOn() { - return demodOpt->getPllBw(); + return agc_is_on; } -/** Get filter lo/hi for a given mode and preset */ -void DockRxOpt::getFilterPreset(int mode, int preset, int * lo, int * hi) const +void DockRxOpt::setAgcOn(bool on) { - 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]; + if (on) + setAgcPresetFromParams(getAgcDecay()); + else + ui->agcPresetCombo->setCurrentIndex(4); + agc_is_on = on; } -int DockRxOpt::getCwOffset() const +int DockRxOpt::getAgcTargetLevel() { - return demodOpt->getCwOffset(); + return agcOpt->targetLevel(); } -/** Read receiver configuration from settings data. */ -void DockRxOpt::readSettings(QSettings *settings) +void DockRxOpt::setAgcTargetLevel(int level) { - 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", 5000).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 - int_val = settings->value("receiver/agc_threshold", -100).toInt(&conv_ok); - if (conv_ok) - agcOpt->setThreshold(int_val); - - 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_slope", 0).toInt(&conv_ok); - if (conv_ok) - agcOpt->setSlope(int_val); - - int_val = settings->value("receiver/agc_gain", 0).toInt(&conv_ok); - if (conv_ok) - agcOpt->setGain(int_val); - - agcOpt->setHang(settings->value("receiver/agc_usehang", false).toBool()); - - if (settings->value("receiver/agc_off", false).toBool()) - ui->agcPresetCombo->setCurrentIndex(4); - - demodOpt->setDcr(settings->value("receiver/am_dcr", true).toBool()); + agcOpt->setTargetLevel(level); +} - demodOpt->setSyncDcr(settings->value("receiver/amsync_dcr", true).toBool()); +int DockRxOpt::getAgcMaxGain() +{ + return agcOpt->maxGain(); +} - int_val = settings->value("receiver/amsync_pllbw", 1000).toInt(&conv_ok); - if (conv_ok) - demodOpt->setPllBw(int_val / 1.0e6); +void DockRxOpt::setAgcMaxGain(int gain) +{ + agcOpt->setMaxGain(gain); +} - 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)]; - } - } +int DockRxOpt::getAgcAttack() +{ + return agcOpt->attack(); +} - setCurrentDemod(int_val); - emit demodSelected(int_val); +void DockRxOpt::setAgcAttack(int attack) +{ + agcOpt->setAttack(attack); +} +int DockRxOpt::getAgcDecay() +{ + return agcOpt->decay(); } -/** Save receiver configuration to settings. */ -void DockRxOpt::saveSettings(QSettings *settings) +void DockRxOpt::setAgcDecay(int decay) { - int int_val; + agcOpt->setDecay(decay); + setAgcOn(agc_is_on); +} - settings->setValue("receiver/demod", currentDemodAsString()); +int DockRxOpt::getAgcHang() +{ + return agcOpt->hang(); +} - int cwofs = demodOpt->getCwOffset(); - if (cwofs == 700) - settings->remove("receiver/cwoffset"); - else - settings->setValue("receiver/cwoffset", cwofs); +void DockRxOpt::setAgcHang(int hang) +{ + agcOpt->setHang(hang); +} - // currently we do not need the decimal - int_val = (int)demodOpt->getMaxDev(); - if (int_val == 5000) - settings->remove("receiver/fm_maxdev"); - else - settings->setValue("receiver/fm_maxdev", int_val); +int DockRxOpt::getAgcPanning() +{ + return agcOpt->panning(); +} - // 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::setAgcPanning(int panning) +{ + agcOpt->setPanning(panning); +} - qint64 offs = ui->filterFreq->getFrequency(); - if (offs) - settings->setValue("receiver/offset", offs); - else - settings->remove("receiver/offset"); +bool DockRxOpt::getAgcPanningAuto() +{ + return agcOpt->panningAuto(); +} - 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::setAgcPanningAuto(bool panningAuto) +{ + agcOpt->setPanningAuto(panningAuto); +} - // AGC settings - int_val = agcOpt->threshold(); - if (int_val != -100) - settings->setValue("receiver/agc_threshold", int_val); +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/agc_threshold"); + ui->agcPresetCombo->setCurrentIndex(3); +} - int_val = agcOpt->decay(); - if (int_val != 500) - settings->setValue("receiver/agc_decay", int_val); - else - settings->remove("receiver/agc_decay"); +void DockRxOpt::setAmDcr(bool on) +{ + demodOpt->setAmDcr(on); +} - int_val = agcOpt->slope(); - if (int_val != 0) - settings->setValue("receiver/agc_slope", int_val); - else - settings->remove("receiver/agc_slope"); +void DockRxOpt::setAmSyncDcr(bool on) +{ + demodOpt->setAmSyncDcr(on); +} - int_val = agcOpt->gain(); - if (int_val != 0) - settings->setValue("receiver/agc_gain", int_val); - else - settings->remove("receiver/agc_gain"); +void DockRxOpt::setAmSyncPllBw(float bw) +{ + demodOpt->setPllBw(bw); +} - if (agcOpt->hang()) - settings->setValue("receiver/agc_usehang", true); - else - settings->remove("receiver/agc_usehang"); +void DockRxOpt::setFmMaxdev(float max_hz) +{ + demodOpt->setMaxDev(max_hz); +} - // AGC Off - if (ui->agcPresetCombo->currentIndex() == 4) - settings->setValue("receiver/agc_off", true); - else - settings->remove("receiver/agc_off"); +void DockRxOpt::setFmEmph(double tau) +{ + demodOpt->setEmph(tau); +} - if (!demodOpt->getDcr()) - settings->setValue("receiver/am_dcr", false); +void DockRxOpt::setNoiseBlanker(int nbid, bool on, float threshold) +{ + if (nbid == 1) + ui->nb1Button->setChecked(on); else - settings->remove("receiver/am_dcr"); + ui->nb2Button->setChecked(on); + nbOpt->setNbThreshold(nbid, threshold); +} - if (!demodOpt->getSyncDcr()) - settings->setValue("receiver/amsync_dcr", false); - else - settings->remove("receiver/amsync_dcr"); +void DockRxOpt::setFreqLock(bool lock) +{ + ui->freqLockButton->setChecked(lock); +} - int_val = qRound(currentAmsyncPll() * 1.0e6f); - if (int_val != 1000) - settings->setValue("receiver/amsync_pllbw", int_val); - else - settings->remove("receiver/amsync_pllbw"); +bool DockRxOpt::getFreqLock() +{ + return ui->freqLockButton->isChecked(); } /** RX frequency changed through spin box */ @@ -634,7 +548,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())); } /** @@ -650,20 +564,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); @@ -688,7 +602,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); } @@ -697,6 +622,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) { @@ -730,27 +661,31 @@ void DockRxOpt::on_agcPresetCombo_currentIndexChanged(int index) } } -void DockRxOpt::agcOpt_hangToggled(bool checked) +/** + * @brief AGC hang time changed. + * @param value The new AGC hang time in ms. + */ +void DockRxOpt::agcOpt_hangChanged(int value) { - emit agcHangToggled(checked); + emit agcHangChanged(value); } /** - * @brief AGC threshold ("knee") changed. - * @param value The new AGC threshold in dB. + * @brief AGC target level changed. + * @param value The new AGC target level in dB. */ -void DockRxOpt::agcOpt_thresholdChanged(int value) +void DockRxOpt::agcOpt_targetLevelChanged(int value) { - emit agcThresholdChanged(value); + emit agcTargetLevelChanged(value); } /** - * @brief AGC slope factor changed. - * @param value The new slope factor in dB. + * @brief AGC attack changed. + * @param value The new attack rate in ms (tbc). */ -void DockRxOpt::agcOpt_slopeChanged(int value) +void DockRxOpt::agcOpt_attackChanged(int value) { - emit agcSlopeChanged(value); + emit agcAttackChanged(value); } /** @@ -763,12 +698,30 @@ void DockRxOpt::agcOpt_decayChanged(int value) } /** - * @brief AGC manual gain changed. + * @brief AGC maimum gain changed. * @param gain The new gain in dB. */ -void DockRxOpt::agcOpt_gainChanged(int gain) +void DockRxOpt::agcOpt_maxGainChanged(int gain) { - emit agcGainChanged(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); } /** @@ -842,96 +795,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); } -bool DockRxOpt::IsModulationValid(QString strModulation) +void DockRxOpt::menuFreqUnlockAll() { - return DockRxOpt::ModulationStrings.contains(strModulation, Qt::CaseInsensitive); + emit freqLock(false, true); + ui->freqLockButton->setChecked(false); } -QString DockRxOpt::GetStringForModulationIndex(int iModulationIndex) +/** Noise blanker threshold has been changed. */ +void DockRxOpt::nbOpt_thresholdChanged(int nbid, double value) { - return ModulationStrings[iModulationIndex]; + if (nbid == 1) + emit noiseBlankerChanged(nbid, ui->nb1Button->isChecked(), (float) value); + else + emit noiseBlankerChanged(nbid, ui->nb2Button->isChecked(), (float) value); +} + +void DockRxOpt::on_nbOptButton_clicked() +{ + 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 afbda61df..40eb1606d 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,36 +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; - bool currentAmDcr() const; - bool currentAmsyncDcr() const; - float currentAmsyncPll() const; - - void getFilterPreset(int mode, int preset, int * lo, int * hi) const; int getCwOffset() const; + void setCwOffset(int offset); double getSqlLevel(void) const; - static QStringList ModulationStrings; - static QString GetStringForModulationIndex(int iModulationIndex); - static int GetEnumForModulationString(QString param); - static bool IsModulationValid(QString strModulation); + bool getAgcOn(); + void setAgcOn(bool on); + int getAgcTargetLevel(); + void setAgcTargetLevel(int level); + int getAgcMaxGain(); + void setAgcMaxGain(int gain); + int getAgcAttack(); + void setAgcAttack(int attack); + int getAgcDecay(); + void setAgcDecay(int decay); + int getAgcHang(); + 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(); @@ -147,6 +137,7 @@ public slots: void filterNarrowShortcut(); void filterNormalShortcut(); void filterWideShortcut(); + void setAgcPresetFromParams(int decay); signals: /** Signal emitted when receiver frequency has changed */ @@ -156,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); @@ -184,29 +175,41 @@ 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); - /** Signal emitted when AGC hang is toggled. */ - void agcHangToggled(bool use_hang); + /** Signal emitted when AGC target level has changed. Level in dB. */ + void agcTargetLevelChanged(int value); - /** Signal emitted when AGC threshold has changed. Threshold in dB. */ - void agcThresholdChanged(int value); + /** Signal emitted when AGC maximum gain has changed. Gain is in dB.*/ + void agcMaxGainChanged(int gain); - /** Signal emitted when AGC slope has changed. Slope is in dB.*/ - void agcSlopeChanged(int slope); + /** Signal emitted when AGC attack has changed. Decay is in millisec.*/ + void agcAttackChanged(int attack); /** Signal emitted when AGC decay has changed. Decay is in millisec.*/ void agcDecayChanged(int decay); - /** Signal emitted when AGC manual gain has changed. Gain is in dB.*/ - void agcGainChanged(int gain); + /** 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: @@ -217,13 +220,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); @@ -237,17 +247,21 @@ private slots: void demodOpt_amSyncPllBwSelected(float pll_bw); // Signals coming from AGC options popup - void agcOpt_hangToggled(bool checked); - void agcOpt_gainChanged(int value); - void agcOpt_thresholdChanged(int value); - void agcOpt_slopeChanged(int value); + void agcOpt_maxGainChanged(int value); + void agcOpt_targetLevelChanged(int value); + 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 36fe18fd8..d5284feb6 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 @@ -558,6 +558,12 @@ This is an offset from the hardware RF frequency.</p></body></htm + + + 0 + 0 + + Receiver frequency @@ -567,6 +573,9 @@ This is an offset from the hardware RF frequency.</p></body></htm Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + kHz + 3 @@ -581,13 +590,6 @@ This is an offset from the hardware RF frequency.</p></body></htm - - - - kHz - - - @@ -694,6 +696,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 e6d037a9b..029d3cae1 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 9d9d99305..ce2bca26e 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 d3a36dbd5..d770dd398 100644 --- a/src/qtgui/plotter.cpp +++ b/src/qtgui/plotter.cpp @@ -28,6 +28,7 @@ * or implied, of Moe Wheatley. */ #include +#include #include #include #include @@ -37,7 +38,6 @@ #include #include "plotter.h" #include "bandplan.h" -#include "bookmarks.h" #include "dxc_spots.h" #include @@ -177,6 +177,10 @@ CPlotter::CPlotter(QWidget *parent) : QFrame(parent) wf_avg_count = 0; wf_span = 0; fft_rate = 15; + m_currentVfo = 0; + m_capturedVfo = 0; + m_lookup_vfo = vfo::make(); + m_lookup_vfo->set_index(0); } CPlotter::~CPlotter() @@ -220,6 +224,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) { @@ -247,9 +264,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(px, m_DemodHiCutFreqX, m_CursorCaptureDelta)) { @@ -285,6 +306,30 @@ void CPlotter::mouseMoveEvent(QMouseEvent* event) if (m_TooltipsEnabled) showToolTip(event, QString("Marker B: %1 kHz").arg(m_MarkerFreqB/1.e3, 0, 'f', 3)); } + 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) @@ -602,6 +647,34 @@ void CPlotter::clearWaterfallBuf() m_wfbuf[i] = 0.0; } +void CPlotter::setCurrentVfo(int current) +{ + m_currentVfo = current; +} + +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 { @@ -626,6 +699,7 @@ void CPlotter::mousePressEvent(QMouseEvent * event) int px = qRound((qreal)pt.x() * m_DPR); int py = qRound((qreal)pt.y() * m_DPR); QPoint ppos = QPoint(px, py); + quint32 mods = event->modifiers() & (Qt::ShiftModifier|Qt::ControlModifier); if (NOCAP == m_CursorCaptured) { @@ -652,7 +726,6 @@ void CPlotter::mousePressEvent(QMouseEvent * event) if (event->buttons() == Qt::LeftButton) { // {shift|ctrl|ctrl-shift}-left-click: set ab markers around signal at cursor - quint32 mods = event->modifiers() & (Qt::ShiftModifier|Qt::ControlModifier); if (m_MarkersEnabled && ((event->modifiers() & mods) != 0)) { float *selectBuf = nullptr; @@ -725,7 +798,8 @@ void CPlotter::mousePressEvent(QMouseEvent * event) } // left-click with no modifiers: set center frequency - else if (mods == 0) { + else + { int best = -1; if (m_PeakDetectActive > 0) @@ -736,7 +810,14 @@ void CPlotter::mousePressEvent(QMouseEvent * event) m_DemodCenterFreq = roundFreq(freqFromX(px), m_ClickResolution); // if cursor not captured set demod frequency and start demod box capture - emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + if(mods == Qt::ShiftModifier) + { + emit newDemodFreqAdd(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + }else if(mods == Qt::ControlModifier){ + // TODO: find some use for the ctrl modifier + }else{ + emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + } // save initial grab position from m_DemodFreqX // setCursor(QCursor(Qt::CrossCursor)); @@ -780,11 +861,41 @@ void CPlotter::mousePressEvent(QMouseEvent * event) { if (tag.first.contains(ppos)) { - m_DemodCenterFreq = tag.second; - emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); - break; + if (event->buttons() == Qt::LeftButton) + { + //just tune + if(mods == Qt::ShiftModifier) + m_CenterFreq = tag.second; + m_DemodCenterFreq = tag.second; + emit newDemodFreq(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } + else if (event->buttons() == Qt::MiddleButton) + { + //tune and load settings + if(mods == Qt::ShiftModifier) + m_CenterFreq = tag.second; + m_DemodCenterFreq = tag.second; + emit newDemodFreqAdd(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } + else if (event->buttons() == Qt::RightButton) + { + //new demod here + if(mods == Qt::ShiftModifier) + m_CenterFreq = tag.second; + m_DemodCenterFreq = tag.second; + emit newDemodFreqLoad(m_DemodCenterFreq, m_DemodCenterFreq - m_CenterFreq); + break; + } } } + updateOverlay(); + } + else if (m_CursorCaptured == CENTER) + { + if (m_currentVfo != m_capturedVfo) + emit selectVfo(m_capturedVfo); } } } @@ -2015,13 +2126,13 @@ void CPlotter::drawOverlay() x = xFromFreq(tag.frequency); qreal 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 } @@ -2217,17 +2328,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.fillRect(m_DemodLowCutFreqX, 0, dw, h, - QColor::fromRgba(PLOTTER_FILTER_BOX_COLOR)); - - painter.setPen(QPen(QColor::fromRgba(PLOTTER_FILTER_LINE_COLOR), m_DPR)); - painter.drawLine(m_DemodFreqX, 0, m_DemodFreqX, h); + drawVfo(painter, m_DemodFreqX, m_DemodLowCutFreqX, dw, h, m_currentVfo, true); } // Draw a black line at the bottom of the plotter to separate it from the @@ -2237,6 +2354,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::fromRgba(PLOTTER_FILTER_BOX_COLOR)); + + painter.setOpacity(1.0); + painter.setPen(QPen(QColor(QColor::fromRgba(is_selected ? PLOTTER_FILTER_LINE_COLOR : PLOTTER_TEXT_COLOR)), m_DPR)); + 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 @@ -2366,7 +2499,6 @@ void CPlotter::setDemodRanges(int FLowCmin, int FLowCmax, m_FHiCmax=FHiCmax; m_symetric=symetric; clampDemodParameters(); - updateOverlay(); } void CPlotter::setCenterFreq(quint64 f) diff --git a/src/qtgui/plotter.h b/src/qtgui/plotter.h index 10ca83c7b..fad0ab69e 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 @@ -56,7 +60,6 @@ class CPlotter : public QFrame void setFilterOffset(qint64 freq_hz) { m_DemodCenterFreq = m_CenterFreq + freq_hz; - updateOverlay(); } qint64 getFilterOffset() const { @@ -72,7 +75,6 @@ class CPlotter : public QFrame { m_DemodLowCutFreq = LowCut; m_DemodHiCutFreq = HiCut; - updateOverlay(); } void getHiLowCutFrequencies(int *LowCut, int *HiCut) const @@ -90,7 +92,6 @@ class CPlotter : public QFrame m_Span = (qint32)s; setFftCenterFreq(m_FftCenter); } - updateOverlay(); } void setVdivDelta(int delta) { m_VdivDelta = delta; } @@ -126,6 +127,11 @@ class CPlotter : public QFrame quint64 getWfTimeRes() const; void setFftRate(int rate_hz); void clearWaterfallBuf(); + void setCurrentVfo(int current); + void addVfo(vfo::sptr n_vfo); + void removeVfo(vfo::sptr n_vfo); + void clearVfos(); + void getLockedVfos(std::vector &to); enum ePlotMode { PLOT_MODE_MAX = 0, @@ -148,6 +154,8 @@ class CPlotter : public QFrame 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 */ @@ -156,6 +164,7 @@ class CPlotter : public QFrame void newSize(); void markerSelectA(qint64 freq); void markerSelectB(qint64 freq); + void selectVfo(int); public slots: // zoom functions @@ -214,6 +223,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); @@ -347,6 +359,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 65c9dc97f..8bc923671 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 000000000..88ea53c3f --- /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 000000000..f643a5e9a --- /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 000000000..8b57fd714 --- /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 b95c6b761..e143c615f 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,24 +32,17 @@ nbrx_sptr make_nbrx(float quad_rate, float audio_rate) } nbrx::nbrx(float quad_rate, float audio_rate) - : receiver_base_cf("NBRX"), - d_running(false), - d_quad_rate(quad_rate), - d_audio_rate(audio_rate), - d_demod(NBRX_DEMOD_FM) + : receiver_base_cf("NBRX", NB_PREF_QUAD_RATE, quad_rate, audio_rate), + d_running(false) { - iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); - nb = make_rx_nb_cc((double)PREF_QUAD_RATE, 3.3, 2.5); - filter = make_rx_filter((double)PREF_QUAD_RATE, -5000.0, 5000.0, 1000.0); - agc = make_rx_agc_cc((double)PREF_QUAD_RATE, true, -100, 0, 0, 500, false); - sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); - meter = make_rx_meter_c((double)PREF_QUAD_RATE); + nb = make_rx_nb_cc((double)NB_PREF_QUAD_RATE, 3.3, 2.5); + filter = make_rx_filter((double)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); // Width of rx_filter can be adjusted at run time, so the input buffer (the // output buffer of nb) needs to be large enough for the longest history @@ -61,35 +52,22 @@ nbrx::nbrx(float quad_rate, float audio_rate) 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, agc, 0); - connect(agc, 0, demod, 0); - - if (audio_rr0) - { - connect(demod, 0, audio_rr0, 0); - - connect(audio_rr0, 0, self(), 0); // left channel - connect(audio_rr0, 0, self(), 1); // right channel - } - else - { - connect(demod, 0, self(), 0); - connect(demod, 0, self(), 1); - } + connect(agc, 2, self(), 0); + connect(agc, 3, self(), 1); } bool nbrx::start() @@ -106,35 +84,56 @@ bool nbrx::stop() return true; } -void nbrx::set_quad_rate(float quad_rate) +void nbrx::set_filter(int low, int high, int tw) { - if (std::abs(d_quad_rate-quad_rate) > 0.5f) - { - qDebug() << "Changing NB_RX quad rate:" << d_quad_rate << "->" << quad_rate; - d_quad_rate = quad_rate; - lock(); - iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); - unlock(); - } + receiver_base_cf::set_filter(low, high, tw); + if(get_demod()!=Modulations::MODE_OFF) + filter->set_param(double(low), double(high), double(tw)); } -void nbrx::set_filter(double low, double high, double tw) +void nbrx::set_cw_offset(int offset) { - filter->set_param(low, high, tw); -} - -void nbrx::set_cw_offset(double offset) -{ - filter->set_cw_offset(offset); + if(offset==get_cw_offset()) + return; + 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); + } } -float nbrx::get_signal_level() +void nbrx::set_offset(int offset) { - return meter->get_level_db(); + if(offset==get_offset()) + return; + 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) @@ -143,182 +142,177 @@ 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_sql_level(double level_db) +void nbrx::set_demod(Modulations::idx new_demod) { - sql->set_threshold(level_db); -} - -void nbrx::set_sql_alpha(double alpha) -{ - sql->set_alpha(alpha); -} - -void nbrx::set_agc_on(bool agc_on) -{ - agc->set_agc_on(agc_on); -} - -void nbrx::set_agc_hang(bool use_hang) -{ - agc->set_use_hang(use_hang); -} - -void nbrx::set_agc_threshold(int threshold) -{ - agc->set_threshold(threshold); -} + Modulations::idx current_demod = receiver_base_cf::get_demod(); -void nbrx::set_agc_slope(int slope) -{ - agc->set_slope(slope); -} - -void nbrx::set_agc_decay(int decay_ms) -{ - agc->set_decay(decay_ms); -} - -void nbrx::set_agc_manual_gain(int gain) -{ - agc->set_manual_gain(gain); -} - -void nbrx::set_demod(int rx_demod) -{ - nbrx_demod current_demod = d_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(agc, 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, self(), 0); - disconnect(audio_rr1, 0, self(), 1); - } - else - { - disconnect(demod, 0, audio_rr0, 0); - disconnect(audio_rr0, 0, self(), 0); - disconnect(audio_rr0, 0, self(), 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, self(), 0); - disconnect(demod, 1, self(), 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, self(), 0); - disconnect(demod, 0, self(), 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(agc, 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, self(), 0); - connect(audio_rr1, 0, self(), 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, self(), 0); - connect(audio_rr0, 0, self(), 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, self(), 0); - connect(demod, 1, self(), 1); - } - else - { - connect(demod, 0, self(), 0); - connect(demod, 0, self(), 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); + if(get_demod() != Modulations::MODE_OFF) + lock(); demod_am->set_dcr(enabled); + if(get_demod() != Modulations::MODE_OFF) + unlock(); } void nbrx::set_amsync_dcr(bool enabled) { + receiver_base_cf::set_amsync_dcr(enabled); + if(get_demod() != Modulations::MODE_OFF) + lock(); demod_amsync->set_dcr(enabled); + if(get_demod() != Modulations::MODE_OFF) + 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 20fcd7d3f..03a9f8ebd 100644 --- a/src/receivers/nbrx.h +++ b/src/receivers/nbrx.h @@ -23,19 +23,14 @@ #ifndef NBRX_H #define NBRX_H -#include #include #include #include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" -#include "dsp/rx_meter.h" -#include "dsp/rx_agc_xx.h" #include "dsp/rx_demod_fm.h" #include "dsp/rx_demod_am.h" -//#include "dsp/resampler_ff.h" -#include "dsp/resampler_xx.h" class nbrx; @@ -55,80 +50,41 @@ 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(); - - void set_quad_rate(float quad_rate); + bool start() override; + bool stop() override; - void set_filter(double low, double high, double tw); - void set_cw_offset(double offset); - - float get_signal_level(); + 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; } - void set_sql_level(double level_db); - void set_sql_alpha(double alpha); - - /* AGC */ - bool has_agc() { return true; } - void set_agc_on(bool agc_on); - void set_agc_hang(bool use_hang); - void set_agc_threshold(int threshold); - void set_agc_slope(int slope); - void set_agc_decay(int decay_ms); - void set_agc_manual_gain(int gain); - - void set_demod(int demod); + bool has_nb() override { return true; } + void set_nb_on(int nbid, bool on) override; + void set_nb_threshold(int nbid, float threshold) override; + + 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. */ - float d_quad_rate; /*!< Input sample rate. */ - int d_audio_rate; /*!< Audio output rate. */ - - nbrx_demod d_demod; /*!< Current demodulator. */ - resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_nb_cc_sptr nb; /*!< Noise blanker. */ - rx_meter_c_sptr meter; /*!< Signal strength. */ - rx_agc_cc_sptr agc; /*!< Receiver AGC. */ - gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ gr::blocks::complex_to_float::sptr demod_raw; /*!< Raw I/Q passthrough. */ gr::blocks::complex_to_real::sptr demod_ssb; /*!< SSB demodulator. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ diff --git a/src/receivers/receiver_base.cpp b/src/receivers/receiver_base.cpp index 0834e6d21..12881ac99 100644 --- a/src/receivers/receiver_base.cpp +++ b/src/receivers/receiver_base.cpp @@ -22,131 +22,216 @@ */ #include #include "receivers/receiver_base.h" - +#include +#include +#include +#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) +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))) + gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(float))), + 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_udp_streaming(false), + 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/float(d_quad_rate)); + 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, + wavfile_sink_gqrx::FORMAT_PCM_16); + audio_udp_sink = make_udp_sink_f(); + + connect(agc, 0, wav_sink, 0); + connect(agc, 1, wav_sink, 1); + connect(agc, 0, audio_udp_sink, 0); + connect(agc, 1, audio_udp_sink, 1); + wav_sink->set_rec_event_handler(std::bind(rec_event, this, std::placeholders::_1, + std::placeholders::_2)); } receiver_base_cf::~receiver_base_cf() { + //Prevent segfault + if (wav_sink) + wav_sink->set_rec_event_handler(nullptr); +} +void receiver_base_cf::set_demod(Modulations::idx demod) +{ + if ((get_demod() == Modulations::MODE_OFF) && (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/float(d_quad_rate)); + unlock(); + } + vfo_s::set_demod(demod); } -bool receiver_base_cf::has_nb() +void receiver_base_cf::set_quad_rate(double quad_rate) { - return false; + 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/float(d_quad_rate)); + unlock(); + } + } } -void receiver_base_cf::set_nb_on(int nbid, bool on) +void receiver_base_cf::set_center_freq(double center_freq) { - (void) nbid; - (void) on; + d_center_freq = center_freq; + wav_sink->set_center_freq(center_freq); } -void receiver_base_cf::set_nb_threshold(int nbid, float threshold) +void receiver_base_cf::set_offset(int offset) { - (void) nbid; - (void) threshold; + vfo_s::set_offset(offset); + ddc->set_center_freq(offset); + wav_sink->set_offset(offset); } -bool receiver_base_cf::has_sql() +void receiver_base_cf::set_cw_offset(int offset) { - return false; + vfo_s::set_cw_offset(offset); } -void receiver_base_cf::set_sql_level(double level_db) +void receiver_base_cf::set_audio_rec_dir(const std::string& dir) { - (void) level_db; + vfo_s::set_audio_rec_dir(dir); + wav_sink->set_rec_dir(dir); } -void receiver_base_cf::set_sql_alpha(double alpha) +void receiver_base_cf::set_audio_rec_sql_triggered(bool enabled) { - (void) alpha; + 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); } -bool receiver_base_cf::has_agc() +void receiver_base_cf::set_audio_rec_min_time(const int time_ms) { - return false; + vfo_s::set_audio_rec_min_time(time_ms); + wav_sink->set_rec_min_time(time_ms); } -void receiver_base_cf::set_agc_on(bool agc_on) +void receiver_base_cf::set_audio_rec_max_gap(const int time_ms) { - (void) agc_on; + vfo_s::set_audio_rec_max_gap(time_ms); + wav_sink->set_rec_max_gap(time_ms); } -void receiver_base_cf::set_agc_hang(bool use_hang) +float receiver_base_cf::get_signal_level() { - (void) use_hang; + return meter->get_level_db(); } -void receiver_base_cf::set_agc_threshold(int threshold) +bool receiver_base_cf::has_nb() { - (void) threshold; + return false; } -void receiver_base_cf::set_agc_slope(int slope) +void receiver_base_cf::set_sql_level(double level_db) { - (void) slope; + sql->set_threshold(level_db); + vfo_s::set_sql_level(level_db); } -void receiver_base_cf::set_agc_decay(int decay_ms) +void receiver_base_cf::set_sql_alpha(double alpha) { - (void) decay_ms; + sql->set_alpha(alpha); + vfo_s::set_sql_alpha(alpha); } -void receiver_base_cf::set_agc_manual_gain(int gain) +void receiver_base_cf::set_agc_on(bool agc_on) { - (void) gain; + agc->set_agc_on(agc_on); + vfo_s::set_agc_on(agc_on); } -bool receiver_base_cf::has_fm() +void receiver_base_cf::set_agc_target_level(int target_level) { - return false; + agc->set_target_level(target_level); + vfo_s::set_agc_target_level(target_level); } -void receiver_base_cf::set_fm_maxdev(float maxdev_hz) +void receiver_base_cf::set_agc_manual_gain(float gain) { - (void) maxdev_hz; + agc->set_manual_gain(gain); + vfo_s::set_agc_manual_gain(gain); } -void receiver_base_cf::set_fm_deemph(double tau) +void receiver_base_cf::set_agc_max_gain(int gain) { - (void) tau; + agc->set_max_gain(gain); + vfo_s::set_agc_max_gain(gain); } -bool receiver_base_cf::has_am() +void receiver_base_cf::set_agc_attack(int attack_ms) { - return false; + agc->set_attack(attack_ms); + vfo_s::set_agc_attack(attack_ms); } -void receiver_base_cf::set_am_dcr(bool enabled) +void receiver_base_cf::set_agc_decay(int decay_ms) { - (void) enabled; + agc->set_decay(decay_ms); + vfo_s::set_agc_decay(decay_ms); } -bool receiver_base_cf::has_amsync() +void receiver_base_cf::set_agc_hang(int hang_ms) { - return false; + agc->set_hang(hang_ms); + vfo_s::set_agc_hang(hang_ms); +} + +void receiver_base_cf::set_agc_panning(int panning) +{ + agc->set_panning(panning); + vfo_s::set_agc_panning(panning); } -void receiver_base_cf::set_amsync_dcr(bool enabled) +void receiver_base_cf::set_agc_mute(bool agc_mute) { - (void) enabled; + agc->set_mute(agc_mute); + vfo_s::set_agc_mute(agc_mute); } -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) @@ -171,3 +256,100 @@ bool receiver_base_cf::is_rds_decoder_active() { return false; } + +/* UDP streaming */ +bool receiver_base_cf::set_udp_host(const std::string &host) +{ + if(d_udp_host == host) + return true; + if (d_udp_streaming) + return false; + return vfo_s::set_udp_host(host); +} + +bool receiver_base_cf::set_udp_port(int port) +{ + if(d_udp_port == port) + return true; + if (d_udp_streaming) + return false; + return vfo_s::set_udp_port(port); +} + +bool receiver_base_cf::set_udp_stereo(bool stereo) +{ + if(d_udp_stereo == stereo) + return true; + if (d_udp_streaming) + return false; + return vfo_s::set_udp_stereo(stereo); +} + +bool receiver_base_cf::set_udp_streaming(bool streaming) +{ + if(d_udp_streaming == streaming) + return true; + try{ + if(!d_udp_streaming && streaming) + audio_udp_sink->start_streaming(d_udp_host, d_udp_port, d_udp_stereo); + if(d_udp_streaming && !streaming) + audio_udp_sink->stop_streaming(); + }catch(std::exception & e) + { + return false; + } + d_udp_streaming = streaming; + return true; +} + +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(); +} + +//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) + return; + from->disconnect(from->agc, 0, from->wav_sink, 0); + from->disconnect(from->agc, 1, from->wav_sink, 1); + wav_sink = from->wav_sink; + wav_sink->set_rec_event_handler(std::bind(rec_event, this, std::placeholders::_1, + std::placeholders::_2)); + connect(agc, 0, wav_sink, 0); + connect(agc, 1, wav_sink, 1); + from->wav_sink.reset(); +} + +std::string receiver_base_cf::get_last_audio_filename() +{ + return d_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(self->get_index(), filename, is_running); + std::cerr<<"d_rec_event("<get_index()<<","< - +#include +#include "dsp/resampler_xx.h" +#include "dsp/rx_meter.h" +#include "dsp/rx_agc_xx.h" +#include "dsp/rx_squelch.h" +#include "dsp/downconverter.h" +#include "interfaces/wav_sink.h" +#include "interfaces/udp_sink_f.h" +#include "receivers/vfo.h" +#include "defines.h" class receiver_base_cf; +#if 0 +/** 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). */ +}; +#endif + #if GNURADIO_VERSION < 0x030900 typedef boost::shared_ptr receiver_base_cf_sptr; #else @@ -42,68 +66,103 @@ 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 */ - receiver_base_cf(std::string src_name); + 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) = 0; + virtual void set_quad_rate(double quad_rate); + virtual void set_center_freq(double center_freq); + void set_offset(int offset) override; - virtual void set_filter(double low, double high, double tw) = 0; - virtual void set_cw_offset(double offset) = 0; + /* 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 float get_signal_level() = 0; + /* UDP streaming */ + bool set_udp_host(const std::string &host) override; + bool set_udp_port(int port) override; + bool set_udp_stereo(bool stereo) override; + virtual bool set_udp_streaming(bool streaming); + inline bool get_udp_streaming() const { return d_udp_streaming; } - 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_hang(bool use_hang); - virtual void set_agc_threshold(int threshold); - virtual void set_agc_slope(int slope); - virtual void set_agc_decay(int decay_ms); - virtual void set_agc_manual_gain(int 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); + 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; + void set_agc_mute(bool agc_mute) override; + virtual float get_agc_gain(); + + /* CW parameters */ + void set_cw_offset(int offset) override; virtual void get_rds_data(std::string &outbuff, int &num); virtual void start_rds_decoder(); virtual void stop_rds_decoder(); virtual void reset_rds_parser(); virtual bool is_rds_decoder_active(); + 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: + 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; + bool d_udp_streaming; + + 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. */ + udp_sink_f_sptr audio_udp_sink; /*!< UDP sink to stream audio over the network. */ +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); }; diff --git a/src/receivers/vfo.cpp b/src/receivers/vfo.cpp new file mode 100644 index 000000000..ed0b531f4 --- /dev/null +++ b/src/receivers/vfo.cpp @@ -0,0 +1,270 @@ +/* -*- 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_agc_mute(bool agc_mute) +{ + d_agc_mute = agc_mute; +} + +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) const +{ + 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) const +{ + 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; +} + +/* UDP streaming */ +bool vfo_s::set_udp_host(const std::string &host) +{ + d_udp_host = host; + return true; +} + +bool vfo_s::set_udp_port(int port) +{ + d_udp_port = port; + return true; +} + +bool vfo_s::set_udp_stereo(bool stereo) +{ + d_udp_stereo = stereo; + return true; +} + +void vfo_s::restore_settings(vfo_s& from, bool force) +{ + 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_filter(from.get_filter_low(), from.get_filter_high(), from.get_filter_tw()); + + 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()); + set_udp_host(from.get_udp_host()); + set_udp_port(from.get_udp_port()); + set_udp_stereo(from.get_udp_stereo()); +} diff --git a/src/receivers/vfo.h b/src/receivers/vfo.h new file mode 100644 index 000000000..558cf09b4 --- /dev/null +++ b/src/receivers/vfo.h @@ -0,0 +1,255 @@ +/* -*- 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_agc_mute(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), + d_udp_host("127.0.0.1"), + d_udp_port(7355), + d_udp_stereo(false) + { + 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; } + inline bool get_agc_mute() const { return d_agc_mute; } + /* 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) const; + float get_nb_threshold(int nbid) const; + /* 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; } + /* UDP streaming */ + inline const std::string &get_udp_host() const { return d_udp_host; } + inline int get_udp_port() const { return d_udp_port; } + inline bool get_udp_stereo() const { return d_udp_stereo; } + + //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); + virtual void set_agc_mute(bool agc_mute); + /* 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); + + /* UDP streaming */ + virtual bool set_udp_host(const std::string &host); + virtual bool set_udp_port(int port); + virtual bool set_udp_stereo(bool stereo); + + 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; + bool d_agc_mute; + + 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; + + std::string d_udp_host; + int d_udp_port; + bool d_udp_stereo; + +} vfo; + + +#endif //VFO_H \ No newline at end of file diff --git a/src/receivers/wfmrx.cpp b/src/receivers/wfmrx.cpp index 3e5838646..c2a1a51e1 100644 --- a/src/receivers/wfmrx.cpp +++ b/src/receivers/wfmrx.cpp @@ -26,45 +26,39 @@ #include #include "receivers/wfmrx.h" -#define PREF_QUAD_RATE 240e3f // 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"), - d_running(false), - d_quad_rate(quad_rate), - d_audio_rate(audio_rate), - d_demod(WFMRX_DEMOD_MONO) + : receiver_base_cf("WFMRX", WFM_PREF_QUAD_RATE, quad_rate, audio_rate), + d_running(false) { - iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); - filter = make_rx_filter((double)PREF_QUAD_RATE, -80000.0, 80000.0, 20000.0); - sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); - meter = make_rx_meter_c((double)PREF_QUAD_RATE); - 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((double)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((double)PREF_QUAD_RATE); + rds = make_rx_rds((double)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); connect(sql, 0, demod_fm, 0); connect(demod_fm, 0, mono, 0); - connect(mono, 0, self(), 0); // left channel - connect(mono, 1, self(), 1); // right channel + connect(mono, 0, agc, 0); // left channel + connect(mono, 1, agc, 1); // right channel + connect(agc, 2, self(), 0); + connect(agc, 3, self(), 1); } wfmrx::~wfmrx() @@ -86,95 +80,20 @@ bool wfmrx::stop() return true; } -void wfmrx::set_quad_rate(float quad_rate) -{ - if (std::abs(d_quad_rate-quad_rate) > 0.5f) - { - qDebug() << "Changing WFM RX quad rate:" << d_quad_rate << "->" << quad_rate; - d_quad_rate = quad_rate; - lock(); - iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); - unlock(); - } -} - -void wfmrx::set_filter(double low, double high, double tw) -{ - filter->set_param(low, high, tw); -} - -float wfmrx::get_signal_level() -{ - return meter->get_level_db(); -} - -/* -void nbrx::set_nb_on(int nbid, bool on) -{ - if (nbid == 1) - nb->set_nb1_on(on); - else if (nbid == 2) - nb->set_nb2_on(on); -} - -void nbrx::set_nb_threshold(int nbid, float threshold) -{ - if (nbid == 1) - nb->set_threshold1(threshold); - else if (nbid == 2) - nb->set_threshold2(threshold); -} -*/ - -void wfmrx::set_sql_level(double level_db) -{ - sql->set_threshold(level_db); -} - -void wfmrx::set_sql_alpha(double alpha) -{ - sql->set_alpha(alpha); -} - -/* -void nbrx::set_agc_on(bool agc_on) -{ - agc->set_agc_on(agc_on); -} - -void nbrx::set_agc_hang(bool use_hang) -{ - agc->set_use_hang(use_hang); -} - -void nbrx::set_agc_threshold(int threshold) -{ - agc->set_threshold(threshold); -} - -void nbrx::set_agc_slope(int slope) +void wfmrx::set_filter(int low, int high, int tw) { - agc->set_slope(slope); + receiver_base_cf::set_filter(low, high, tw); + if(get_demod()!=Modulations::MODE_OFF) + filter->set_param(double(low), double(high), double(tw)); } -void nbrx::set_agc_decay(int decay_ms) -{ - agc->set_decay(decay_ms); -} - -void nbrx::set_agc_manual_gain(int gain) -{ - agc->set_manual_gain(gain); -} -*/ - -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; } @@ -183,65 +102,55 @@ 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, self(), 0); // left channel - disconnect(mono, 1, self(), 1); // right channel + 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, self(), 0); // left channel - disconnect(stereo, 1, self(), 1); // right channel + 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, self(), 0); // left channel - disconnect(stereo_oirt, 1, self(), 1); // right channel + disconnect(stereo_oirt, 0, agc, 0); // left channel + disconnect(stereo_oirt, 1, agc, 1); // right channel break; } switch (demod) { - case WFMRX_DEMOD_MONO: + case Modulations::MODE_WFM_MONO: default: connect(demod_fm, 0, mono, 0); - connect(mono, 0, self(), 0); // left channel - connect(mono, 1, self(), 1); // right channel + 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, self(), 0); // left channel - connect(stereo, 1, self(), 1); // right channel + 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, self(), 0); // left channel - connect(stereo_oirt, 1, self(), 1); // right channel + 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 cc0e03614..5b1864629 100644 --- a/src/receivers/wfmrx.h +++ b/src/receivers/wfmrx.h @@ -24,14 +24,11 @@ #ifndef WFMRX_H #define WFMRX_H -#include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" -#include "dsp/rx_meter.h" #include "dsp/rx_demod_fm.h" #include "dsp/stereo_demod.h" -#include "dsp/resampler_xx.h" #include "dsp/rx_rds.h" #include "dsp/rds/decoder.h" #include "dsp/rds/parser.h" @@ -56,70 +53,31 @@ 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_quad_rate(float quad_rate); - void set_filter(double low, double high, double tw); - void set_cw_offset(double offset) { (void)offset; } - - float get_signal_level(); + 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); - - /* Squelch parameter */ - bool has_sql() { return true; } - void set_sql_level(double level_db); - void set_sql_alpha(double alpha); - - /* AGC */ - bool has_agc() { return false; } - /*void set_agc_on(bool agc_on); - void set_agc_hang(bool use_hang); - void set_agc_threshold(int threshold); - void set_agc_slope(int slope); - void set_agc_decay(int decay_ms); - void set_agc_manual_gain(int gain);*/ - - 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(); + bool has_nb() override { return false; } + + void set_demod(Modulations::idx demod) override; + + 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. */ - float d_quad_rate; /*!< Input sample rate. */ - int d_audio_rate; /*!< Audio output rate. */ - - wfmrx_demod d_demod; /*!< Current demodulator. */ - resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ - rx_meter_c_sptr meter; /*!< Signal strength. */ - gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ stereo_demod_sptr stereo; /*!< FM stereo demodulator. */ stereo_demod_sptr stereo_oirt; /*!< FM stereo oirt demodulator. */