2014-05-18 11:52:39 -04:00
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// //
// This program 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 as version 3 of the License, or //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
# include <QTime>
2015-08-17 02:29:34 -04:00
# include <QDebug>
2014-05-18 11:52:39 -04:00
# include <stdio.h>
2015-01-26 14:33:28 -05:00
# include <complex.h>
2017-10-08 05:37:15 -04:00
2017-12-10 14:27:08 -05:00
# include "SWGChannelSettings.h"
# include "SWGNFMDemodSettings.h"
2018-03-18 15:17:11 -04:00
# include "SWGChannelReport.h"
# include "SWGNFMDemodReport.h"
2017-12-10 14:27:08 -05:00
2017-12-28 21:03:28 -05:00
# include "dsp/downchannelizer.h"
2017-07-27 05:24:01 -04:00
# include "util/stepfunctions.h"
2018-03-18 15:17:11 -04:00
# include "util/db.h"
2014-05-18 11:52:39 -04:00
# include "audio/audiooutput.h"
2015-08-17 02:29:34 -04:00
# include "dsp/dspengine.h"
2017-10-08 08:06:48 -04:00
# include "dsp/threadedbasebandsamplesink.h"
2018-02-14 14:11:36 -05:00
# include "dsp/dspcommands.h"
2017-12-28 21:03:28 -05:00
# include "device/devicesourceapi.h"
2017-10-08 05:37:15 -04:00
# include "nfmdemod.h"
2014-05-18 11:52:39 -04:00
MESSAGE_CLASS_DEFINITION ( NFMDemod : : MsgConfigureNFMDemod , Message )
2017-10-08 05:37:15 -04:00
MESSAGE_CLASS_DEFINITION ( NFMDemod : : MsgConfigureChannelizer , Message )
2017-10-08 17:36:47 -04:00
MESSAGE_CLASS_DEFINITION ( NFMDemod : : MsgReportCTCSSFreq , Message )
2014-05-18 11:52:39 -04:00
2018-05-30 16:25:57 -04:00
const QString NFMDemod : : m_channelIdURI = " sdrangel.channel.nfmdemod " ;
2017-11-22 19:19:32 -05:00
const QString NFMDemod : : m_channelId = " NFMDemod " ;
2017-11-08 08:23:49 -05:00
2017-08-26 18:27:11 -04:00
static const double afSqTones [ 2 ] = { 1000.0 , 6000.0 } ; // {1200.0, 8000.0};
2018-04-14 15:45:45 -04:00
static const double afSqTones_lowrate [ 2 ] = { 1000.0 , 3500.0 } ;
2017-08-26 18:27:11 -04:00
const int NFMDemod : : m_udpBlockSize = 512 ;
2017-10-08 08:06:48 -04:00
NFMDemod : : NFMDemod ( DeviceSourceAPI * devieAPI ) :
2017-12-20 17:57:06 -05:00
ChannelSinkAPI ( m_channelIdURI ) ,
m_deviceAPI ( devieAPI ) ,
2017-12-28 23:23:24 -05:00
m_inputSampleRate ( 48000 ) ,
m_inputFrequencyOffset ( 0 ) ,
2018-02-18 08:46:36 -05:00
m_running ( false ) ,
2017-12-20 17:57:06 -05:00
m_ctcssIndex ( 0 ) ,
m_sampleCount ( 0 ) ,
m_squelchCount ( 0 ) ,
2018-04-10 19:31:48 -04:00
m_squelchGate ( 4800 ) ,
2017-12-20 17:57:06 -05:00
m_squelchLevel ( - 990 ) ,
m_squelchOpen ( false ) ,
m_afSquelchOpen ( false ) ,
m_magsq ( 0.0f ) ,
m_magsqSum ( 0.0f ) ,
m_magsqPeak ( 0.0f ) ,
m_magsqCount ( 0 ) ,
2018-04-14 15:45:45 -04:00
m_afSquelch ( ) ,
2018-04-21 03:23:01 -04:00
m_squelchDelayLine ( 24000 ) ,
2017-12-20 17:57:06 -05:00
m_audioFifo ( 48000 ) ,
m_settingsMutex ( QMutex : : Recursive )
2014-05-18 11:52:39 -04:00
{
2018-03-16 05:24:19 -04:00
qDebug ( " NFMDemod::NFMDemod " ) ;
2017-11-22 19:19:32 -05:00
setObjectName ( m_channelId ) ;
2015-08-12 03:03:02 -04:00
2015-08-23 20:06:11 -04:00
m_audioBuffer . resize ( 1 < < 14 ) ;
2014-05-18 11:52:39 -04:00
m_audioBufferFill = 0 ;
2015-09-12 10:34:57 -04:00
m_agcLevel = 1.0 ;
2015-06-16 02:15:28 -04:00
2018-03-27 07:18:00 -04:00
DSPEngine : : instance ( ) - > getAudioDeviceManager ( ) - > addAudioSink ( & m_audioFifo , getInputMessageQueue ( ) ) ;
m_audioSampleRate = DSPEngine : : instance ( ) - > getAudioDeviceManager ( ) - > getOutputSampleRate ( ) ;
2018-04-14 15:45:45 -04:00
m_discriCompensation = ( m_audioSampleRate / 48000.0f ) ;
m_discriCompensation * = sqrt ( m_discriCompensation ) ;
2017-10-08 05:37:15 -04:00
2018-04-10 19:31:48 -04:00
m_ctcssDetector . setCoefficients ( m_audioSampleRate / 16 , m_audioSampleRate / 8.0f ) ; // 0.5s / 2 Hz resolution
2018-04-14 15:45:45 -04:00
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
2018-04-10 19:31:48 -04:00
2018-03-31 09:06:35 -04:00
m_lowpass . create ( 301 , m_audioSampleRate , 250.0 ) ;
2018-03-16 05:24:19 -04:00
applyChannelSettings ( m_inputSampleRate , m_inputFrequencyOffset , true ) ;
applySettings ( m_settings , true ) ;
2017-11-05 19:39:44 -05:00
m_channelizer = new DownChannelizer ( this ) ;
m_threadedChannelizer = new ThreadedBasebandSampleSink ( m_channelizer , this ) ;
m_deviceAPI - > addThreadedSink ( m_threadedChannelizer ) ;
2017-11-19 12:18:17 -05:00
m_deviceAPI - > addChannelAPI ( this ) ;
2014-05-18 11:52:39 -04:00
}
NFMDemod : : ~ NFMDemod ( )
{
2018-03-23 22:36:49 -04:00
DSPEngine : : instance ( ) - > getAudioDeviceManager ( ) - > removeAudioSink ( & m_audioFifo ) ;
2017-11-19 12:18:17 -05:00
m_deviceAPI - > removeChannelAPI ( this ) ;
2017-10-08 08:06:48 -04:00
m_deviceAPI - > removeThreadedSink ( m_threadedChannelizer ) ;
delete m_threadedChannelizer ;
delete m_channelizer ;
2014-05-18 11:52:39 -04:00
}
2015-01-26 14:33:28 -05:00
float arctan2 ( Real y , Real x )
{
Real coeff_1 = M_PI / 4 ;
Real coeff_2 = 3 * coeff_1 ;
Real abs_y = fabs ( y ) + 1e-10 ; // kludge to prevent 0/0 condition
Real angle ;
if ( x > = 0 ) {
Real r = ( x - abs_y ) / ( x + abs_y ) ;
angle = coeff_1 - coeff_1 * r ;
} else {
Real r = ( x + abs_y ) / ( abs_y - x ) ;
angle = coeff_2 - coeff_1 * r ;
}
2017-12-28 21:03:28 -05:00
if ( y < 0 ) {
2015-01-26 14:33:28 -05:00
return ( - angle ) ;
2017-12-28 21:03:28 -05:00
} else {
return ( angle ) ;
}
2015-01-26 14:33:28 -05:00
}
Real angleDist ( Real a , Real b )
{
Real dist = b - a ;
while ( dist < = M_PI )
dist + = 2 * M_PI ;
while ( dist > = M_PI )
dist - = 2 * M_PI ;
return dist ;
}
2017-05-25 14:13:34 -04:00
void NFMDemod : : feed ( const SampleVector : : const_iterator & begin , const SampleVector : : const_iterator & end , bool firstOfBurst __attribute__ ( ( unused ) ) )
2014-05-18 11:52:39 -04:00
{
Complex ci ;
2018-02-19 18:14:50 -05:00
if ( ! m_running ) {
return ;
}
2015-08-24 16:09:46 -04:00
m_settingsMutex . lock ( ) ;
2015-01-26 14:33:28 -05:00
2015-08-19 21:38:31 -04:00
for ( SampleVector : : const_iterator it = begin ; it ! = end ; + + it )
{
2015-10-11 00:34:12 -04:00
Complex c ( it - > real ( ) , it - > imag ( ) ) ;
2014-05-18 11:52:39 -04:00
c * = m_nco . nextIQ ( ) ;
2017-11-05 20:12:44 -05:00
if ( m_interpolator . decimate ( & m_interpolatorDistanceRemain , c , & ci ) )
{
2015-01-26 14:33:28 -05:00
2017-11-05 20:12:44 -05:00
qint16 sample ;
2015-06-16 02:15:28 -04:00
2017-11-05 20:12:44 -05:00
double magsqRaw ; // = ci.real()*ci.real() + c.imag()*c.imag();
Real deviation ;
2017-01-30 17:28:06 -05:00
2017-11-05 20:12:44 -05:00
Real demod = m_phaseDiscri . phaseDiscriminatorDelta ( ci , magsqRaw , deviation ) ;
2017-01-30 17:28:06 -05:00
2018-01-22 02:46:05 -05:00
Real magsq = magsqRaw / ( SDR_RX_SCALED * SDR_RX_SCALED ) ;
2018-02-03 04:33:02 -05:00
m_movingAverage ( magsq ) ;
2017-11-05 20:12:44 -05:00
m_magsqSum + = magsq ;
2016-12-04 20:09:08 -05:00
2017-11-05 20:12:44 -05:00
if ( magsq > m_magsqPeak )
{
m_magsqPeak = magsq ;
}
2016-12-04 20:09:08 -05:00
2017-11-05 20:12:44 -05:00
m_magsqCount + + ;
m_sampleCount + + ;
2016-12-04 21:46:42 -05:00
2017-11-05 20:12:44 -05:00
// AF processing
2015-06-19 02:27:29 -04:00
2017-11-05 20:12:44 -05:00
if ( m_settings . m_deltaSquelch )
{
2018-04-21 03:23:01 -04:00
if ( m_afSquelch . analyze ( demod * m_discriCompensation ) )
{
m_afSquelchOpen = m_afSquelch . evaluate ( ) ; // ? m_squelchGate + m_squelchDecay : 0;
if ( ! m_afSquelchOpen ) {
m_squelchDelayLine . zeroBack ( m_audioSampleRate / 10 ) ; // zero out evaluation period
}
2017-11-05 20:12:44 -05:00
}
2015-06-19 02:27:29 -04:00
2017-11-05 20:12:44 -05:00
if ( m_afSquelchOpen )
{
2018-04-21 03:23:01 -04:00
m_squelchDelayLine . write ( demod * m_discriCompensation ) ;
2018-05-01 19:22:34 -04:00
if ( m_squelchCount < 2 * m_squelchGate ) {
2017-11-05 20:12:44 -05:00
m_squelchCount + + ;
}
}
else
{
2018-04-21 03:23:01 -04:00
m_squelchDelayLine . write ( 0 ) ;
if ( m_squelchCount > 0 ) {
m_squelchCount - - ;
2017-11-05 20:12:44 -05:00
}
}
}
else
{
2018-02-03 04:33:02 -05:00
if ( ( Real ) m_movingAverage < m_squelchLevel )
2017-11-05 20:12:44 -05:00
{
2018-04-21 03:23:01 -04:00
m_squelchDelayLine . write ( 0 ) ;
if ( m_squelchCount > 0 ) {
m_squelchCount - - ;
2017-11-05 20:12:44 -05:00
}
}
else
{
2018-04-21 03:23:01 -04:00
m_squelchDelayLine . write ( demod * m_discriCompensation ) ;
2018-05-01 19:22:34 -04:00
if ( m_squelchCount < 2 * m_squelchGate ) {
2017-11-05 20:12:44 -05:00
m_squelchCount + + ;
}
}
}
2015-06-19 02:27:29 -04:00
2018-05-09 13:38:26 -04:00
m_squelchOpen = ( m_squelchCount > m_squelchGate ) ;
2018-05-01 19:22:34 -04:00
if ( m_settings . m_audioMute )
2017-11-05 20:12:44 -05:00
{
2018-05-01 19:22:34 -04:00
sample = 0 ;
}
else
{
if ( m_squelchOpen )
{
if ( m_settings . m_ctcssOn )
2017-05-13 06:10:03 -04:00
{
2018-05-01 19:22:34 -04:00
Real ctcss_sample = m_lowpass . filter ( demod * m_discriCompensation ) ;
2017-11-05 20:12:44 -05:00
2018-05-01 19:22:34 -04:00
if ( ( m_sampleCount & 7 ) = = 7 ) // decimate 48k -> 6k
{
if ( m_ctcssDetector . analyze ( & ctcss_sample ) )
2017-11-05 20:12:44 -05:00
{
2018-05-01 19:22:34 -04:00
int maxToneIndex ;
if ( m_ctcssDetector . getDetectedTone ( maxToneIndex ) )
2017-11-05 20:12:44 -05:00
{
2018-05-01 19:22:34 -04:00
if ( maxToneIndex + 1 ! = m_ctcssIndex )
{
if ( getMessageQueueToGUI ( ) ) {
MsgReportCTCSSFreq * msg = MsgReportCTCSSFreq : : create ( m_ctcssDetector . getToneSet ( ) [ maxToneIndex ] ) ;
getMessageQueueToGUI ( ) - > push ( msg ) ;
}
m_ctcssIndex = maxToneIndex + 1 ;
2017-11-05 20:12:44 -05:00
}
}
2018-05-01 19:22:34 -04:00
else
2017-11-05 20:12:44 -05:00
{
2018-05-01 19:22:34 -04:00
if ( m_ctcssIndex ! = 0 )
{
if ( getMessageQueueToGUI ( ) ) {
MsgReportCTCSSFreq * msg = MsgReportCTCSSFreq : : create ( 0 ) ;
getMessageQueueToGUI ( ) - > push ( msg ) ;
}
m_ctcssIndex = 0 ;
2017-11-05 20:12:44 -05:00
}
}
}
2017-05-13 06:10:03 -04:00
}
}
2017-11-05 20:12:44 -05:00
2018-05-01 19:22:34 -04:00
if ( m_settings . m_ctcssOn & & m_ctcssIndexSelected & & ( m_ctcssIndexSelected ! = m_ctcssIndex ) )
{
sample = 0 ;
}
else
{
sample = m_bandpass . filter ( m_squelchDelayLine . readBack ( m_squelchGate ) ) * m_settings . m_volume ;
}
2017-11-05 20:12:44 -05:00
}
else
{
2018-05-01 19:22:34 -04:00
if ( m_ctcssIndex ! = 0 )
{
if ( getMessageQueueToGUI ( ) ) {
MsgReportCTCSSFreq * msg = MsgReportCTCSSFreq : : create ( 0 ) ;
getMessageQueueToGUI ( ) - > push ( msg ) ;
}
m_ctcssIndex = 0 ;
2017-05-13 06:10:03 -04:00
}
2017-10-08 17:36:47 -04:00
2018-05-01 19:22:34 -04:00
sample = 0 ;
2017-11-05 20:12:44 -05:00
}
}
2015-01-26 14:33:28 -05:00
2018-05-01 19:22:34 -04:00
2017-11-05 20:12:44 -05:00
m_audioBuffer [ m_audioBufferFill ] . l = sample ;
m_audioBuffer [ m_audioBufferFill ] . r = sample ;
+ + m_audioBufferFill ;
2015-08-19 21:38:31 -04:00
2017-11-05 20:12:44 -05:00
if ( m_audioBufferFill > = m_audioBuffer . size ( ) )
{
2018-09-12 09:30:53 -04:00
uint res = m_audioFifo . write ( ( const quint8 * ) & m_audioBuffer [ 0 ] , m_audioBufferFill ) ;
2015-08-19 21:38:31 -04:00
2017-11-05 20:12:44 -05:00
if ( res ! = m_audioBufferFill )
{
qDebug ( " NFMDemod::feed: %u/%u audio samples written " , res , m_audioBufferFill ) ;
}
2015-08-19 21:38:31 -04:00
2017-11-05 20:12:44 -05:00
m_audioBufferFill = 0 ;
}
2014-05-18 11:52:39 -04:00
2017-11-05 20:12:44 -05:00
m_interpolatorDistanceRemain + = m_interpolatorDistance ;
}
2014-05-18 11:52:39 -04:00
}
2015-08-19 21:38:31 -04:00
2018-02-19 18:14:50 -05:00
if ( m_audioBufferFill > 0 )
2015-08-19 21:38:31 -04:00
{
2018-09-12 09:30:53 -04:00
uint res = m_audioFifo . write ( ( const quint8 * ) & m_audioBuffer [ 0 ] , m_audioBufferFill ) ;
2015-08-19 21:38:31 -04:00
if ( res ! = m_audioBufferFill )
{
2015-08-23 20:06:11 -04:00
qDebug ( " NFMDemod::feed: %u/%u tail samples written " , res , m_audioBufferFill ) ;
}
2015-08-19 21:38:31 -04:00
2015-01-26 14:33:28 -05:00
m_audioBufferFill = 0 ;
}
2014-05-18 11:52:39 -04:00
2015-08-24 16:09:46 -04:00
m_settingsMutex . unlock ( ) ;
2014-05-18 11:52:39 -04:00
}
void NFMDemod : : start ( )
{
2017-06-05 19:53:52 -04:00
qDebug ( ) < < " NFMDemod::start " ;
2017-12-29 05:04:47 -05:00
m_squelchCount = 0 ;
2015-08-23 20:06:11 -04:00
m_audioFifo . clear ( ) ;
2015-12-16 18:45:44 -05:00
m_phaseDiscri . reset ( ) ;
2018-01-08 18:59:10 -05:00
applyChannelSettings ( m_inputSampleRate , m_inputFrequencyOffset , true ) ;
2018-02-18 08:46:36 -05:00
m_running = true ;
2014-05-18 11:52:39 -04:00
}
void NFMDemod : : stop ( )
{
2018-02-18 08:46:36 -05:00
qDebug ( ) < < " NFMDemod::stop " ;
m_running = false ;
2014-05-18 11:52:39 -04:00
}
2015-08-17 02:29:34 -04:00
bool NFMDemod : : handleMessage ( const Message & cmd )
2014-05-18 11:52:39 -04:00
{
2016-10-02 15:52:39 -04:00
if ( DownChannelizer : : MsgChannelizerNotification : : match ( cmd ) )
2015-08-17 02:29:34 -04:00
{
2016-10-02 15:52:39 -04:00
DownChannelizer : : MsgChannelizerNotification & notif = ( DownChannelizer : : MsgChannelizerNotification & ) cmd ;
2017-12-28 23:23:24 -05:00
qDebug ( ) < < " NFMDemod::handleMessage: DownChannelizer::MsgChannelizerNotification " ;
2015-08-17 02:29:34 -04:00
2017-12-28 23:23:24 -05:00
applyChannelSettings ( notif . getSampleRate ( ) , notif . getFrequencyOffset ( ) ) ;
2015-08-17 02:29:34 -04:00
2014-05-18 11:52:39 -04:00
return true ;
2015-08-17 02:29:34 -04:00
}
2017-10-08 08:06:48 -04:00
else if ( MsgConfigureChannelizer : : match ( cmd ) )
{
MsgConfigureChannelizer & cfg = ( MsgConfigureChannelizer & ) cmd ;
2017-12-29 05:04:47 -05:00
2017-12-28 23:23:24 -05:00
qDebug ( ) < < " NFMDemod::handleMessage: MsgConfigureChannelizer: "
< < " sampleRate: " < < cfg . getSampleRate ( )
< < " centerFrequency: " < < cfg . getCenterFrequency ( ) ;
2017-10-08 08:06:48 -04:00
m_channelizer - > configure ( m_channelizer - > getInputMessageQueue ( ) ,
cfg . getSampleRate ( ) ,
cfg . getCenterFrequency ( ) ) ;
return true ;
}
2015-08-17 02:29:34 -04:00
else if ( MsgConfigureNFMDemod : : match ( cmd ) )
{
2017-10-08 05:37:15 -04:00
MsgConfigureNFMDemod & cfg = ( MsgConfigureNFMDemod & ) cmd ;
2017-12-28 23:23:24 -05:00
qDebug ( ) < < " NFMDemod::handleMessage: MsgConfigureNFMDemod " ;
2015-08-17 02:29:34 -04:00
2017-12-29 05:04:47 -05:00
applySettings ( cfg . getSettings ( ) , cfg . getForce ( ) ) ;
2017-10-08 05:37:15 -04:00
return true ;
2015-08-17 02:29:34 -04:00
}
2018-03-05 20:23:47 -05:00
else if ( BasebandSampleSink : : MsgThreadedSink : : match ( cmd ) )
{
BasebandSampleSink : : MsgThreadedSink & cfg = ( BasebandSampleSink : : MsgThreadedSink & ) cmd ;
const QThread * thread = cfg . getThread ( ) ;
qDebug ( " NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p " , thread ) ;
return true ;
}
2018-03-27 07:18:00 -04:00
else if ( DSPConfigureAudio : : match ( cmd ) )
{
DSPConfigureAudio & cfg = ( DSPConfigureAudio & ) cmd ;
uint32_t sampleRate = cfg . getSampleRate ( ) ;
qDebug ( ) < < " NFMDemod::handleMessage: DSPConfigureAudio: "
< < " sampleRate: " < < sampleRate ;
if ( sampleRate ! = m_audioSampleRate ) {
applyAudioSampleRate ( sampleRate ) ;
}
return true ;
}
2018-02-14 14:11:36 -05:00
else if ( DSPSignalNotification : : match ( cmd ) )
{
return true ;
}
2015-08-17 02:29:34 -04:00
else
{
2015-08-23 20:06:11 -04:00
return false ;
2014-05-18 11:52:39 -04:00
}
}
2015-01-26 14:33:28 -05:00
2018-03-27 07:18:00 -04:00
void NFMDemod : : applyAudioSampleRate ( int sampleRate )
{
qDebug ( " NFMDemod::applyAudioSampleRate: %d " , sampleRate ) ;
MsgConfigureChannelizer * channelConfigMsg = MsgConfigureChannelizer : : create (
sampleRate , m_settings . m_inputFrequencyOffset ) ;
m_inputMessageQueue . push ( channelConfigMsg ) ;
m_settingsMutex . lock ( ) ;
2018-04-14 15:45:45 -04:00
2018-03-27 07:18:00 -04:00
m_interpolator . create ( 16 , m_inputSampleRate , m_settings . m_rfBandwidth / 2.2f ) ;
m_interpolatorDistanceRemain = 0 ;
m_interpolatorDistance = ( Real ) m_inputSampleRate / ( Real ) sampleRate ;
m_lowpass . create ( 301 , sampleRate , 250.0 ) ;
m_bandpass . create ( 301 , sampleRate , 300.0 , m_settings . m_afBandwidth ) ;
2018-04-10 19:31:48 -04:00
m_squelchGate = ( sampleRate / 100 ) * m_settings . m_squelchGate ; // gate is given in 10s of ms at 48000 Hz audio sample rate
m_squelchCount = 0 ; // reset squelch open counter
m_ctcssDetector . setCoefficients ( sampleRate / 16 , sampleRate / 8.0f ) ; // 0.5s / 2 Hz resolution
2018-04-14 15:45:45 -04:00
if ( sampleRate < 16000 ) {
m_afSquelch . setCoefficients ( sampleRate / 2000 , 600 , sampleRate , 200 , 0 , afSqTones_lowrate ) ; // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
} else {
m_afSquelch . setCoefficients ( sampleRate / 2000 , 600 , sampleRate , 200 , 0 , afSqTones ) ; // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
}
m_discriCompensation = ( sampleRate / 48000.0f ) ;
m_discriCompensation * = sqrt ( m_discriCompensation ) ;
2018-04-13 22:45:22 -04:00
m_phaseDiscri . setFMScaling ( sampleRate / static_cast < float > ( m_settings . m_fmDeviation ) ) ;
2018-03-30 02:55:49 -04:00
m_audioFifo . setSize ( sampleRate ) ;
2018-04-21 03:23:01 -04:00
m_squelchDelayLine . resize ( sampleRate / 2 ) ;
2018-04-14 15:45:45 -04:00
2018-03-27 07:18:00 -04:00
m_settingsMutex . unlock ( ) ;
m_audioSampleRate = sampleRate ;
}
2018-01-08 18:59:10 -05:00
void NFMDemod : : applyChannelSettings ( int inputSampleRate , int inputFrequencyOffset , bool force )
2015-01-26 14:33:28 -05:00
{
2017-12-28 23:23:24 -05:00
qDebug ( ) < < " NFMDemod::applyChannelSettings: "
< < " inputSampleRate: " < < inputSampleRate
< < " inputFrequencyOffset: " < < inputFrequencyOffset ;
if ( ( inputFrequencyOffset ! = m_inputFrequencyOffset ) | |
2018-01-08 18:59:10 -05:00
( inputSampleRate ! = m_inputSampleRate ) | | force )
2017-12-28 23:23:24 -05:00
{
m_nco . setFreq ( - inputFrequencyOffset , inputSampleRate ) ;
}
2018-01-08 18:59:10 -05:00
if ( ( inputSampleRate ! = m_inputSampleRate ) | | force )
2017-10-08 05:37:15 -04:00
{
2017-12-28 23:23:24 -05:00
m_settingsMutex . lock ( ) ;
2018-03-27 07:18:00 -04:00
m_interpolator . create ( 16 , inputSampleRate , m_settings . m_rfBandwidth / 2.2f ) ;
2017-12-28 23:23:24 -05:00
m_interpolatorDistanceRemain = 0 ;
2018-03-27 07:18:00 -04:00
m_interpolatorDistance = ( Real ) inputSampleRate / ( Real ) m_audioSampleRate ;
2017-12-28 23:23:24 -05:00
m_settingsMutex . unlock ( ) ;
2017-10-08 05:37:15 -04:00
}
2015-01-26 14:33:28 -05:00
2017-12-28 23:23:24 -05:00
m_inputSampleRate = inputSampleRate ;
m_inputFrequencyOffset = inputFrequencyOffset ;
}
void NFMDemod : : applySettings ( const NFMDemodSettings & settings , bool force )
{
qDebug ( ) < < " NFMDemod::applySettings: "
< < " m_inputFrequencyOffset: " < < settings . m_inputFrequencyOffset
< < " m_rfBandwidth: " < < settings . m_rfBandwidth
< < " m_afBandwidth: " < < settings . m_afBandwidth
< < " m_fmDeviation: " < < settings . m_fmDeviation
< < " m_volume: " < < settings . m_volume
< < " m_squelchGate: " < < settings . m_squelchGate
< < " m_deltaSquelch: " < < settings . m_deltaSquelch
< < " m_squelch: " < < settings . m_squelch
< < " m_ctcssIndex: " < < settings . m_ctcssIndex
< < " m_ctcssOn: " < < settings . m_ctcssOn
< < " m_audioMute: " < < settings . m_audioMute
2018-03-27 07:18:00 -04:00
< < " m_audioDeviceName: " < < settings . m_audioDeviceName
2017-12-28 23:23:24 -05:00
< < " force: " < < force ;
2018-03-27 07:18:00 -04:00
if ( ( settings . m_rfBandwidth ! = m_settings . m_rfBandwidth ) | | force )
2017-10-08 05:37:15 -04:00
{
m_settingsMutex . lock ( ) ;
2017-12-28 23:23:24 -05:00
m_interpolator . create ( 16 , m_inputSampleRate , settings . m_rfBandwidth / 2.2 ) ;
2017-10-08 05:37:15 -04:00
m_interpolatorDistanceRemain = 0 ;
2018-03-27 07:18:00 -04:00
m_interpolatorDistance = ( Real ) m_inputSampleRate / ( Real ) m_audioSampleRate ;
2017-10-08 05:37:15 -04:00
m_settingsMutex . unlock ( ) ;
}
2015-01-26 14:33:28 -05:00
2018-04-10 19:31:48 -04:00
if ( ( settings . m_fmDeviation ! = m_settings . m_fmDeviation ) | | force )
2017-10-08 05:37:15 -04:00
{
2018-04-10 19:31:48 -04:00
m_phaseDiscri . setFMScaling ( ( 8.0f * m_audioSampleRate ) / static_cast < float > ( settings . m_fmDeviation ) ) ; // integrate 4x factor
2017-10-08 05:37:15 -04:00
}
2016-04-03 21:44:06 -04:00
2018-03-27 07:18:00 -04:00
if ( ( settings . m_afBandwidth ! = m_settings . m_afBandwidth ) | | force )
2017-10-08 05:37:15 -04:00
{
m_settingsMutex . lock ( ) ;
2018-03-27 07:18:00 -04:00
m_bandpass . create ( 301 , m_audioSampleRate , 300.0 , settings . m_afBandwidth ) ;
2017-10-08 05:37:15 -04:00
m_settingsMutex . unlock ( ) ;
}
2015-01-26 14:33:28 -05:00
2017-10-08 05:37:15 -04:00
if ( ( settings . m_squelchGate ! = m_settings . m_squelchGate ) | | force )
{
2018-04-10 19:31:48 -04:00
m_squelchGate = ( m_audioSampleRate / 100 ) * settings . m_squelchGate ; // gate is given in 10s of ms at 48000 Hz audio sample rate
2017-10-08 05:37:15 -04:00
m_squelchCount = 0 ; // reset squelch open counter
}
2016-03-31 13:38:39 -04:00
2017-10-08 05:37:15 -04:00
if ( ( settings . m_squelch ! = m_settings . m_squelch ) | |
( settings . m_deltaSquelch ! = m_settings . m_deltaSquelch ) | | force )
{
if ( settings . m_deltaSquelch )
2018-10-11 02:52:50 -04:00
{ // input is a value in negative centis
m_squelchLevel = ( - settings . m_squelch ) / 100.0 ;
2017-10-08 05:37:15 -04:00
m_afSquelch . setThreshold ( m_squelchLevel ) ;
m_afSquelch . reset ( ) ;
}
else
2018-10-11 02:52:50 -04:00
{ // input is a value in deci-Bels
m_squelchLevel = std : : pow ( 10.0 , settings . m_squelch / 10.0 ) ;
2018-02-03 04:33:02 -05:00
m_movingAverage . reset ( ) ;
2017-10-08 05:37:15 -04:00
}
2017-11-05 19:39:44 -05:00
m_squelchCount = 0 ; // reset squelch open counter
2017-10-08 05:37:15 -04:00
}
2015-01-26 14:33:28 -05:00
2017-10-08 05:37:15 -04:00
if ( ( settings . m_ctcssIndex ! = m_settings . m_ctcssIndex ) | | force )
2017-10-08 04:52:37 -04:00
{
2017-10-08 05:37:15 -04:00
setSelectedCtcssIndex ( settings . m_ctcssIndex ) ;
2017-10-08 04:52:37 -04:00
}
2018-03-27 07:18:00 -04:00
if ( ( settings . m_audioDeviceName ! = m_settings . m_audioDeviceName ) | | force )
{
AudioDeviceManager * audioDeviceManager = DSPEngine : : instance ( ) - > getAudioDeviceManager ( ) ;
int audioDeviceIndex = audioDeviceManager - > getOutputDeviceIndex ( settings . m_audioDeviceName ) ;
//qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex);
audioDeviceManager - > addAudioSink ( & m_audioFifo , getInputMessageQueue ( ) , audioDeviceIndex ) ;
uint32_t audioSampleRate = audioDeviceManager - > getOutputSampleRate ( audioDeviceIndex ) ;
if ( m_audioSampleRate ! = audioSampleRate ) {
applyAudioSampleRate ( audioSampleRate ) ;
}
}
2017-10-08 05:37:15 -04:00
m_settings = settings ;
2015-01-26 14:33:28 -05:00
}
2017-12-10 14:27:08 -05:00
2017-12-17 17:15:42 -05:00
QByteArray NFMDemod : : serialize ( ) const
{
return m_settings . serialize ( ) ;
}
bool NFMDemod : : deserialize ( const QByteArray & data )
{
2017-12-27 21:21:48 -05:00
bool success = true ;
if ( ! m_settings . deserialize ( data ) )
2017-12-17 17:15:42 -05:00
{
m_settings . resetToDefaults ( ) ;
2017-12-27 21:21:48 -05:00
success = false ;
2017-12-17 17:15:42 -05:00
}
2017-12-27 21:21:48 -05:00
NFMDemod : : MsgConfigureChannelizer * channelConfigMsg = NFMDemod : : MsgConfigureChannelizer : : create (
2018-03-27 07:18:00 -04:00
m_audioSampleRate , m_settings . m_inputFrequencyOffset ) ;
2017-12-27 21:21:48 -05:00
m_inputMessageQueue . push ( channelConfigMsg ) ;
MsgConfigureNFMDemod * msg = MsgConfigureNFMDemod : : create ( m_settings , true ) ;
m_inputMessageQueue . push ( msg ) ;
return success ;
2017-12-17 17:15:42 -05:00
}
2017-12-10 14:27:08 -05:00
int NFMDemod : : webapiSettingsGet (
SWGSDRangel : : SWGChannelSettings & response ,
QString & errorMessage __attribute__ ( ( unused ) ) )
{
response . setNfmDemodSettings ( new SWGSDRangel : : SWGNFMDemodSettings ( ) ) ;
2018-02-21 07:50:50 -05:00
response . getNfmDemodSettings ( ) - > init ( ) ;
2017-12-28 23:23:24 -05:00
webapiFormatChannelSettings ( response , m_settings ) ;
2017-12-10 14:27:08 -05:00
return 200 ;
}
2017-12-11 12:18:47 -05:00
int NFMDemod : : webapiSettingsPutPatch (
bool force ,
2017-12-23 21:27:07 -05:00
const QStringList & channelSettingsKeys ,
2017-12-11 12:18:47 -05:00
SWGSDRangel : : SWGChannelSettings & response ,
QString & errorMessage __attribute__ ( ( unused ) ) )
{
2017-12-23 21:27:07 -05:00
NFMDemodSettings settings = m_settings ;
2017-12-26 16:29:24 -05:00
bool frequencyOffsetChanged = false ;
2017-12-23 21:27:07 -05:00
if ( channelSettingsKeys . contains ( " afBandwidth " ) ) {
settings . m_afBandwidth = response . getNfmDemodSettings ( ) - > getAfBandwidth ( ) ;
}
if ( channelSettingsKeys . contains ( " audioMute " ) ) {
settings . m_audioMute = response . getNfmDemodSettings ( ) - > getAudioMute ( ) ! = 0 ;
}
if ( channelSettingsKeys . contains ( " ctcssIndex " ) ) {
settings . m_ctcssIndex = response . getNfmDemodSettings ( ) - > getCtcssIndex ( ) ;
}
if ( channelSettingsKeys . contains ( " ctcssOn " ) ) {
settings . m_ctcssOn = response . getNfmDemodSettings ( ) - > getCtcssOn ( ) ! = 0 ;
}
if ( channelSettingsKeys . contains ( " deltaSquelch " ) ) {
settings . m_deltaSquelch = response . getNfmDemodSettings ( ) - > getDeltaSquelch ( ) ! = 0 ;
}
if ( channelSettingsKeys . contains ( " fmDeviation " ) ) {
settings . m_fmDeviation = response . getNfmDemodSettings ( ) - > getFmDeviation ( ) ;
}
if ( channelSettingsKeys . contains ( " inputFrequencyOffset " ) )
{
settings . m_inputFrequencyOffset = response . getNfmDemodSettings ( ) - > getInputFrequencyOffset ( ) ;
frequencyOffsetChanged = true ;
}
if ( channelSettingsKeys . contains ( " rfBandwidth " ) ) {
settings . m_rfBandwidth = response . getNfmDemodSettings ( ) - > getRfBandwidth ( ) ;
}
if ( channelSettingsKeys . contains ( " rgbColor " ) ) {
settings . m_rgbColor = response . getNfmDemodSettings ( ) - > getRgbColor ( ) ;
}
if ( channelSettingsKeys . contains ( " squelch " ) ) {
settings . m_squelch = response . getNfmDemodSettings ( ) - > getSquelch ( ) ;
}
if ( channelSettingsKeys . contains ( " squelchGate " ) ) {
settings . m_squelchGate = response . getNfmDemodSettings ( ) - > getSquelchGate ( ) ;
}
if ( channelSettingsKeys . contains ( " title " ) ) {
settings . m_title = * response . getNfmDemodSettings ( ) - > getTitle ( ) ;
}
if ( channelSettingsKeys . contains ( " volume " ) ) {
settings . m_volume = response . getNfmDemodSettings ( ) - > getVolume ( ) ;
}
2018-03-29 01:55:51 -04:00
if ( channelSettingsKeys . contains ( " audioDeviceName " ) ) {
settings . m_audioDeviceName = * response . getNfmDemodSettings ( ) - > getAudioDeviceName ( ) ;
}
2017-12-23 21:27:07 -05:00
if ( frequencyOffsetChanged )
{
MsgConfigureChannelizer * channelConfigMsg = MsgConfigureChannelizer : : create (
2018-04-10 19:31:48 -04:00
m_audioSampleRate , settings . m_inputFrequencyOffset ) ;
2017-12-23 21:27:07 -05:00
m_inputMessageQueue . push ( channelConfigMsg ) ;
}
2017-12-11 12:18:47 -05:00
MsgConfigureNFMDemod * msg = MsgConfigureNFMDemod : : create ( settings , force ) ;
m_inputMessageQueue . push ( msg ) ;
if ( m_guiMessageQueue ) // forward to GUI if any
{
MsgConfigureNFMDemod * msgToGUI = MsgConfigureNFMDemod : : create ( settings , force ) ;
m_guiMessageQueue - > push ( msgToGUI ) ;
}
2017-12-23 21:27:07 -05:00
webapiFormatChannelSettings ( response , settings ) ;
2017-12-11 12:18:47 -05:00
return 200 ;
}
2017-12-23 21:27:07 -05:00
2018-03-18 15:17:11 -04:00
int NFMDemod : : webapiReportGet (
SWGSDRangel : : SWGChannelReport & response ,
QString & errorMessage __attribute__ ( ( unused ) ) )
{
response . setNfmDemodReport ( new SWGSDRangel : : SWGNFMDemodReport ( ) ) ;
response . getNfmDemodReport ( ) - > init ( ) ;
webapiFormatChannelReport ( response ) ;
return 200 ;
}
2017-12-23 21:27:07 -05:00
void NFMDemod : : webapiFormatChannelSettings ( SWGSDRangel : : SWGChannelSettings & response , const NFMDemodSettings & settings )
{
response . getNfmDemodSettings ( ) - > setAfBandwidth ( settings . m_afBandwidth ) ;
response . getNfmDemodSettings ( ) - > setAudioMute ( settings . m_audioMute ? 1 : 0 ) ;
response . getNfmDemodSettings ( ) - > setCtcssIndex ( settings . m_ctcssIndex ) ;
response . getNfmDemodSettings ( ) - > setCtcssOn ( settings . m_ctcssOn ? 1 : 0 ) ;
response . getNfmDemodSettings ( ) - > setDeltaSquelch ( settings . m_deltaSquelch ? 1 : 0 ) ;
response . getNfmDemodSettings ( ) - > setFmDeviation ( settings . m_fmDeviation ) ;
response . getNfmDemodSettings ( ) - > setInputFrequencyOffset ( settings . m_inputFrequencyOffset ) ;
response . getNfmDemodSettings ( ) - > setRfBandwidth ( settings . m_rfBandwidth ) ;
response . getNfmDemodSettings ( ) - > setRgbColor ( settings . m_rgbColor ) ;
response . getNfmDemodSettings ( ) - > setSquelch ( settings . m_squelch ) ;
response . getNfmDemodSettings ( ) - > setSquelchGate ( settings . m_squelchGate ) ;
response . getNfmDemodSettings ( ) - > setVolume ( settings . m_volume ) ;
if ( response . getNfmDemodSettings ( ) - > getTitle ( ) ) {
* response . getNfmDemodSettings ( ) - > getTitle ( ) = settings . m_title ;
} else {
response . getNfmDemodSettings ( ) - > setTitle ( new QString ( settings . m_title ) ) ;
}
2018-03-29 01:55:51 -04:00
if ( response . getNfmDemodSettings ( ) - > getAudioDeviceName ( ) ) {
* response . getNfmDemodSettings ( ) - > getAudioDeviceName ( ) = settings . m_audioDeviceName ;
} else {
response . getNfmDemodSettings ( ) - > setAudioDeviceName ( new QString ( settings . m_audioDeviceName ) ) ;
}
2017-12-23 21:27:07 -05:00
}
2018-03-18 15:17:11 -04:00
void NFMDemod : : webapiFormatChannelReport ( SWGSDRangel : : SWGChannelReport & response )
{
double magsqAvg , magsqPeak ;
int nbMagsqSamples ;
getMagSqLevels ( magsqAvg , magsqPeak , nbMagsqSamples ) ;
response . getNfmDemodReport ( ) - > setChannelPowerDb ( CalcDb : : dbPower ( magsqAvg ) ) ;
response . getNfmDemodReport ( ) - > setCtcssTone ( m_settings . m_ctcssOn ? ( m_ctcssIndex ? 0 : m_ctcssDetector . getToneSet ( ) [ m_ctcssIndex - 1 ] ) : 0 ) ;
response . getNfmDemodReport ( ) - > setSquelch ( m_squelchOpen ? 1 : 0 ) ;
2018-03-27 07:18:00 -04:00
response . getNfmDemodReport ( ) - > setAudioSampleRate ( m_audioSampleRate ) ;
response . getNfmDemodReport ( ) - > setChannelSampleRate ( m_inputSampleRate ) ;
2018-03-18 15:17:11 -04:00
}