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