/* -*- 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 #include #include #include #include #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[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 pt; /* a single FFT point used in calculations */ std::complex 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)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 pt; /* a single FFT point used in calculations */ std::complex 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)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("

This is Gqrx %1

" /*"

This is a beta release.

"*/ "

Gqrx is a software defined radio receiver for the Funcube Dongle.

" "

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 " "Gqrx website.

" "

" "You can get help in the Gqrx Google group." "

" "

" "GNU Radio website
" "Funcube Dongle website
" /*"Ettus Research (USRP)
"*/ "

" "

" "Gqrx license: GNU GPL." "

").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")); }