SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
nfmdemod.cpp
Go to the documentation of this file.
1 // Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
3 // written by Christian Daniel //
4 // //
5 // This program is free software; you can redistribute it and/or modify //
6 // it under the terms of the GNU General Public License as published by //
7 // the Free Software Foundation as version 3 of the License, or //
8 // (at your option) any later version. //
9 // //
10 // This program is distributed in the hope that it will be useful, //
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 // GNU General Public License V3 for more details. //
14 // //
15 // You should have received a copy of the GNU General Public License //
16 // along with this program. If not, see <http://www.gnu.org/licenses/>. //
18 
19 #include <stdio.h>
20 #include <complex.h>
21 
22 #include <QTime>
23 #include <QDebug>
24 #include <QNetworkAccessManager>
25 #include <QNetworkReply>
26 #include <QBuffer>
27 
28 #include "SWGChannelSettings.h"
29 #include "SWGNFMDemodSettings.h"
30 #include "SWGChannelReport.h"
31 #include "SWGNFMDemodReport.h"
32 
33 #include "dsp/downchannelizer.h"
34 #include "util/stepfunctions.h"
35 #include "util/db.h"
36 #include "audio/audiooutput.h"
37 #include "dsp/dspengine.h"
39 #include "dsp/dspcommands.h"
40 #include "device/deviceapi.h"
41 
42 #include "nfmdemod.h"
43 
47 
48 const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod";
49 const QString NFMDemod::m_channelId = "NFMDemod";
50 
51 static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0};
52 static const double afSqTones_lowrate[2] = {1000.0, 3500.0};
53 const int NFMDemod::m_udpBlockSize = 512;
54 
56  ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
57  m_deviceAPI(devieAPI),
58  m_inputSampleRate(48000),
59  m_inputFrequencyOffset(0),
60  m_running(false),
61  m_ctcssIndex(0),
62  m_sampleCount(0),
63  m_squelchCount(0),
64  m_squelchGate(4800),
65  m_squelchLevel(-990),
66  m_squelchOpen(false),
67  m_afSquelchOpen(false),
68  m_magsq(0.0f),
69  m_magsqSum(0.0f),
70  m_magsqPeak(0.0f),
71  m_magsqCount(0),
72  m_afSquelch(),
73  m_squelchDelayLine(24000),
74  m_audioFifo(48000),
75  m_settingsMutex(QMutex::Recursive)
76 {
77  qDebug("NFMDemod::NFMDemod");
78  setObjectName(m_channelId);
79 
80  m_audioBuffer.resize(1<<14);
82 
83  m_agcLevel = 1.0;
84 
89 
90  m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution
91  m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
92 
94 
97 
98  m_channelizer = new DownChannelizer(this);
102 
103  m_networkManager = new QNetworkAccessManager();
104  connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
105 }
106 
108 {
109  disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
110  delete m_networkManager;
114  delete m_threadedChannelizer;
115  delete m_channelizer;
116 }
117 
118 float arctan2(Real y, Real x)
119 {
120  Real coeff_1 = M_PI / 4;
121  Real coeff_2 = 3 * coeff_1;
122  Real abs_y = fabs(y) + 1e-10; // kludge to prevent 0/0 condition
123  Real angle;
124  if( x>= 0) {
125  Real r = (x - abs_y) / (x + abs_y);
126  angle = coeff_1 - coeff_1 * r;
127  } else {
128  Real r = (x + abs_y) / (abs_y - x);
129  angle = coeff_2 - coeff_1 * r;
130  }
131  if(y < 0) {
132  return(-angle);
133  } else {
134  return(angle);
135  }
136 }
137 
139 {
140  Real dist = b - a;
141 
142  while(dist <= M_PI)
143  dist += 2 * M_PI;
144  while(dist >= M_PI)
145  dist -= 2 * M_PI;
146 
147  return dist;
148 }
149 
150 void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
151 {
152  (void) firstOfBurst;
153  Complex ci;
154 
155  if (!m_running) {
156  return;
157  }
158 
159  m_settingsMutex.lock();
160 
161  for (SampleVector::const_iterator it = begin; it != end; ++it)
162  {
163  Complex c(it->real(), it->imag());
164  c *= m_nco.nextIQ();
165 
166  if (m_interpolatorDistance < 1.0f) // interpolate
167  {
169  {
170  processOneSample(ci);
172  }
173  }
174  else // decimate
175  {
177  {
178  processOneSample(ci);
180  }
181  }
182  }
183 
184  m_settingsMutex.unlock();
185 }
186 
188 {
189  qint16 sample;
190 
191  double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
192  Real deviation;
193 
194  Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
195 
196  Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
197  m_movingAverage(magsq);
198  m_magsqSum += magsq;
199 
200  if (magsq > m_magsqPeak)
201  {
202  m_magsqPeak = magsq;
203  }
204 
205  m_magsqCount++;
206  m_sampleCount++;
207 
208  // AF processing
209 
211  {
213  {
214  m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0;
215 
216  if (!m_afSquelchOpen) {
217  m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period
218  }
219  }
220 
221  if (m_afSquelchOpen)
222  {
223  m_squelchDelayLine.write(demod * m_discriCompensation);
224 
225  if (m_squelchCount < 2*m_squelchGate) {
226  m_squelchCount++;
227  }
228  }
229  else
230  {
232 
233  if (m_squelchCount > 0) {
234  m_squelchCount--;
235  }
236  }
237  }
238  else
239  {
241  {
243 
244  if (m_squelchCount > 0) {
245  m_squelchCount--;
246  }
247  }
248  else
249  {
251 
252  if (m_squelchCount < 2*m_squelchGate) {
253  m_squelchCount++;
254  }
255  }
256  }
257 
259 
261  {
262  sample = 0;
263  }
264  else
265  {
266  if (m_squelchOpen)
267  {
268  if (m_settings.m_ctcssOn)
269  {
270  Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation);
271 
272  if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
273  {
274  if (m_ctcssDetector.analyze(&ctcss_sample))
275  {
276  int maxToneIndex;
277 
278  if (m_ctcssDetector.getDetectedTone(maxToneIndex))
279  {
280  if (maxToneIndex+1 != m_ctcssIndex)
281  {
282  if (getMessageQueueToGUI()) {
284  getMessageQueueToGUI()->push(msg);
285  }
286  m_ctcssIndex = maxToneIndex+1;
287  }
288  }
289  else
290  {
291  if (m_ctcssIndex != 0)
292  {
293  if (getMessageQueueToGUI()) {
295  getMessageQueueToGUI()->push(msg);
296  }
297  m_ctcssIndex = 0;
298  }
299  }
300  }
301  }
302  }
303 
305  {
306  sample = 0;
307  }
308  else
309  {
310  if (m_settings.m_highPass) {
312  } else {
314  }
315  }
316  }
317  else
318  {
319  if (m_ctcssIndex != 0)
320  {
321  if (getMessageQueueToGUI()) {
323  getMessageQueueToGUI()->push(msg);
324  }
325 
326  m_ctcssIndex = 0;
327  }
328 
329  sample = 0;
330  }
331  }
332 
333 
334  m_audioBuffer[m_audioBufferFill].l = sample;
335  m_audioBuffer[m_audioBufferFill].r = sample;
337 
338  if (m_audioBufferFill >= m_audioBuffer.size())
339  {
340  uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
341 
342  if (res != m_audioBufferFill)
343  {
344  qDebug("NFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill);
345  }
346 
347  m_audioBufferFill = 0;
348  }
349 
350  if (m_audioBufferFill > 0)
351  {
352  uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
353 
354  if (res != m_audioBufferFill)
355  {
356  qDebug("NFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
357  }
358 
359  m_audioBufferFill = 0;
360  }
361 }
362 
364 {
365  qDebug() << "NFMDemod::start";
366  m_squelchCount = 0;
367  m_audioFifo.clear();
370  m_running = true;
371 }
372 
374 {
375  qDebug() << "NFMDemod::stop";
376  m_running = false;
377 }
378 
380 {
382  {
384  qDebug() << "NFMDemod::handleMessage: DownChannelizer::MsgChannelizerNotification";
385 
387 
388  return true;
389  }
390  else if (MsgConfigureChannelizer::match(cmd))
391  {
393 
394  qDebug() << "NFMDemod::handleMessage: MsgConfigureChannelizer:"
395  << " sampleRate: " << cfg.getSampleRate()
396  << " centerFrequency: " << cfg.getCenterFrequency();
397 
399  cfg.getSampleRate(),
400  cfg.getCenterFrequency());
401 
402  return true;
403  }
404  else if (MsgConfigureNFMDemod::match(cmd))
405  {
407  qDebug() << "NFMDemod::handleMessage: MsgConfigureNFMDemod";
408 
409  applySettings(cfg.getSettings(), cfg.getForce());
410 
411  return true;
412  }
414  {
416  const QThread *thread = cfg.getThread();
417  qDebug("NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread);
418  return true;
419  }
420  else if (DSPConfigureAudio::match(cmd))
421  {
422  DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
423  uint32_t sampleRate = cfg.getSampleRate();
424 
425  qDebug() << "NFMDemod::handleMessage: DSPConfigureAudio:"
426  << " sampleRate: " << sampleRate;
427 
428  if (sampleRate != m_audioSampleRate) {
429  applyAudioSampleRate(sampleRate);
430  }
431 
432  return true;
433  }
434  else if (DSPSignalNotification::match(cmd))
435  {
436  return true;
437  }
438  else
439  {
440  return false;
441  }
442 }
443 
444 void NFMDemod::applyAudioSampleRate(int sampleRate)
445 {
446  qDebug("NFMDemod::applyAudioSampleRate: %d", sampleRate);
447 
449  sampleRate, m_settings.m_inputFrequencyOffset);
450  m_inputMessageQueue.push(channelConfigMsg);
451 
452  m_settingsMutex.lock();
453 
457  m_ctcssLowpass.create(301, sampleRate, 250.0);
458  m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
459  m_lowpass.create(301, sampleRate, m_settings.m_afBandwidth);
460  m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
461  m_squelchCount = 0; // reset squelch open counter
462  m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
463 
464  if (sampleRate < 16000) {
465  m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
466 
467  } else {
468  m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
469  }
470 
471  m_discriCompensation = (sampleRate/48000.0f);
473 
474  m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation));
475  m_audioFifo.setSize(sampleRate);
476  m_squelchDelayLine.resize(sampleRate/2);
477 
478  m_settingsMutex.unlock();
479 
480  m_audioSampleRate = sampleRate;
481 }
482 
483 void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
484 {
485  qDebug() << "NFMDemod::applyChannelSettings:"
486  << " inputSampleRate: " << inputSampleRate
487  << " inputFrequencyOffset: " << inputFrequencyOffset;
488 
489  if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
490  (inputSampleRate != m_inputSampleRate) || force)
491  {
492  m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
493  }
494 
495  if ((inputSampleRate != m_inputSampleRate) || force)
496  {
497  m_settingsMutex.lock();
498  m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
500  m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
501  m_settingsMutex.unlock();
502  }
503 
504  m_inputSampleRate = inputSampleRate;
505  m_inputFrequencyOffset = inputFrequencyOffset;
506 }
507 
508 void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
509 {
510  qDebug() << "NFMDemod::applySettings:"
511  << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
512  << " m_rfBandwidth: " << settings.m_rfBandwidth
513  << " m_afBandwidth: " << settings.m_afBandwidth
514  << " m_fmDeviation: " << settings.m_fmDeviation
515  << " m_volume: " << settings.m_volume
516  << " m_squelchGate: " << settings.m_squelchGate
517  << " m_deltaSquelch: " << settings.m_deltaSquelch
518  << " m_squelch: " << settings.m_squelch
519  << " m_ctcssIndex: " << settings.m_ctcssIndex
520  << " m_ctcssOn: " << settings.m_ctcssOn
521  << " m_highPass: " << settings.m_highPass
522  << " m_audioMute: " << settings.m_audioMute
523  << " m_audioDeviceName: " << settings.m_audioDeviceName
524  << " m_useReverseAPI: " << settings.m_useReverseAPI
525  << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
526  << " m_reverseAPIPort: " << settings.m_reverseAPIPort
527  << " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
528  << " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
529  << " force: " << force;
530 
531  QList<QString> reverseAPIKeys;
532 
533  if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
534  reverseAPIKeys.append("inputFrequencyOffset");
535  }
536  if ((settings.m_volume != m_settings.m_volume) || force) {
537  reverseAPIKeys.append("volume");
538  }
539  if ((settings.m_ctcssOn != m_settings.m_ctcssOn) || force) {
540  reverseAPIKeys.append("ctcssOn");
541  }
542  if ((settings.m_audioMute != m_settings.m_audioMute) || force) {
543  reverseAPIKeys.append("audioMute");
544  }
545  if ((settings.m_rgbColor != m_settings.m_rgbColor) || force) {
546  reverseAPIKeys.append("rgbColor");
547  }
548  if ((settings.m_title != m_settings.m_title) || force) {
549  reverseAPIKeys.append("title");
550  }
551 
552  if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
553  {
554  reverseAPIKeys.append("rfBandwidth");
555  m_settingsMutex.lock();
559  m_settingsMutex.unlock();
560  }
561 
562  if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
563  {
564  reverseAPIKeys.append("fmDeviation");
565  m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
566  }
567 
568  if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
569  {
570  reverseAPIKeys.append("afBandwidth");
571  m_settingsMutex.lock();
572  m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth);
574  m_settingsMutex.unlock();
575  }
576 
577  if ((settings.m_squelchGate != m_settings.m_squelchGate) || force)
578  {
579  reverseAPIKeys.append("squelchGate");
580  m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
581  m_squelchCount = 0; // reset squelch open counter
582  }
583 
584  if ((settings.m_squelch != m_settings.m_squelch) || force) {
585  reverseAPIKeys.append("squelch");
586  }
587  if ((settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) {
588  reverseAPIKeys.append("deltaSquelch");
589  }
590 
591  if ((settings.m_squelch != m_settings.m_squelch) ||
592  (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force)
593  {
594  if (settings.m_deltaSquelch)
595  { // input is a value in negative centis
596  m_squelchLevel = (- settings.m_squelch) / 100.0;
598  m_afSquelch.reset();
599  }
600  else
601  { // input is a value in deci-Bels
602  m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
604  }
605 
606  m_squelchCount = 0; // reset squelch open counter
607  }
608 
609  if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force)
610  {
611  reverseAPIKeys.append("ctcssIndex");
613  }
614 
615  if ((settings.m_highPass != m_settings.m_highPass) || force) {
616  reverseAPIKeys.append("highPass");
617  }
618 
619  if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
620  {
621  reverseAPIKeys.append("audioDeviceName");
623  int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);
624  //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
625  audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
626  uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
627 
628  if (m_audioSampleRate != audioSampleRate) {
629  applyAudioSampleRate(audioSampleRate);
630  }
631  }
632 
633  if (settings.m_useReverseAPI)
634  {
635  bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
640  webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
641  }
642 
643  m_settings = settings;
644 }
645 
646 QByteArray NFMDemod::serialize() const
647 {
648  return m_settings.serialize();
649 }
650 
651 bool NFMDemod::deserialize(const QByteArray& data)
652 {
653  bool success = true;
654 
655  if (!m_settings.deserialize(data))
656  {
658  success = false;
659  }
660 
663  m_inputMessageQueue.push(channelConfigMsg);
664 
667 
668  return success;
669 }
670 
673  QString& errorMessage)
674 {
675  (void) errorMessage;
677  response.getNfmDemodSettings()->init();
679  return 200;
680 }
681 
683  bool force,
684  const QStringList& channelSettingsKeys,
686  QString& errorMessage)
687 {
688  (void) errorMessage;
689  NFMDemodSettings settings = m_settings;
690  bool frequencyOffsetChanged = false;
691 
692  if (channelSettingsKeys.contains("afBandwidth")) {
693  settings.m_afBandwidth = response.getNfmDemodSettings()->getAfBandwidth();
694  }
695  if (channelSettingsKeys.contains("audioMute")) {
696  settings.m_audioMute = response.getNfmDemodSettings()->getAudioMute() != 0;
697  }
698  if (channelSettingsKeys.contains("highPass")) {
699  settings.m_highPass = response.getNfmDemodSettings()->getHighPass() != 0;
700  }
701  if (channelSettingsKeys.contains("ctcssIndex")) {
702  settings.m_ctcssIndex = response.getNfmDemodSettings()->getCtcssIndex();
703  }
704  if (channelSettingsKeys.contains("ctcssOn")) {
705  settings.m_ctcssOn = response.getNfmDemodSettings()->getCtcssOn() != 0;
706  }
707  if (channelSettingsKeys.contains("deltaSquelch")) {
708  settings.m_deltaSquelch = response.getNfmDemodSettings()->getDeltaSquelch() != 0;
709  }
710  if (channelSettingsKeys.contains("fmDeviation")) {
711  settings.m_fmDeviation = response.getNfmDemodSettings()->getFmDeviation();
712  }
713  if (channelSettingsKeys.contains("inputFrequencyOffset"))
714  {
716  frequencyOffsetChanged = true;
717  }
718  if (channelSettingsKeys.contains("rfBandwidth")) {
719  settings.m_rfBandwidth = response.getNfmDemodSettings()->getRfBandwidth();
720  }
721  if (channelSettingsKeys.contains("rgbColor")) {
722  settings.m_rgbColor = response.getNfmDemodSettings()->getRgbColor();
723  }
724  if (channelSettingsKeys.contains("squelch")) {
725  settings.m_squelch = response.getNfmDemodSettings()->getSquelch();
726  }
727  if (channelSettingsKeys.contains("squelchGate")) {
728  settings.m_squelchGate = response.getNfmDemodSettings()->getSquelchGate();
729  }
730  if (channelSettingsKeys.contains("title")) {
731  settings.m_title = *response.getNfmDemodSettings()->getTitle();
732  }
733  if (channelSettingsKeys.contains("volume")) {
734  settings.m_volume = response.getNfmDemodSettings()->getVolume();
735  }
736  if (channelSettingsKeys.contains("audioDeviceName")) {
737  settings.m_audioDeviceName = *response.getNfmDemodSettings()->getAudioDeviceName();
738  }
739  if (channelSettingsKeys.contains("useReverseAPI")) {
740  settings.m_useReverseAPI = response.getNfmDemodSettings()->getUseReverseApi() != 0;
741  }
742  if (channelSettingsKeys.contains("reverseAPIAddress")) {
744  }
745  if (channelSettingsKeys.contains("reverseAPIPort")) {
746  settings.m_reverseAPIPort = response.getNfmDemodSettings()->getReverseApiPort();
747  }
748  if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
750  }
751  if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
753  }
754 
755  if (frequencyOffsetChanged)
756  {
759  m_inputMessageQueue.push(channelConfigMsg);
760  }
761 
762  MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(settings, force);
764 
765  if (m_guiMessageQueue) // forward to GUI if any
766  {
767  MsgConfigureNFMDemod *msgToGUI = MsgConfigureNFMDemod::create(settings, force);
768  m_guiMessageQueue->push(msgToGUI);
769  }
770 
771  webapiFormatChannelSettings(response, settings);
772 
773  return 200;
774 }
775 
778  QString& errorMessage)
779 {
780  (void) errorMessage;
782  response.getNfmDemodReport()->init();
783  webapiFormatChannelReport(response);
784  return 200;
785 }
786 
788 {
789  response.getNfmDemodSettings()->setAfBandwidth(settings.m_afBandwidth);
790  response.getNfmDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
791  response.getNfmDemodSettings()->setHighPass(settings.m_highPass ? 1 : 0);
792  response.getNfmDemodSettings()->setCtcssIndex(settings.m_ctcssIndex);
793  response.getNfmDemodSettings()->setCtcssOn(settings.m_ctcssOn ? 1 : 0);
794  response.getNfmDemodSettings()->setDeltaSquelch(settings.m_deltaSquelch ? 1 : 0);
795  response.getNfmDemodSettings()->setFmDeviation(settings.m_fmDeviation);
797  response.getNfmDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
798  response.getNfmDemodSettings()->setRgbColor(settings.m_rgbColor);
799  response.getNfmDemodSettings()->setSquelch(settings.m_squelch);
800  response.getNfmDemodSettings()->setSquelchGate(settings.m_squelchGate);
801  response.getNfmDemodSettings()->setVolume(settings.m_volume);
802 
803  if (response.getNfmDemodSettings()->getTitle()) {
804  *response.getNfmDemodSettings()->getTitle() = settings.m_title;
805  } else {
806  response.getNfmDemodSettings()->setTitle(new QString(settings.m_title));
807  }
808 
809  if (response.getNfmDemodSettings()->getAudioDeviceName()) {
810  *response.getNfmDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
811  } else {
812  response.getNfmDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
813  }
814 
815  response.getNfmDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
816 
817  if (response.getNfmDemodSettings()->getReverseApiAddress()) {
819  } else {
820  response.getNfmDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
821  }
822 
826 }
827 
829 {
830  double magsqAvg, magsqPeak;
831  int nbMagsqSamples;
832  getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
833 
834  response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
836  response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0);
839 }
840 
841 void NFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force)
842 {
844  swgChannelSettings->setDirection(0); // single sink (Rx)
845  swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
846  swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
847  swgChannelSettings->setChannelType(new QString("NFMDemod"));
848  swgChannelSettings->setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings());
849  SWGSDRangel::SWGNFMDemodSettings *swgNFMDemodSettings = swgChannelSettings->getNfmDemodSettings();
850 
851  // transfer data that has been modified. When force is on transfer all data except reverse API data
852 
853  if (channelSettingsKeys.contains("afBandwidth") || force) {
854  swgNFMDemodSettings->setAfBandwidth(settings.m_afBandwidth);
855  }
856  if (channelSettingsKeys.contains("audioMute") || force) {
857  swgNFMDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0);
858  }
859  if (channelSettingsKeys.contains("highPass") || force) {
860  swgNFMDemodSettings->setAudioMute(settings.m_highPass ? 1 : 0);
861  }
862  if (channelSettingsKeys.contains("ctcssIndex") || force) {
863  swgNFMDemodSettings->setCtcssIndex(settings.m_ctcssIndex);
864  }
865  if (channelSettingsKeys.contains("ctcssOn") || force) {
866  swgNFMDemodSettings->setCtcssOn(settings.m_ctcssOn ? 1 : 0);
867  }
868  if (channelSettingsKeys.contains("deltaSquelch") || force) {
869  swgNFMDemodSettings->setDeltaSquelch(settings.m_deltaSquelch ? 1 : 0);
870  }
871  if (channelSettingsKeys.contains("fmDeviation") || force) {
872  swgNFMDemodSettings->setFmDeviation(settings.m_fmDeviation);
873  }
874  if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
875  swgNFMDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
876  }
877  if (channelSettingsKeys.contains("rfBandwidth") || force) {
878  swgNFMDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
879  }
880  if (channelSettingsKeys.contains("rgbColor") || force) {
881  swgNFMDemodSettings->setRgbColor(settings.m_rgbColor);
882  }
883  if (channelSettingsKeys.contains("squelch") || force) {
884  swgNFMDemodSettings->setSquelch(settings.m_squelch);
885  }
886  if (channelSettingsKeys.contains("squelchGate") || force) {
887  swgNFMDemodSettings->setSquelchGate(settings.m_squelchGate);
888  }
889  if (channelSettingsKeys.contains("title") || force) {
890  swgNFMDemodSettings->setTitle(new QString(settings.m_title));
891  }
892  if (channelSettingsKeys.contains("volume") || force) {
893  swgNFMDemodSettings->setVolume(settings.m_volume);
894  }
895  if (channelSettingsKeys.contains("audioDeviceName") || force) {
896  swgNFMDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
897  }
898 
899  QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
900  .arg(settings.m_reverseAPIAddress)
901  .arg(settings.m_reverseAPIPort)
902  .arg(settings.m_reverseAPIDeviceIndex)
903  .arg(settings.m_reverseAPIChannelIndex);
904  m_networkRequest.setUrl(QUrl(channelSettingsURL));
905  m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
906 
907  QBuffer *buffer=new QBuffer();
908  buffer->open((QBuffer::ReadWrite));
909  buffer->write(swgChannelSettings->asJson().toUtf8());
910  buffer->seek(0);
911 
912  // Always use PATCH to avoid passing reverse API settings
913  m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
914 
915  delete swgChannelSettings;
916 }
917 
918 void NFMDemod::networkManagerFinished(QNetworkReply *reply)
919 {
920  QNetworkReply::NetworkError replyError = reply->error();
921 
922  if (replyError)
923  {
924  qWarning() << "NFMDemod::networkManagerFinished:"
925  << " error(" << (int) replyError
926  << "): " << replyError
927  << ": " << reply->errorString();
928  return;
929  }
930 
931  QString answer = reply->readAll();
932  answer.chop(1); // remove last \n
933  qDebug("NFMDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
934 }
Real m_interpolatorDistanceRemain
Definition: nfmdemod.h:222
void resize(int size)
void setOriginatorChannelIndex(qint32 originator_channel_index)
void addAudioSink(AudioFifo *audioFifo, MessageQueue *sampleSinkMessageQueue, int outputDeviceIndex=-1)
Add the audio sink.
uint m_audioBufferFill
Definition: nfmdemod.h:248
virtual void stop()
Definition: nfmdemod.cpp:373
uint16_t m_reverseAPIPort
QNetworkRequest m_networkRequest
Definition: nfmdemod.h:256
int getOutputSampleRate(int outputDeviceIndex=-1)
bool deserialize(const QByteArray &data)
void getMagSqLevels(double &avg, double &peak, int &nbSamples)
Definition: nfmdemod.h:171
int m_sampleCount
Definition: nfmdemod.h:229
Complex nextIQ()
Return next complex sample.
Definition: nco.cpp:61
void setCoefficients(int zN, int SampleRate)
bool decimate(Real *distance, const Complex &next, Complex *result)
Definition: interpolator.h:38
void configure(MessageQueue *messageQueue, int sampleRate, int centerFrequency)
void setUseReverseApi(qint32 use_reverse_api)
float m_discriCompensation
compensation factor that depends on audio rate (1 for 48 kS/s)
Definition: nfmdemod.h:216
PhaseDiscriminators m_phaseDiscri
Definition: nfmdemod.h:253
void push(Message *message, bool emitSignal=true)
Push message onto queue.
void setAfBandwidth(float af_bandwidth)
QMutex m_settingsMutex
Definition: nfmdemod.h:251
SWGNFMDemodSettings * getNfmDemodSettings()
const Real * getToneSet() const
Definition: ctcssdetector.h:45
void removeChannelSinkAPI(ChannelAPI *channelAPI, int streamIndex=0)
Definition: deviceapi.cpp:163
void setChannelSampleRate(qint32 channel_sample_rate)
void create(int nTaps, double sampleRate, double lowCutoff, double highCutoff)
Definition: bandpass.h:15
static double dbPower(double magsq, double floor=1e-12)
Definition: db.cpp:22
void addChannelSinkAPI(ChannelAPI *channelAPI, int streamIndex=0)
Definition: deviceapi.cpp:156
void create(int phaseSteps, double sampleRate, double cutoff, double nbTapsPerPhase=4.5)
void processOneSample(Complex &ci)
Definition: nfmdemod.cpp:187
MessageQueue * getInputMessageQueue()
Get the queue for asynchronous inbound communication.
void setFMScaling(Real fmScaling)
Definition: phasediscri.h:42
bool m_running
Definition: nfmdemod.h:217
bool interpolate(Real *distance, const Complex &next, Complex *result)
Definition: interpolator.h:53
double m_magsqSum
Definition: nfmdemod.h:237
void clear()
Definition: audiofifo.cpp:156
void create(int nTaps, double sampleRate, double cutoff)
Definition: lowpass.h:15
int getDeviceSetIndex() const
Definition: channelapi.h:89
int getOutputDeviceIndex(const QString &deviceName) const
AFSquelch m_afSquelch
Definition: nfmdemod.h:243
MovingAverageUtil< Real, double, 32 > m_movingAverage
Definition: nfmdemod.h:242
static MsgConfigureChannelizer * create(int sampleRate, int centerFrequency)
Definition: nfmdemod.h:83
Real angleDist(Real a, Real b)
Definition: nfmdemod.cpp:138
bool analyze(double sample)
Definition: afsquelch.cpp:116
void removeAudioSink(AudioFifo *audioFifo)
Remove the audio sink.
uint32_t m_audioSampleRate
Definition: nfmdemod.h:215
ThreadedBasebandSampleSink * m_threadedChannelizer
Definition: nfmdemod.h:209
static MsgConfigureNFMDemod * create(const NFMDemodSettings &settings, bool force)
Definition: nfmdemod.h:60
virtual QByteArray serialize() const
Definition: nfmdemod.cpp:646
Type filter(Type sample)
Definition: bandpass.h:77
void setNfmDemodSettings(SWGNFMDemodSettings *nfm_demod_settings)
void zeroBack(int delay)
void setAudioSampleRate(qint32 audio_sample_rate)
SWGNFMDemodReport * getNfmDemodReport()
Real m_squelch
deci-Bels
#define M_PI
Definition: rdsdemod.cpp:27
void setChannelType(QString *channel_type)
void setOriginatorDeviceSetIndex(qint32 originator_device_set_index)
void setSelectedCtcssIndex(int selectedCtcssIndex)
Definition: nfmdemod.h:164
MessageQueue m_inputMessageQueue
Queue for asynchronous inbound communication.
virtual int webapiSettingsPutPatch(bool force, const QStringList &channelSettingsKeys, SWGSDRangel::SWGChannelSettings &response, QString &errorMessage)
Definition: nfmdemod.cpp:682
virtual void feed(const SampleVector::const_iterator &begin, const SampleVector::const_iterator &end, bool po)
Definition: nfmdemod.cpp:150
NCO m_nco
Definition: nfmdemod.h:219
void write(const T &element)
unsigned int uint32_t
Definition: rtptypes_win.h:46
QString m_reverseAPIAddress
bool getDetectedTone(int &maxTone) const
Definition: ctcssdetector.h:51
Interpolator m_interpolator
Definition: nfmdemod.h:220
MessageQueue * m_guiMessageQueue
Input message queue to the GUI.
int m_squelchCount
Definition: nfmdemod.h:230
static const QString m_channelIdURI
Definition: nfmdemod.h:189
float arctan2(Real y, Real x)
Definition: nfmdemod.cpp:118
bool evaluate()
Definition: afsquelch.cpp:188
#define MESSAGE_CLASS_DEFINITION(Name, BaseClass)
Definition: message.h:52
QByteArray serialize() const
int getSampleRate() const
Definition: dspcommands.h:390
void setInputFrequencyOffset(qint64 input_frequency_offset)
static DSPEngine * instance()
Definition: dspengine.cpp:51
void setSquelchGate(qint32 squelch_gate)
uint16_t m_reverseAPIChannelIndex
static const int m_udpBlockSize
Definition: nfmdemod.h:258
AudioFifo m_audioFifo
Definition: nfmdemod.h:249
void setDeltaSquelch(qint32 delta_squelch)
Real m_interpolatorDistance
Definition: nfmdemod.h:221
static bool match(const Message *message)
Definition: message.cpp:45
void setFreq(Real freq, Real sampleRate)
Definition: nco.cpp:49
bool m_squelchOpen
Definition: nfmdemod.h:234
int m_magsqCount
Definition: nfmdemod.h:239
void removeChannelSink(ThreadedBasebandSampleSink *sink, int streamIndex=0)
Remove a channel sink (Rx)
Definition: deviceapi.cpp:127
DeviceAPI * m_deviceAPI
Definition: nfmdemod.h:208
void setReverseApiAddress(QString *reverse_api_address)
bool m_afSquelchOpen
Definition: nfmdemod.h:235
void setRfBandwidth(float rf_bandwidth)
Fixed< IntType, IntBits > sqrt(Fixed< IntType, IntBits > const &x)
Definition: fixed.h:2283
int m_ctcssIndexSelected
Definition: nfmdemod.h:228
Lowpass< Real > m_lowpass
Definition: nfmdemod.h:225
MessageQueue * getMessageQueueToGUI()
virtual QString asJson() override
uint16_t m_reverseAPIDeviceIndex
Real m_squelchLevel
Definition: nfmdemod.h:233
Real m_agcLevel
Definition: nfmdemod.h:244
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport &response)
Definition: nfmdemod.cpp:828
QNetworkAccessManager * m_networkManager
Definition: nfmdemod.h:255
void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings &response, const NFMDemodSettings &settings)
Definition: nfmdemod.cpp:787
virtual void start()
Definition: nfmdemod.cpp:363
double m_magsqPeak
Definition: nfmdemod.h:238
void setAudioDeviceName(QString *audio_device_name)
T & readBack(int delay)
AudioDeviceManager * getAudioDeviceManager()
Definition: dspengine.h:55
void addChannelSink(ThreadedBasebandSampleSink *sink, int streamIndex=0)
Add a channel sink (Rx)
Definition: deviceapi.cpp:118
Lowpass< Real > m_ctcssLowpass
Definition: nfmdemod.h:223
void applyAudioSampleRate(int sampleRate)
Definition: nfmdemod.cpp:444
bool setSize(uint32_t numSamples)
Definition: audiofifo.cpp:59
void applySettings(const NFMDemodSettings &settings, bool force=false)
Definition: nfmdemod.cpp:508
Type filter(Type sample)
Definition: lowpass.h:55
virtual bool handleMessage(const Message &cmd)
Processing of a message. Returns true if message has actually been processed.
Definition: nfmdemod.cpp:379
void networkManagerFinished(QNetworkReply *reply)
Definition: nfmdemod.cpp:918
void setReverseApiDeviceIndex(qint32 reverse_api_device_index)
NFMDemodSettings m_settings
Definition: nfmdemod.h:214
int m_ctcssIndex
Definition: nfmdemod.h:227
void setNfmDemodReport(SWGNFMDemodReport *nfm_demod_report)
int m_inputSampleRate
Definition: nfmdemod.h:212
void setChannelPowerDb(float channel_power_db)
void setThreshold(double _threshold)
Definition: afsquelch.cpp:271
DownChannelizer * m_channelizer
Definition: nfmdemod.h:210
void setCtcssTone(float ctcss_tone)
virtual int webapiReportGet(SWGSDRangel::SWGChannelReport &response, QString &errorMessage)
Definition: nfmdemod.cpp:776
int32_t m_inputFrequencyOffset
void reset()
Definition: afsquelch.cpp:172
AudioVector m_audioBuffer
Definition: nfmdemod.h:247
int getIndexInDeviceSet() const
Definition: channelapi.h:87
void webapiReverseSendSettings(QList< QString > &channelSettingsKeys, const NFMDemodSettings &settings, bool force)
Definition: nfmdemod.cpp:841
virtual int webapiSettingsGet(SWGSDRangel::SWGChannelSettings &response, QString &errorMessage)
Definition: nfmdemod.cpp:671
void setCtcssIndex(qint32 ctcss_index)
std::complex< Real > Complex
Definition: dsptypes.h:43
virtual bool deserialize(const QByteArray &data)
Definition: nfmdemod.cpp:651
NFMDemod(DeviceAPI *deviceAPI)
Definition: nfmdemod.cpp:55
CTCSSDetector m_ctcssDetector
Definition: nfmdemod.h:226
int m_squelchGate
Definition: nfmdemod.h:231
const NFMDemodSettings & getSettings() const
Definition: nfmdemod.h:57
Real phaseDiscriminatorDelta(const Complex &sample, double &magsq, Real &fmDev)
Definition: phasediscri.h:62
#define SDR_RX_SCALED
Definition: dsptypes.h:34
float Real
Definition: dsptypes.h:42
Bandpass< Real > m_bandpass
Definition: nfmdemod.h:224
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force=false)
Definition: nfmdemod.cpp:483
static const QString m_channelId
Definition: nfmdemod.h:190
DoubleBufferFIFO< Real > m_squelchDelayLine
Definition: nfmdemod.h:245
int m_inputFrequencyOffset
Definition: nfmdemod.h:213
uint32_t write(const quint8 *data, uint32_t numSamples)
Definition: audiofifo.cpp:66
void setReverseApiPort(qint32 reverse_api_port)
void setFmDeviation(qint32 fm_deviation)
bool analyze(Real *sample)
void setCoefficients(unsigned int N, unsigned int nbAvg, unsigned int sampleRate, unsigned int samplesAttack, unsigned int samplesDecay, const double *tones)
center frequency of tones tested
Definition: afsquelch.cpp:71
void setReverseApiChannelIndex(qint32 reverse_api_channel_index)
static MsgReportCTCSSFreq * create(Real freq)
Definition: nfmdemod.h:105