1 /* -*- c++ -*- */
2 /*
3  * Gqrx SDR: Software defined radio receiver powered by GNU Radio and Qt
4  *           https://gqrx.dk/
5  *
6  * Copyright 2011-2016 Alexandru Csete OZ9AEC.
7  *
8  * Gqrx is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3, or (at your option)
11  * any later version.
12  *
13  * Gqrx is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with Gqrx; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street,
21  * Boston, MA 02110-1301, USA.
22  */
23 #include <cmath>
24 #include <QDebug>
25 #include <QDateTime>
26 #include <QShortcut>
27 #include <QDir>
28 #include "dockaudio.h"
29 #include "ui_dockaudio.h"
30 
31 #define DEFAULT_FFT_SPLIT 100
32 
DockAudio(QWidget * parent)33 DockAudio::DockAudio(QWidget *parent) :
34     QDockWidget(parent),
35     ui(new Ui::DockAudio),
36     autoSpan(true),
37     rx_freq(144000000)
38 {
39     ui->setupUi(this);
40 
41 #ifdef Q_OS_LINUX
42     // buttons can be smaller than 50x32
43     ui->audioMuteButton->setMinimumSize(48, 24);
44     ui->audioStreamButton->setMinimumSize(48, 24);
45     ui->audioRecButton->setMinimumSize(48, 24);
46     ui->audioPlayButton->setMinimumSize(48, 24);
47     ui->audioConfButton->setMinimumSize(48, 24);
48 #endif
49 
50     audioOptions = new CAudioOptions(this);
51 
52     connect(audioOptions, SIGNAL(newFftSplit(int)), ui->audioSpectrum, SLOT(setPercent2DScreen(int)));
53     connect(audioOptions, SIGNAL(newPandapterRange(int,int)), this, SLOT(setNewPandapterRange(int,int)));
54     connect(audioOptions, SIGNAL(newWaterfallRange(int,int)), this, SLOT(setNewWaterfallRange(int,int)));
55     connect(audioOptions, SIGNAL(newRecDirSelected(QString)), this, SLOT(setNewRecDir(QString)));
56     connect(audioOptions, SIGNAL(newUdpHost(QString)), this, SLOT(setNewUdpHost(QString)));
57     connect(audioOptions, SIGNAL(newUdpPort(int)), this, SLOT(setNewUdpPort(int)));
58     connect(audioOptions, SIGNAL(newUdpStereo(bool)), this, SLOT(setNewUdpStereo(bool)));
59 
60     connect(ui->audioSpectrum, SIGNAL(pandapterRangeChanged(float,float)), audioOptions, SLOT(setPandapterSliderValues(float,float)));
61 
62     ui->audioSpectrum->setFreqUnits(1000);
63     ui->audioSpectrum->setSampleRate(48000);  // Full bandwidth
64     ui->audioSpectrum->setSpanFreq(12000);
65     ui->audioSpectrum->setCenterFreq(0);
66     ui->audioSpectrum->setPercent2DScreen(DEFAULT_FFT_SPLIT);
67     ui->audioSpectrum->setFftCenterFreq(6000);
68     ui->audioSpectrum->setDemodCenterFreq(0);
69     ui->audioSpectrum->setFilterBoxEnabled(false);
70     ui->audioSpectrum->setCenterLineEnabled(false);
71     ui->audioSpectrum->setBookmarksEnabled(false);
72     ui->audioSpectrum->setBandPlanEnabled(false);
73     ui->audioSpectrum->setFftRange(-80., 0.);
74     ui->audioSpectrum->setVdivDelta(40);
75     ui->audioSpectrum->setHdivDelta(40);
76     ui->audioSpectrum->setFreqDigits(1);
77 
78     QShortcut *rec_toggle_shortcut = new QShortcut(QKeySequence(Qt::Key_R), this);
79     QShortcut *mute_toggle_shortcut = new QShortcut(QKeySequence(Qt::Key_M), this);
80     QShortcut *audio_gain_increase_shortcut1 = new QShortcut(QKeySequence(Qt::Key_Plus), this);
81     QShortcut *audio_gain_decrease_shortcut1 = new QShortcut(QKeySequence(Qt::Key_Minus), this);
82 
83     QObject::connect(rec_toggle_shortcut, &QShortcut::activated, this, &DockAudio::recordToggleShortcut);
84     QObject::connect(mute_toggle_shortcut, &QShortcut::activated, this, &DockAudio::muteToggleShortcut);
85     QObject::connect(audio_gain_increase_shortcut1, &QShortcut::activated, this, &DockAudio::increaseAudioGainShortcut);
86     QObject::connect(audio_gain_decrease_shortcut1, &QShortcut::activated, this, &DockAudio::decreaseAudioGainShortcut);
87 }
88 
~DockAudio()89 DockAudio::~DockAudio()
90 {
91     delete ui;
92 }
93 
setFftRange(quint64 minf,quint64 maxf)94 void DockAudio::setFftRange(quint64 minf, quint64 maxf)
95 {
96     if (autoSpan)
97     {
98         qint32 span = (qint32)(maxf - minf);
99         quint64 fc = minf + (maxf - minf)/2;
100 
101         ui->audioSpectrum->setFftCenterFreq(fc);
102         ui->audioSpectrum->setSpanFreq(span);
103         ui->audioSpectrum->setCenterFreq(0);
104     }
105 }
106 
setNewFftData(float * fftData,int size)107 void DockAudio::setNewFftData(float *fftData, int size)
108 {
109     ui->audioSpectrum->setNewFftData(fftData, size);
110 }
111 
setInvertScrolling(bool enabled)112 void DockAudio::setInvertScrolling(bool enabled)
113 {
114     ui->audioSpectrum->setInvertScrolling(enabled);
115 }
116 
117 /*! \brief Set new audio gain.
118  *  \param gain the new audio gain in tens of dB (0 dB = 10)
119  */
setAudioGain(int gain)120 void DockAudio::setAudioGain(int gain)
121 {
122     ui->audioGainSlider->setValue(gain);
123 }
124 
125 
126 /*! \brief Get current audio gain.
127  *  \returns The current audio gain in tens of dB (0 dB = 10).
128  */
audioGain()129 int  DockAudio::audioGain()
130 {
131     return ui->audioGainSlider->value();
132 }
133 
134 /*! Set FFT plot color. */
setFftColor(QColor color)135 void DockAudio::setFftColor(QColor color)
136 {
137     ui->audioSpectrum->setFftPlotColor(color);
138 }
139 
140 /*! Enable/disable filling area under FFT plot. */
setFftFill(bool enabled)141 void DockAudio::setFftFill(bool enabled)
142 {
143     ui->audioSpectrum->setFftFill(enabled);
144 }
145 
146 /*! Public slot to trig audio recording by external events (e.g. satellite AOS).
147  *
148  * If a recording is already in progress we ignore the event.
149  */
startAudioRecorder(void)150 void DockAudio::startAudioRecorder(void)
151 {
152     if (ui->audioRecButton->isChecked())
153     {
154         qDebug() << __func__ << "An audio recording is already in progress";
155         return;
156     }
157 
158     // emulate a button click
159     ui->audioRecButton->click();
160 }
161 
162 /*! Public slot to stop audio recording by external events (e.g. satellite LOS).
163  *
164  * The event is ignored if no audio recording is in progress.
165  */
stopAudioRecorder(void)166 void DockAudio::stopAudioRecorder(void)
167 {
168     if (ui->audioRecButton->isChecked())
169         ui->audioRecButton->click(); // emulate a button click
170     else
171         qDebug() << __func__ << "No audio recording in progress";
172 }
173 
174 /*! Public slot to set new RX frequency in Hz. */
setRxFrequency(qint64 freq)175 void DockAudio::setRxFrequency(qint64 freq)
176 {
177     rx_freq = freq;
178 }
179 
setWfColormap(const QString & cmap)180 void DockAudio::setWfColormap(const QString &cmap)
181 {
182     ui->audioSpectrum->setWfColormap(cmap);
183 }
184 
185 /*! \brief Audio gain changed.
186  *  \param value The new audio gain value in tens of dB (because slider uses int)
187  */
on_audioGainSlider_valueChanged(int value)188 void DockAudio::on_audioGainSlider_valueChanged(int value)
189 {
190     float gain = float(value) / 10.0;
191 
192     // update dB label
193     ui->audioGainDbLabel->setText(QString("%1 dB").arg(gain, 5, 'f', 1));
194     if (!ui->audioMuteButton->isChecked())
195         emit audioGainChanged(gain);
196 }
197 
198 /*! \brief Streaming button clicked.
199  *  \param checked Whether streaming is ON or OFF.
200  */
on_audioStreamButton_clicked(bool checked)201 void DockAudio::on_audioStreamButton_clicked(bool checked)
202 {
203     if (checked)
204         emit audioStreamingStarted(udp_host, udp_port, udp_stereo);
205     else
206         emit audioStreamingStopped();
207 }
208 
209 /*! \brief Record button clicked.
210  *  \param checked Whether recording is ON or OFF.
211  *
212  * We use the clicked signal instead of the toggled which allows us to change the
213  * state programmatically using toggle() without triggering the signal.
214  */
on_audioRecButton_clicked(bool checked)215 void DockAudio::on_audioRecButton_clicked(bool checked)
216 {
217     if (checked) {
218         // FIXME: option to use local time
219         // use toUTC() function compatible with older versions of Qt.
220         QString file_name = QDateTime::currentDateTime().toUTC().toString("gqrx_yyyyMMdd_hhmmss");
221         last_audio = QString("%1/%2_%3.wav").arg(rec_dir).arg(file_name).arg(rx_freq);
222         QFileInfo info(last_audio);
223 
224         // emit signal and start timer
225         emit audioRecStarted(last_audio);
226 
227         ui->audioRecLabel->setText(info.fileName());
228         ui->audioRecButton->setToolTip(tr("Stop audio recorder"));
229         ui->audioPlayButton->setEnabled(false); /* prevent playback while recording */
230     }
231     else {
232         ui->audioRecLabel->setText("<i>DSP</i>");
233         ui->audioRecButton->setToolTip(tr("Start audio recorder"));
234         emit audioRecStopped();
235 
236         ui->audioPlayButton->setEnabled(true);
237     }
238 }
239 
240 /*! \brief Playback button clicked.
241  *  \param checked Whether playback is ON or OFF.
242  *
243  * We use the clicked signal instead of the toggled which allows us to change the
244  * state programmatically using toggle() without triggering the signal.
245  */
on_audioPlayButton_clicked(bool checked)246 void DockAudio::on_audioPlayButton_clicked(bool checked)
247 {
248     if (checked) {
249         QFileInfo info(last_audio);
250 
251         if(info.exists()) {
252             // emit signal and start timer
253             emit audioPlayStarted(last_audio);
254 
255             ui->audioRecLabel->setText(info.fileName());
256             ui->audioPlayButton->setToolTip(tr("Stop audio playback"));
257             ui->audioRecButton->setEnabled(false); // prevent recording while we play
258         }
259         else {
260             ui->audioPlayButton->setChecked(false);
261             ui->audioPlayButton->setEnabled(false);
262         }
263     }
264     else {
265         ui->audioRecLabel->setText("<i>DSP</i>");
266         ui->audioPlayButton->setToolTip(tr("Start playback of last recorded audio file"));
267         emit audioPlayStopped();
268 
269         ui->audioRecButton->setEnabled(true);
270     }
271 }
272 
273 /*! \brief Configure button clicked. */
on_audioConfButton_clicked()274 void DockAudio::on_audioConfButton_clicked()
275 {
276     audioOptions->show();
277 }
278 
279 /*! \brief Mute audio. */
on_audioMuteButton_clicked(bool checked)280 void DockAudio::on_audioMuteButton_clicked(bool checked)
281 {
282     if (checked)
283     {
284         emit audioGainChanged(-INFINITY);
285     }
286     else
287     {
288         int value = ui->audioGainSlider->value();
289         float gain = float(value) / 10.0;
290         emit audioGainChanged(gain);
291     }
292 }
293 
294 /*! \brief Set status of audio record button. */
setAudioRecButtonState(bool checked)295 void DockAudio::setAudioRecButtonState(bool checked)
296 {
297     if (checked == ui->audioRecButton->isChecked()) {
298         /* nothing to do */
299         return;
300     }
301 
302     // toggle the button and set the state of the other buttons accordingly
303     ui->audioRecButton->toggle();
304     bool isChecked = ui->audioRecButton->isChecked();
305 
306     ui->audioRecButton->setToolTip(isChecked ? tr("Stop audio recorder") : tr("Start audio recorder"));
307     ui->audioPlayButton->setEnabled(!isChecked);
308     //ui->audioRecConfButton->setEnabled(!isChecked);
309 }
310 
311 /*! \brief Set status of audio record button. */
setAudioPlayButtonState(bool checked)312 void DockAudio::setAudioPlayButtonState(bool checked)
313 {
314     if (checked == ui->audioPlayButton->isChecked()) {
315         // nothing to do
316         return;
317     }
318 
319     // toggle the button and set the state of the other buttons accordingly
320     ui->audioPlayButton->toggle();
321     bool isChecked = ui->audioPlayButton->isChecked();
322 
323     ui->audioPlayButton->setToolTip(isChecked ? tr("Stop audio playback") : tr("Start playback of last recorded audio file"));
324     ui->audioRecButton->setEnabled(!isChecked);
325     //ui->audioRecConfButton->setEnabled(!isChecked);
326 }
327 
saveSettings(QSettings * settings)328 void DockAudio::saveSettings(QSettings *settings)
329 {
330     int     ival, fft_min, fft_max;
331 
332     if (!settings)
333         return;
334 
335     settings->beginGroup("audio");
336 
337     settings->setValue("gain", audioGain());
338 
339     ival = audioOptions->getFftSplit();
340     if (ival != DEFAULT_FFT_SPLIT)
341         settings->setValue("fft_split", ival);
342     else
343         settings->remove("fft_split");
344 
345     audioOptions->getPandapterRange(&fft_min, &fft_max);
346     if (fft_min != -80)
347         settings->setValue("pandapter_min_db", fft_min);
348     else
349         settings->remove("pandapter_min_db");
350     if (fft_max != 0)
351         settings->setValue("pandapter_max_db", fft_max);
352     else
353         settings->remove("pandapter_max_db");
354 
355     audioOptions->getWaterfallRange(&fft_min, &fft_max);
356     if (fft_min != -80)
357         settings->setValue("waterfall_min_db", fft_min);
358     else
359         settings->remove("waterfall_min_db");
360     if (fft_max != 0)
361         settings->setValue("waterfall_max_db", fft_max);
362     else
363         settings->remove("waterfall_max_db");
364 
365     if (audioOptions->getLockButtonState())
366         settings->setValue("db_ranges_locked", true);
367     else
368         settings->remove("db_ranges_locked");
369 
370     if (rec_dir != QDir::homePath())
371         settings->setValue("rec_dir", rec_dir);
372     else
373         settings->remove("rec_dir");
374 
375     if (udp_host.isEmpty())
376         settings->remove("udp_host");
377     else
378         settings->setValue("udp_host", udp_host);
379 
380     if (udp_port != 7355)
381         settings->setValue("udp_port", udp_port);
382     else
383         settings->remove("udp_port");
384 
385     if (udp_stereo != false)
386         settings->setValue("udp_stereo", udp_stereo);
387     else
388         settings->remove("udp_stereo");
389 
390     settings->endGroup();
391 }
392 
readSettings(QSettings * settings)393 void DockAudio::readSettings(QSettings *settings)
394 {
395     int     bool_val, ival, fft_min, fft_max;
396     bool    conv_ok = false;
397 
398     if (!settings)
399         return;
400 
401     settings->beginGroup("audio");
402 
403     ival = settings->value("gain", QVariant(-60)).toInt(&conv_ok);
404     if (conv_ok)
405         setAudioGain(ival);
406 
407     ival = settings->value("fft_split", DEFAULT_FFT_SPLIT).toInt(&conv_ok);
408     if (conv_ok)
409         audioOptions->setFftSplit(ival);
410 
411     fft_min = settings->value("pandapter_min_db", QVariant(-80)).toInt(&conv_ok);
412     if (!conv_ok)
413         fft_min = -80;
414     fft_max = settings->value("pandapter_max_db", QVariant(0)).toInt(&conv_ok);
415     if (!conv_ok)
416         fft_max = 0;
417     audioOptions->setPandapterRange(fft_min, fft_max);
418 
419     fft_min = settings->value("waterfall_min_db", QVariant(-80)).toInt(&conv_ok);
420     if (!conv_ok)
421         fft_min = -80;
422     fft_max = settings->value("waterfall_max_db", QVariant(0)).toInt(&conv_ok);
423     if (!conv_ok)
424         fft_max = 0;
425     audioOptions->setWaterfallRange(fft_min, fft_max);
426 
427     bool_val = settings->value("db_ranges_locked", false).toBool();
428     audioOptions->setLockButtonState(bool_val);
429 
430     // Location of audio recordings
431     rec_dir = settings->value("rec_dir", QDir::homePath()).toString();
432     audioOptions->setRecDir(rec_dir);
433 
434     // Audio streaming host, port and stereo setting
435     udp_host = settings->value("udp_host", "localhost").toString();
436     udp_port = settings->value("udp_port", 7355).toInt(&conv_ok);
437     if (!conv_ok)
438         udp_port = 7355;
439     udp_stereo = settings->value("udp_stereo", false).toBool();
440 
441     audioOptions->setUdpHost(udp_host);
442     audioOptions->setUdpPort(udp_port);
443     audioOptions->setUdpStereo(udp_stereo);
444 
445     settings->endGroup();
446 }
447 
setNewPandapterRange(int min,int max)448 void DockAudio::setNewPandapterRange(int min, int max)
449 {
450     ui->audioSpectrum->setPandapterRange(min, max);
451 }
452 
setNewWaterfallRange(int min,int max)453 void DockAudio::setNewWaterfallRange(int min, int max)
454 {
455     ui->audioSpectrum->setWaterfallRange(min, max);
456 }
457 
458 /*! \brief Slot called when a new valid recording directory has been selected
459  *         in the audio conf dialog.
460  */
setNewRecDir(const QString & dir)461 void DockAudio::setNewRecDir(const QString &dir)
462 {
463     rec_dir = dir;
464 }
465 
466 /*! \brief Slot called when a new network host has been entered. */
setNewUdpHost(const QString & host)467 void DockAudio::setNewUdpHost(const QString &host)
468 {
469     if (host.isEmpty())
470         udp_host = "localhost";
471     else
472         udp_host = host;
473 }
474 
475 /*! \brief Slot called when a new network port has been entered. */
setNewUdpPort(int port)476 void DockAudio::setNewUdpPort(int port)
477 {
478     udp_port = port;
479 }
480 
481 /*! \brief Slot called when the mono/stereo streaming setting changes. */
setNewUdpStereo(bool enabled)482 void DockAudio::setNewUdpStereo(bool enabled)
483 {
484     udp_stereo = enabled;
485 }
486 
recordToggleShortcut()487 void DockAudio::recordToggleShortcut() {
488     ui->audioRecButton->click();
489 }
490 
muteToggleShortcut()491 void DockAudio::muteToggleShortcut() {
492     ui->audioMuteButton->click();
493 }
494 
increaseAudioGainShortcut()495 void DockAudio::increaseAudioGainShortcut() {
496 	ui->audioGainSlider->triggerAction(QSlider::SliderPageStepAdd);
497 }
498 
decreaseAudioGainShortcut()499 void DockAudio::decreaseAudioGainShortcut() {
500 	ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub);
501 }
502