gqrx/mainwindow.cpp
2012-05-11 23:42:52 +02:00

1300 lines
38 KiB
C++

/* -*- c++ -*- */
/*
* Copyright 2011-2012 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 <QSettings>
#include <QByteArray>
#include <QDateTime>
#include <QDesktopServices>
#include <QDebug>
#include "qtgui/ioconfig.h"
#include "mainwindow.h"
/* Qt Designer files */
#include "ui_mainwindow.h"
/* DSP */
#include "receiver.h"
MainWindow::MainWindow(const QString cfgfile, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow),
d_lnb_lo(0),
dec_bpsk1000(0),
dec_afsk1200(0)
{
ui->setupUi(this);
/* Initialise default configuration directory */
QByteArray xdg_dir = qgetenv("XDG_CONFIG_HOME");
if (xdg_dir.isEmpty())
m_cfg_dir = QString("%1/.config/gqrx").arg(QDir::homePath()); // Qt takes care of conversion to native separators
else
m_cfg_dir = QString("%1/gqrx").arg(xdg_dir.data());
setWindowTitle(QString("gqrx %1 (Funcube Dongle)").arg(VERSION));
/* frequency control widget */
ui->freqCtrl->Setup(10, (quint64) 0, (quint64) 9999e6, 1, UNITS_MHZ);
ui->freqCtrl->SetFrequency(144500000);
d_filter_shape = receiver::FILTER_SHAPE_NORMAL;
/* create receiver object */
QString indev = CIoConfig::getFcdDeviceName();
//QString outdev = settings.value("output").toString();
rx = new receiver(indev.toStdString(), "");
rx->set_rf_freq(144500000.0f);
/* meter timer */
meter_timer = new QTimer(this);
connect(meter_timer, SIGNAL(timeout()), this, SLOT(meterTimeout()));
/* FFT timer & data */
iq_fft_timer = new QTimer(this);
connect(iq_fft_timer, SIGNAL(timeout()), this, SLOT(iqFftTimeout()));
audio_fft_timer = new QTimer(this);
connect(audio_fft_timer, SIGNAL(timeout()), this, SLOT(audioFftTimeout()));
d_fftData = new std::complex<float>[MAX_FFT_SIZE];
d_realFftData = new double[MAX_FFT_SIZE];
/* timer for data decoders */
dec_timer = new QTimer(this);
connect(dec_timer, SIGNAL(timeout()), this, SLOT(decoderTimeout()));
/* create dock widgets */
uiDockRxOpt = new DockRxOpt();
uiDockAudio = new DockAudio();
uiDockFcdCtl = new DockFcdCtl();
//uiDockIqPlay = new DockIqPlayer();
uiDockFft = new DockFft();
/* Add dock widgets to main window. This should be done even for
dock widgets that are going to be hidden, otherwise they will
end up floating in their own top-level window and can not be
docked to the mainwindow.
*/
addDockWidget(Qt::RightDockWidgetArea, uiDockFcdCtl);
addDockWidget(Qt::RightDockWidgetArea, uiDockRxOpt);
tabifyDockWidget(uiDockFcdCtl, uiDockRxOpt);
addDockWidget(Qt::RightDockWidgetArea, uiDockAudio);
addDockWidget(Qt::RightDockWidgetArea, uiDockFft);
tabifyDockWidget(uiDockAudio, uiDockFft);
//addDockWidget(Qt::BottomDockWidgetArea, uiDockIqPlay);
/* hide docks that we don't want to show initially */
uiDockFcdCtl->hide();
uiDockFft->hide();
//uiDockIqPlay->hide();
/* misc configurations */
//uiDockAudio->setFftRange(0, 8000); // FM
/* Add dock widget actions to View menu. By doing it this way all signal/slot
connections will be established automagially.
*/
ui->menu_View->addAction(uiDockFcdCtl->toggleViewAction());
ui->menu_View->addAction(uiDockRxOpt->toggleViewAction());
ui->menu_View->addAction(uiDockAudio->toggleViewAction());
ui->menu_View->addAction(uiDockFft->toggleViewAction());
//ui->menu_View->addAction(uiDockIqPlay->toggleViewAction());
ui->menu_View->addSeparator();
ui->menu_View->addAction(ui->mainToolBar->toggleViewAction());
ui->menu_View->addSeparator();
ui->menu_View->addAction(ui->actionFullScreen);
/* connect signals and slots */
connect(ui->freqCtrl, SIGNAL(NewFrequency(qint64)), this, SLOT(setNewFrequency(qint64)));
connect(uiDockFcdCtl, SIGNAL(lnbLoChanged(double)), this, SLOT(setLnbLo(double)));
connect(uiDockFcdCtl, SIGNAL(lnaGainChanged(float)), SLOT(setRfGain(float)));
connect(uiDockFcdCtl, SIGNAL(freqCorrChanged(int)), this, SLOT(setFreqCorr(int)));
connect(uiDockFcdCtl, SIGNAL(iqCorrChanged(double,double)), this, SLOT(setIqCorr(double,double)));
connect(uiDockRxOpt, SIGNAL(filterOffsetChanged(qint64)), this, SLOT(setFilterOffset(qint64)));
connect(uiDockRxOpt, SIGNAL(demodSelected(int)), this, SLOT(selectDemod(int)));
connect(uiDockRxOpt, SIGNAL(fmMaxdevSelected(float)), this, SLOT(setFmMaxdev(float)));
connect(uiDockRxOpt, SIGNAL(fmEmphSelected(double)), this, SLOT(setFmEmph(double)));
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(agcDecayChanged(int)), this, SLOT(setAgcDecay(int)));
connect(uiDockRxOpt, SIGNAL(noiseBlankerChanged(int,bool,float)), this, SLOT(setNoiseBlanker(int,bool,float)));
connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), this, SLOT(setSqlLevel(double)));
connect(uiDockAudio, SIGNAL(audioGainChanged(float)), this, SLOT(setAudioGain(float)));
connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString)));
connect(uiDockAudio, SIGNAL(audioRecStopped()), this, SLOT(stopAudioRec()));
connect(uiDockAudio, SIGNAL(audioPlayStarted(QString)), this, SLOT(startAudioPlayback(QString)));
connect(uiDockAudio, SIGNAL(audioPlayStopped()), this, SLOT(stopAudioPlayback()));
connect(uiDockAudio, SIGNAL(fftRateChanged(int)), this, SLOT(setAudioFftRate(int)));
connect(uiDockFft, SIGNAL(fftSizeChanged(int)), this, SLOT(setIqFftSize(int)));
connect(uiDockFft, SIGNAL(fftRateChanged(int)), this, SLOT(setIqFftRate(int)));
connect(uiDockFft, SIGNAL(fftSplitChanged(int)), this, SLOT(setIqFftSplit(int)));
// restore last session
loadConfig(cfgfile);
}
MainWindow::~MainWindow()
{
/* stop and delete timers */
dec_timer->stop();
delete dec_timer;
meter_timer->stop();
delete meter_timer;
iq_fft_timer->stop();
delete iq_fft_timer;
audio_fft_timer->stop();
delete audio_fft_timer;
if (m_settings)
{
m_settings->setValue("configversion", 2);
// save session
m_settings->setValue("input/frequency", ui->freqCtrl->GetFrequency());
if (d_lnb_lo)
m_settings->setValue("input/lnb_lo", d_lnb_lo);
else
m_settings->remove("input/lnb_lo");
double dblval = uiDockFcdCtl->lnaGain();
m_settings->setValue("input/gain", dblval);
m_settings->setValue("input/corr_freq", uiDockFcdCtl->freqCorr());
dblval = uiDockFcdCtl->iqGain();
if (dblval < 1.0)
m_settings->setValue("input/corr_iq_gain", dblval);
else
m_settings->remove("input/corr_iq_gain");
dblval = uiDockFcdCtl->iqPhase();
if (dblval != 0.0)
m_settings->setValue("input/corr_iq_phase", dblval);
else
m_settings->remove("input/corr_iq_phase");
m_settings->sync();
delete m_settings;
}
delete ui;
delete uiDockRxOpt;
delete uiDockAudio;
delete uiDockFft;
//delete uiDockIqPlay;
delete uiDockFcdCtl;
delete rx;
delete [] d_fftData;
delete [] d_realFftData;
}
/*! \brief Load new configuration.
* \param cfgfile
* \returns Always true.
*
* If cfgfile is an absolute path it will be used as is, otherwise it is assumed to be the
* name of a file under m_cfg_dir.
*
* If cfgfile does not exist it will be created.
*/
bool MainWindow::loadConfig(const QString cfgfile)
{
qDebug() << "Loading configuration from:" << cfgfile;
if (m_settings)
delete m_settings;
if (QDir::isAbsolutePath(cfgfile))
m_settings = new QSettings(cfgfile, QSettings::IniFormat);
else
m_settings = new QSettings(QString("%1/%2").arg(m_cfg_dir).arg(cfgfile), QSettings::IniFormat);
qDebug() << "Configuration file:" << m_settings->fileName();
emit configChanged(m_settings);
// manual reconf (FIXME: check status)
bool cok = false;
uiDockFcdCtl->setFreqCorr(m_settings->value("input/corr_freq", -115).toInt(&cok));
d_lnb_lo = m_settings->value("input/lnb_lo", 0).toLongLong(&cok);
uiDockFcdCtl->setLnbLo((double)d_lnb_lo/1.0e6);
ui->freqCtrl->SetFrequency(m_settings->value("input/frequency", 144500000).toLongLong(&cok));
uiDockFcdCtl->setLnaGain(m_settings->value("input/gain", 20).toFloat(&cok));
setRfGain(m_settings->value("input/gain", 20).toFloat(&cok));
uiDockFcdCtl->setIqGain(m_settings->value("input/corr_iq_gain", 1.0).toDouble(&cok));
uiDockFcdCtl->setIqPhase(m_settings->value("input/corr_iq_phase", 0.0).toDouble(&cok));
return true;
}
/*! \brief Save current configuration to a file.
* \param cfgfile
* \returns True if the operation was successful.
*
* If cfgfile is an absolute path it will be used as is, otherwise it is assumed to be the
* name of a file under m_cfg_dir.
*
* If cfgfile already exists it will be overwritten (we assume that a file selection dialog
* has already asked for confirmation of overwrite.
*
* Since QSettings does not support "save as" we do this by copying the current
* settings to a new file.
*/
bool MainWindow::saveConfig(const QString cfgfile)
{
QString oldfile = m_settings->fileName();
QString newfile;
qDebug() << "Saving configuration to:" << cfgfile;
m_settings->sync();
if (QDir::isAbsolutePath(cfgfile))
newfile = cfgfile;
else
newfile = QString("%1/%2").arg(m_cfg_dir).arg(cfgfile);
if (QFile::copy(oldfile, newfile))
{
loadConfig(cfgfile);
return true;
}
else
{
qDebug() << "Error saving configuration to" << newfile;
return false;
}
}
/*! \brief Slot for receiving frequency change signals.
* \param[in] freq The new frequency.
*
* This slot is connected to the CFreqCtrl::NewFrequency() signal and is used
* to set new RF frequency.
*/
void MainWindow::setNewFrequency(qint64 freq)
{
/* set receiver frequency */
rx->set_rf_freq((double) (freq-d_lnb_lo));
/* update pandapter */
ui->plotter->SetCenterFreq(freq);
/* update RX frequncy label in rxopts */
uiDockRxOpt->setRfFreq(freq);
}
/*! \brief Set new LNB LO frequency.
* \param freq_mhz The new frequency in MHz.
*/
void MainWindow::setLnbLo(double freq_mhz)
{
// calculate current RF frequency
qint64 rf_freq = ui->freqCtrl->GetFrequency() - d_lnb_lo;
d_lnb_lo = qint64(freq_mhz*1e6);
qDebug() << "New LNB LO:" << d_lnb_lo << "Hz";
// Show updated frequency in display
ui->freqCtrl->SetFrequency(d_lnb_lo + rf_freq);
}
/*! \brief Set new channel filter offset.
* \param freq_hs The new filter offset in Hz.
*/
void MainWindow::setFilterOffset(qint64 freq_hz)
{
rx->set_filter_offset((double) freq_hz);
ui->plotter->SetFilterOffset(freq_hz);
}
/*! \brief Set RF gain.
* \param gain The new RF gain.
*
* Valid range depends on hardware.
*/
void MainWindow::setRfGain(float gain)
{
rx->set_rf_gain(gain);
}
/*! \brief Set new frequency offset value.
* \param ppm Frequency correction.
*
* The valid range is between -200 and 200, though this is not checked.
*/
void MainWindow::setFreqCorr(int ppm)
{
qDebug() << "PPM:" << ppm;
rx->set_freq_corr(ppm);
}
/*! \brief Set new DC offset values.
* \param dci I correction.
* \param dcq Q correction.
*
* The valid range is between -1.0 and 1.0, though hthis is not checked.
*/
void MainWindow::setDcCorr(double dci, double dcq)
{
qDebug() << "DCI:" << dci << " DCQ:" << dcq;
rx->set_dc_corr(dci, dcq);
}
/*! \brief Set new IQ correction values.
* \param gain IQ gain correction.
* \param phase IQ phase correction.
*
* The valid range is between -1.0 and 1.0, though hthis is not checked.
*/
void MainWindow::setIqCorr(double gain, double phase)
{
qDebug() << "Gain:" << gain << " Phase:" << phase;
rx->set_iq_corr(gain, phase);
}
/*! \brief Select new demodulator.
* \param demod New demodulator index.
*
* This slot basically maps the index of the mode selector to receiver::demod
* and configures the default channel filter.
*
*/
void MainWindow::selectDemod(int index)
{
float maxdev;
int filter_preset = uiDockRxOpt->currentFilter();
int flo, fhi;
switch (index) {
case 0:
/* Raw I/Q */
qDebug() << "RAW I/Q mode not implemented!";
break;
/* AM */
case 1:
rx->set_demod(receiver::DEMOD_AM);
ui->plotter->SetDemodRanges(-20000, -100, 100, 20000, true);
uiDockAudio->setFftRange(0,15000);
switch (filter_preset) {
case 0: //wide
flo = -10000;
fhi = 10000;
break;
case 2: // narrow
flo = -2500;
fhi = 2500;
break;
default: // normal
flo = -5000;
fhi = 5000;
break;
}
break;
/* FM */
case 2:
rx->set_demod(receiver::DEMOD_FM);
maxdev = uiDockRxOpt->currentMaxdev();
if (maxdev < 20000.0) {
ui->plotter->SetDemodRanges(-25000, -100, 100, 25000, true);
uiDockAudio->setFftRange(0,12000);
switch (filter_preset) {
case 0: //wide
flo = -10000;
fhi = 10000;
break;
case 2: // narrow
flo = -2500;
fhi = 2500;
break;
default: // normal
flo = -5000;
fhi = 5000;
break;
}
}
else {
ui->plotter->SetDemodRanges(-45000, -10000, 10000, 45000, true);
uiDockAudio->setFftRange(0,24000);
switch (filter_preset) {
/** FIXME: not sure about these **/
case 0: //wide
flo = -45000;
fhi = 45000;
break;
case 2: // narrow
flo = -10000;
fhi = 10000;
break;
default: // normal
flo = -35000;
fhi = 35000;
break;
}
}
break;
/* LSB */
case 3:
rx->set_demod(receiver::DEMOD_SSB);
ui->plotter->SetDemodRanges(-10000, -100, -5000, 0, false);
uiDockAudio->setFftRange(0,3500);
switch (filter_preset) {
case 0: //wide
flo = -4100;
fhi = -100;
break;
case 2: // narrow
flo = -1600;
fhi = -200;
break;
default: // normal
flo = -3000;
fhi = -200;
break;
}
break;
/* USB */
case 4:
rx->set_demod(receiver::DEMOD_SSB);
ui->plotter->SetDemodRanges(0, 5000, 100, 10000, false);
uiDockAudio->setFftRange(0,3500);
switch (filter_preset) {
case 0: //wide
flo = 100;
fhi = 4100;
break;
case 2: // narrow
flo = 200;
fhi = 1600;
break;
default: // normal
flo = 200;
fhi = 3000;
break;
}
break;
/* CWL */
case 5:
rx->set_demod(receiver::DEMOD_SSB);
ui->plotter->SetDemodRanges(-10000, -100, -5000, 0, false);
uiDockAudio->setFftRange(0,1500);
switch (filter_preset) {
case 0: //wide
flo = -2300;
fhi = -200;
break;
case 2: // narrow
flo = -900;
fhi = -400;
break;
default: // normal
flo = -1200;
fhi = -200;
break;
}
break;
/* CWU */
case 6:
rx->set_demod(receiver::DEMOD_SSB);
ui->plotter->SetDemodRanges(0, 5000, 100, 10000, false);
uiDockAudio->setFftRange(0,1500);
switch (filter_preset) {
case 0: //wide
flo = 200;
fhi = 2300;
break;
case 2: // narrow
flo = 400;
fhi = 900;
break;
default: // normal
flo = 200;
fhi = 1200;
break;
}
break;
default:
qDebug() << "Invalid mode selection: " << index;
flo = -5000;
fhi = 5000;
break;
}
qDebug() << "Filter preset for mode" << index << "LO:" << flo << "HI:" << fhi;
ui->plotter->SetHiLowCutFrequencies(flo, fhi);
rx->set_filter((double)flo, (double)fhi, receiver::FILTER_SHAPE_NORMAL);
}
/*! \brief New FM deviation selected.
* \param max_dev The enw FM deviation.
*/
void MainWindow::setFmMaxdev(float max_dev)
{
qDebug() << "FM MAX_DEV: " << max_dev;
/* receiver will check range */
rx->set_fm_maxdev(max_dev);
/* update filter */
if (max_dev < 20000.0) {
ui->plotter->SetDemodRanges(-25000, -1000, 1000, 25000, true);
ui->plotter->SetHiLowCutFrequencies(-5000, 5000);
rx->set_filter(-5000.0, 5000.0, receiver::FILTER_SHAPE_NORMAL);
}
else {
ui->plotter->SetDemodRanges(-45000, -10000, 10000, 45000, true);
ui->plotter->SetHiLowCutFrequencies(-35000, 35000);
rx->set_filter(-35000.0, 35000.0, receiver::FILTER_SHAPE_NORMAL);
}
}
/*! \brief New FM de-emphasis time consant selected.
* \param tau The new time constant
*/
void MainWindow::setFmEmph(double tau)
{
qDebug() << "FM TAU: " << tau;
/* receiver will check range */
rx->set_fm_deemph(tau);
}
/*! \brief AM DCR status changed (slot).
* \param enabled Whether DCR is enabled or not.
*/
void MainWindow::setAmDcrStatus(bool enabled)
{
rx->set_am_dcr(enabled);
}
/*! \brief Audio gain changed.
* \param value The new audio gain in dB.
*/
void MainWindow::setAudioGain(float value)
{
rx->set_af_gain(value);
}
/*! \brief Set AGC ON/OFF.
* \param agc_on Whether AGC is ON (true) or OFF (false).
*/
void MainWindow::setAgcOn(bool agc_on)
{
rx->set_agc_on(agc_on);
}
/*! \brief AGC hang ON/OFF.
* \param use_hang Whether to use hang.
*/
void MainWindow::setAgcHang(bool use_hang)
{
rx->set_agc_hang(use_hang);
}
/*! \brief AGC threshold changed.
* \param threshold The new threshold.
*/
void MainWindow::setAgcThreshold(int threshold)
{
rx->set_agc_threshold(threshold);
}
/*! \brief AGC slope factor changed.
* \param factor The new slope factor.
*/
void MainWindow::setAgcSlope(int factor)
{
rx->set_agc_slope(factor);
}
/*! \brief AGC manual gain changed.
* \param gain The new manual gain in dB.
*/
void MainWindow::setAgcGain(int gain)
{
rx->set_agc_manual_gain(gain);
}
/*! \brief AGC decay changed.
* \param factor The new AGC decay.
*/
void MainWindow::setAgcDecay(int msec)
{
rx->set_agc_decay(msec);
}
/*! \brief Noide blanker configuration changed.
* \param nb1 Noise blanker 1 ON/OFF.
* \param nb2 Noise blanker 2 ON/OFF.
* \param threshold Noise blanker threshold.
*/
void MainWindow::setNoiseBlanker(int nbid, bool on, float threshold)
{
qDebug() << "Noise blanker NB:" << nbid << " ON:" << on << "THLD:" << threshold;
rx->set_nb_on(nbid, on);
rx->set_nb_threshold(nbid, threshold);
}
/*! \brief Squelch level changed.
* \param level_db The new squelch level in dBFS.
*/
void MainWindow::setSqlLevel(double level_db)
{
rx->set_sql_level(level_db);
}
/*! \brief Signal strength meter timeout */
void MainWindow::meterTimeout()
{
float level;
level = rx->get_signal_pwr(true);
ui->sMeter->setLevel(level);
}
/*! \brief Baseband FFT plot timeout. */
void MainWindow::iqFftTimeout()
{
int fftsize;
int i;
std::complex<float> pt; /* a single FFT point used in calculations */
std::complex<float> scaleFactor; /* normalizing factor (fftsize cast to complex) */
double min=0.0,max=-120.0,avg=0.0;
rx->get_iq_fft_data(d_fftData, fftsize);
if (fftsize == 0) {
/* nothing to do, wait until next activation. */
return;
}
scaleFactor = std::complex<float>((float)fftsize);
/** FIXME: move post processing to rx_fft_c **/
/* Normalize, calculcate power and shift the FFT */
for (i = 0; i < fftsize; i++) {
/* normalize and shift */
if (i < fftsize/2) {
pt = d_fftData[fftsize/2+i] / scaleFactor;
}
else {
pt = d_fftData[i-fftsize/2] / scaleFactor;
}
/* calculate power in dBFS */
d_realFftData[i] = 10.0 * log10(pt.imag()*pt.imag() + pt.real()*pt.real() + 1.0e-20);
/*
if (d_realFftData[i] < min)
min = d_realFftData[i];
if (d_realFftData[i] > max)
max = d_realFftData[i];
avg = (avg+d_realFftData[i]) / 2.0;
*/
}
ui->plotter->SetNewFttData(d_realFftData, fftsize);
//qDebug() << "FFT size: " << fftsize;
//qDebug() << "FFT[0]=" << d_realFftData[0] << " FFT[MID]=" << d_realFftData[fftsize/2];
//qDebug() << "MIN:" << min << " AVG:" << avg << " MAX:" << max;
}
/*! \brief Audio FFT plot timeout. */
void MainWindow::audioFftTimeout()
{
int fftsize;
int i;
std::complex<float> pt; /* a single FFT point used in calculations */
std::complex<float> scaleFactor; /* normalizing factor (fftsize cast to complex) */
double min=0.0,max=-120.0,avg=0.0;
rx->get_audio_fft_data(d_fftData, fftsize);
if (fftsize == 0) {
/* nothing to do, wait until next activation. */
qDebug() << "No audio FFT data.";
return;
}
scaleFactor = std::complex<float>((float)fftsize);
/** FIXME: move post processing to rx_fft_f **/
/* Normalize, calculcate power and shift the FFT */
for (i = 0; i < fftsize; i++) {
/* normalize and shift */
if (i < fftsize/2) {
pt = d_fftData[fftsize/2+i] / scaleFactor;
}
else {
pt = d_fftData[i-fftsize/2] / scaleFactor;
}
/* calculate power in dBFS */
d_realFftData[i] = 10.0 * log10(pt.imag()*pt.imag() + pt.real()*pt.real() + 1.0e-20);
}
uiDockAudio->setNewFttData(d_realFftData, fftsize);
}
/*! \brief Start audio recorder.
* \param filename The file name into which audio should be recorded.
*/
void MainWindow::startAudioRec(const QString filename)
{
if (rx->start_audio_recording(filename.toStdString())) {
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));
}
}
/*! \brief Stop audio recorder. */
void MainWindow::stopAudioRec()
{
if (rx->stop_audio_recording()) {
/* okay, this one would be weird if it really happened */
ui->statusBar->showMessage(tr("Error stopping audio recorder"));
uiDockAudio->setAudioRecButtonState(true);
}
else {
ui->statusBar->showMessage(tr("Audio recorder stopped"), 5000);
}
}
/*! \brief Start playback of audio file. */
void MainWindow::startAudioPlayback(const QString filename)
{
if (rx->start_audio_playback(filename.toStdString())) {
ui->statusBar->showMessage(tr("Error trying to play %1").arg(filename));
/* reset state of record button */
uiDockAudio->setAudioPlayButtonState(false);
}
else {
ui->statusBar->showMessage(tr("Playing %1").arg(filename));
}
}
/*! \brief Stop playback of audio file. */
void MainWindow::stopAudioPlayback()
{
if (rx->stop_audio_playback()) {
/* okay, this one would be weird if it really happened */
ui->statusBar->showMessage(tr("Error stopping audio playback"));
uiDockAudio->setAudioPlayButtonState(true);
}
else {
ui->statusBar->showMessage(tr("Audio playback stopped"), 5000);
}
}
/*! \brief Start/stop I/Q data playback.
* \param play True if playback is started, false if it is stopped.
* \param filename Full path of the I/Q data file.
*/
void MainWindow::toggleIqPlayback(bool play, const QString filename)
{
if (play) {
/* starting playback */
if (rx->start_iq_playback(filename.toStdString(), 96000.0)) {
ui->statusBar->showMessage(tr("Error trying to play %1").arg(filename));
}
else {
ui->statusBar->showMessage(tr("Playing %1").arg(filename));
/* disable REC button */
ui->actionIqRec->setEnabled(false);
}
}
else {
/* stopping playback */
if (rx->stop_iq_playback()) {
/* okay, this one would be weird if it really happened */
ui->statusBar->showMessage(tr("Error stopping I/Q playback"));
}
else {
ui->statusBar->showMessage(tr("I/Q playback stopped"), 5000);
}
/* enable REC button */
ui->actionIqRec->setEnabled(true);
}
}
/*! \brief FFT size has changed. */
void MainWindow::setIqFftSize(int size)
{
qDebug() << "Changing baseband FFT size TBD...";
}
/*! \brief Baseband FFT rate has changed. */
void MainWindow::setIqFftRate(int fps)
{
int interval = 1000 / fps;
if (interval < 10)
return;
if (iq_fft_timer->isActive())
iq_fft_timer->setInterval(interval);
}
/*! \brief Vertical split between waterfall and pandapter changed.
* \param pct_pand The percentage of the waterfall.
*/
void MainWindow::setIqFftSplit(int pct_wf)
{
if ((pct_wf >= 20) && (pct_wf <= 80)) {
ui->plotter->SetPercent2DScreen(pct_wf);
}
}
/*! \brief Audio FFT rate has changed. */
void MainWindow::setAudioFftRate(int fps)
{
int interval = 1000 / fps;
if (interval < 10)
return;
if (audio_fft_timer->isActive())
audio_fft_timer->setInterval(interval);
}
/*! \brief Start/Stop DSP processing.
* \param checked Flag indicating whether DSP processing should be ON or OFF.
*
* This slot is executed when the actionDSP is toggled by the user. This can either be
* via the menu bar or the "power on" button in the main toolbar.
*/
void MainWindow::on_actionDSP_triggered(bool checked)
{
if (checked) {
/* start receiver */
rx->start();
/* start GUI timers */
meter_timer->start(100);
iq_fft_timer->start(1000/uiDockFft->fftRate());
audio_fft_timer->start(1000/uiDockAudio->fftRate());
/* update menu text and button tooltip */
ui->actionDSP->setToolTip(tr("Stop DSP processing"));
ui->actionDSP->setText(tr("Stop DSP"));
}
else {
/* stop GUI timers */
meter_timer->stop();
iq_fft_timer->stop();
audio_fft_timer->stop();
/* stop receiver */
rx->stop();
/* update menu text and button tooltip */
ui->actionDSP->setToolTip(tr("Start DSP processing"));
ui->actionDSP->setText(tr("Start DSP"));
}
}
/*! \brief Load configuration activated by user. */
void MainWindow::on_actionLoadSettings_triggered()
{
QString cfgfile = QFileDialog::getOpenFileName(this,
tr("Load settings"),
m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir,
tr("Settings (*.conf)"));
qDebug() << "File to open:" << cfgfile;
if (cfgfile.isEmpty())
return;
if (!cfgfile.endsWith(".conf", Qt::CaseSensitive))
cfgfile.append(".conf");
loadConfig(cfgfile);
// store last dir
QFileInfo fi(cfgfile);
if (m_cfg_dir != fi.absolutePath())
m_last_dir = fi.absolutePath();
}
/*! \brief Save configuration activated by user. */
void MainWindow::on_actionSaveSettings_triggered()
{
QString cfgfile = QFileDialog::getSaveFileName(this,
tr("Save settings"),
m_last_dir.isEmpty() ? m_cfg_dir : m_last_dir,
tr("Settings (*.conf)"));
qDebug() << "File to save:" << cfgfile;
if (cfgfile.isEmpty())
return;
if (!cfgfile.endsWith(".conf", Qt::CaseSensitive))
cfgfile.append(".conf");
saveConfig(cfgfile);
// store last dir
QFileInfo fi(cfgfile);
if (m_cfg_dir != fi.absolutePath())
m_last_dir = fi.absolutePath();
}
/*! \brief Toggle I/Q recording. */
void MainWindow::on_actionIqRec_triggered(bool checked)
{
#if 0
if (checked) {
/* generate file name using date, time, rf freq and BW */
int freq = (int)rx->get_rf_freq()/1000;
// FIXME: option to use local time
QString lastRec = QDateTime::currentDateTimeUtc().toString("gqrx-yyyyMMdd-hhmmss-%1-96.'bin'").arg(freq);
/* start recorder */
if (rx->start_iq_recording(lastRec.toStdString())) {
/* reset action status */
ui->actionIqRec->toggle();
ui->statusBar->showMessage(tr("Error starting I/Q recoder"));
}
else {
ui->statusBar->showMessage(tr("Recording I/Q data to: %1").arg(lastRec), 5000);
/* disable I/Q player */
uiDockIqPlay->setEnabled(false);
}
}
else {
/* stop current recording */
if (rx->stop_iq_recording()) {
ui->statusBar->showMessage(tr("Error stopping I/Q recoder"));
}
else {
ui->statusBar->showMessage(tr("I/Q data recoding stopped"), 5000);
}
/* enable I/Q player */
uiDockIqPlay->setEnabled(true);
}
#endif
}
/* CPlotter::NewDemodFreq() is emitted */
void MainWindow::on_plotter_NewDemodFreq(qint64 freq, qint64 delta)
{
// set RX filter
rx->set_filter_offset((double) delta);
// update RF freq label and channel filter offset
uiDockRxOpt->setFilterOffset(delta);
uiDockRxOpt->setRfFreq(freq-delta);
}
/* CPlotter::NewfilterFreq() is emitted */
void MainWindow::on_plotter_NewFilterFreq(int low, int high)
{
receiver::status retcode;
/* parameter correctness will be checked in receiver class */
retcode = rx->set_filter((double) low, (double) high, d_filter_shape);
}
/*! \brief Full screen button or menu item toggled. */
void MainWindow::on_actionFullScreen_triggered(bool checked)
{
if (checked)
{
ui->statusBar->hide();
showFullScreen();
}
else
{
ui->statusBar->show();
showNormal();
}
}
/*! \brief Action: I/O device configurator triggered.
*
* This slot is activated when the user selects "I/O Devices" in the
* menu. It activates the I/O configurator and if the user closes the
* configurator using the OK button, the new configuration is read and
* sent to the receiver.
*/
void MainWindow::on_actionIODevices_triggered()
{
QSettings settings;
QString cindev = settings.value("input").toString();
QString coutdev = settings.value("output").toString();
CIoConfig *ioconf = new CIoConfig();
int confres = ioconf->exec();
if (confres == QDialog::Accepted) {
QString nindev = settings.value("input").toString();
QString noutdev = settings.value("output").toString();
// we need to ensure that we don't reconfigure RX
// with the same device as the already used one because
// that can crash the receiver when using ALSA :(
if (cindev != nindev)
rx->set_input_device(nindev.toStdString());
if (coutdev != noutdev)
rx->set_output_device(noutdev.toStdString());
}
delete ioconf;
}
#define DATA_BUFFER_SIZE 48000
/*! \brief AFSK1200 decoder action triggered.
*
* This slot is called when the user activates the AFSK1200
* action. It will create an AFSK1200 decoder window and start
* and start pushing data from the receiver to it.
*/
void MainWindow::on_actionAFSK1200_triggered()
{
if (dec_afsk1200 != 0) {
qDebug() << "AFSK1200 decoder already active.";
dec_afsk1200->raise();
}
else {
qDebug() << "Starting AFSK1200 decoder.";
/* start sample sniffer */
if (rx->start_sniffer(22050, DATA_BUFFER_SIZE) == receiver::STATUS_OK) {
dec_afsk1200 = new Afsk1200Win(this);
connect(dec_afsk1200, SIGNAL(windowClosed()), this, SLOT(afsk1200win_closed()));
dec_afsk1200->show();
dec_timer->start(100);
}
else {
int ret = QMessageBox::warning(this, tr("Gqrx error"),
tr("Error starting sample sniffer.\n"
"Close all data decoders and try again."),
QMessageBox::Ok, QMessageBox::Ok);
}
}
}
/*! \brief Destroy AFSK1200 decoder window got closed.
*
* This slot is connected to the windowClosed() signal of the AFSK1200 decoder
* object. We need this to properly destroy the object, stop timeout and clean
* up whatever need to be cleaned up.
*/
void MainWindow::afsk1200win_closed()
{
/* stop cyclic processing */
dec_timer->stop();
rx->stop_sniffer();
/* delete decoder object */
delete dec_afsk1200;
dec_afsk1200 = 0;
}
/*! \brief BPSK1000 decoder action triggered.
*
* This slot is called when the user activates the BPSK1000
* action. It will create an BPSK1000 decoder window and start
* and start pushing data from the receiver to it.
*/
void MainWindow::on_actionBPSK1000_triggered()
{
if (dec_bpsk1000 != 0) {
qDebug() << "BPSK1000 decoder already active.";
dec_bpsk1000->raise();
}
else {
qDebug() << "Starting BPSK1000 decoder.";
/* start sample sniffer */
if (rx->start_sniffer(48000, DATA_BUFFER_SIZE) == receiver::STATUS_OK) {
dec_bpsk1000 = new Bpsk1000Win(this);
connect(dec_bpsk1000, SIGNAL(windowClosed()), this, SLOT(bpsk1000win_closed()));
dec_bpsk1000->show();
dec_timer->start(100);
}
else {
int ret = QMessageBox::warning(this, tr("Gqrx error"),
tr("Error starting sample sniffer.\n"
"Close all data decoders and try again."),
QMessageBox::Ok, QMessageBox::Ok);
}
}
}
/*! \brief Destroy BPSK1000 decoder window got closed.
*
* This slot is connected to the windowClosed() signal of the BPSK1000 decoder
* object. We need this to properly destroy the object, stop timeout and clean
* up whatever need to be cleaned up.
*/
void MainWindow::bpsk1000win_closed()
{
/* stop cyclic processing */
dec_timer->stop();
rx->stop_sniffer();
/* delete decoder object */
delete dec_bpsk1000;
dec_bpsk1000 = 0;
}
/*! \brief Cyclic processing for acquiring samples from receiver and
* processing them with data decoders (see dec_* objects)
*/
void MainWindow::decoderTimeout()
{
float buffer[DATA_BUFFER_SIZE];
int num;
//qDebug() << "Process decoder";
rx->get_sniffer_data(&buffer[0], num);
if (dec_bpsk1000) {
dec_bpsk1000->process_samples(&buffer[0], num);
}
else if (dec_afsk1200) {
dec_afsk1200->process_samples(&buffer[0], num);
}
/* else stop timeout and sniffer? */
}
/*! \brief Launch Gqrx google group website. */
void MainWindow::on_actionUserGroup_triggered()
{
bool res = QDesktopServices::openUrl(QUrl("https://groups.google.com/forum/#!forum/gqrx",
QUrl::TolerantMode));
if (!res)
{
QMessageBox::warning(this, tr("Error"),
tr("Failed to open website:\n"
"https://groups.google.com/forum/#!forum/gqrx"),
QMessageBox::Close);
}
}
/*! \brief Action: About Qthid
*
* This slot is called when the user activates the
* Help|About menu item (or Gqrx|About on Mac)
*/
void MainWindow::on_actionAbout_triggered()
{
QMessageBox::about(this, tr("About Gqrx"),
tr("<p>This is Gqrx %1</p>"
/*"<p><b>This is a beta release.</b></p>"*/
"<p>Gqrx is a software defined radio receiver for the Funcube Dongle.</p>"
"<p>Gqrx is powered by GNU Radio and the Qt toolkit (see About Qt) and is "
"currently available for Linux. You can download the latest version from the "
"<a href='http://gqrx.sf.net/'>Gqrx website</a>.</p>"
"<p>"
"You can get help in the <a href='https://groups.google.com/forum/#!forum/gqrx'>Gqrx Google group</a>."
"</p>"
"<p>"
"<a href='http://www.gnuradio.org/'>GNU Radio website</a><br/>"
"<a href='http://funcubedongle.com/'>Funcube Dongle website</a><br/>"
/*"<a href='http://www.ettus.com/'>Ettus Research (USRP)</a><br/>"*/
"</p>"
"<p>"
"Gqrx license: <a href='http://www.gnu.org/licenses/gpl.html'>GNU GPL</a>."
"</p>").arg(VERSION));
}
/*! \brief Action: About Qt
*
* This slot is called when the user activates the
* Help|About Qt menu item (or Gqrx|About Qt on Mac)
*/
void MainWindow::on_actionAboutQt_triggered()
{
QMessageBox::aboutQt(this, tr("About Qt"));
}