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 @@
+
+
+
\ 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 @@
+
+
+
+
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 @@
+
+
+
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
@@ -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. */