1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-07-05 10:25:32 -04:00

Merge pull request #690 from kasper93/rf_filter

NFMDemod: Add RF filter for freq deviation
This commit is contained in:
Edouard Griffiths 2020-11-07 18:09:23 +01:00 committed by GitHub
commit a47416041f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 104 deletions

View File

@ -11,6 +11,8 @@ set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# configure version # configure version
set(sdrangel_VERSION_MAJOR "4") set(sdrangel_VERSION_MAJOR "4")
set(sdrangel_VERSION_MINOR "21") set(sdrangel_VERSION_MINOR "21")
@ -207,7 +209,7 @@ elseif (WIN32)
# compile with full multicore # compile with full multicore
if(MSVC) if(MSVC)
add_compile_definitions(/MP) add_compile_options(/MP)
endif() endif()
# in alternative we can use ExternalProject # in alternative we can use ExternalProject

View File

@ -35,6 +35,8 @@
const double NFMDemodSink::afSqTones[] = {1000.0, 6000.0}; // {1200.0, 8000.0}; const double NFMDemodSink::afSqTones[] = {1000.0, 6000.0}; // {1200.0, 8000.0};
const double NFMDemodSink::afSqTones_lowrate[] = {1000.0, 3500.0}; const double NFMDemodSink::afSqTones_lowrate[] = {1000.0, 3500.0};
const unsigned NFMDemodSink::FFT_FILTER_LENGTH = 1024;
const unsigned NFMDemodSink::CTCSS_DETECTOR_RATE = 6000;
NFMDemodSink::NFMDemodSink() : NFMDemodSink::NFMDemodSink() :
m_channelSampleRate(48000), m_channelSampleRate(48000),
@ -42,13 +44,15 @@ NFMDemodSink::NFMDemodSink() :
m_audioSampleRate(48000), m_audioSampleRate(48000),
m_audioBufferFill(0), m_audioBufferFill(0),
m_audioFifo(48000), m_audioFifo(48000),
m_rfFilter(FFT_FILTER_LENGTH),
m_ctcssIndex(0), m_ctcssIndex(0),
m_sampleCount(0), m_sampleCount(0),
m_squelchCount(0), m_squelchCount(0),
m_squelchGate(4800), m_squelchGate(4800),
m_filterTaps(48000 / 48 + 1), m_filterTaps((48000 / 48) | 1),
m_squelchLevel(-990), m_squelchLevel(-990),
m_squelchOpen(false), m_squelchOpen(false),
m_afSquelchOpen(false),
m_magsq(0.0f), m_magsq(0.0f),
m_magsqSum(0.0f), m_magsqSum(0.0f),
m_magsqPeak(0.0f), m_magsqPeak(0.0f),
@ -57,30 +61,31 @@ NFMDemodSink::NFMDemodSink() :
m_squelchDelayLine(24000), m_squelchDelayLine(24000),
m_messageQueueToGUI(nullptr) m_messageQueueToGUI(nullptr)
{ {
m_agcLevel = 1.0;
m_audioBuffer.resize(1<<16); m_audioBuffer.resize(1<<16);
m_phaseDiscri.setFMScaling(0.5f);
applySettings(m_settings, true); applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
} }
NFMDemodSink::~NFMDemodSink()
{
}
void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{ {
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it) for (SampleVector::const_iterator it = begin; it != end; ++it)
{ {
Complex c(it->real(), it->imag()); Complex c(it->real(), it->imag());
c *= m_nco.nextIQ(); c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate Complex ci;
fftfilt::cmplx *rf;
int rf_out = m_rfFilter.runFilt(c, &rf); // filter RF before demod
for (int i = 0 ; i < rf_out; i++)
{ {
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) if (m_interpolatorDistance == 1.0f)
{
processOneSample(rf[i]);
}
else if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, rf[i], &ci))
{ {
processOneSample(ci); processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance; m_interpolatorDistanceRemain += m_interpolatorDistance;
@ -88,13 +93,14 @@ void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
} }
else // decimate else // decimate
{ {
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) if (m_interpolator.decimate(&m_interpolatorDistanceRemain, rf[i], &ci))
{ {
processOneSample(ci); processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance; m_interpolatorDistanceRemain += m_interpolatorDistance;
} }
} }
} }
}
if (m_audioBufferFill > 0) if (m_audioBufferFill > 0)
{ {
@ -124,12 +130,12 @@ void NFMDemodSink::processOneSample(Complex &ci)
m_magsqCount++; m_magsqCount++;
m_sampleCount++; m_sampleCount++;
bool squelchOpen = false; bool squelchOpen = m_afSquelchOpen && m_settings.m_deltaSquelch;
if (m_settings.m_deltaSquelch) if (m_settings.m_deltaSquelch)
{ {
if (m_afSquelch.analyze(demod)) if (m_afSquelch.analyze(demod))
{ {
squelchOpen = m_afSquelch.evaluate(); m_afSquelchOpen = squelchOpen = m_afSquelch.evaluate();
if (!squelchOpen) { if (!squelchOpen) {
m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period
@ -163,15 +169,18 @@ void NFMDemodSink::processOneSample(Complex &ci)
if (m_squelchOpen) if (m_squelchOpen)
{ {
if (m_settings.m_ctcssOn) if (m_settings.m_ctcssOn)
{
int factor = (m_audioSampleRate / CTCSS_DETECTOR_RATE) - 1; // decimate -> 6k
if ((m_sampleCount & factor) == factor)
{ {
Real ctcssSample = m_ctcssLowpass.filter(demod); Real ctcssSample = m_ctcssLowpass.filter(demod);
int factor = (m_audioSampleRate / 6000) - 1; // decimate -> 6k if (m_ctcssDetector.analyze(&ctcssSample))
if ((m_sampleCount & factor) == factor && m_ctcssDetector.analyze(&ctcssSample))
{ {
int maxToneIndex; int maxToneIndex;
ctcssIndex = m_ctcssDetector.getDetectedTone(maxToneIndex) ? maxToneIndex + 1 : 0; ctcssIndex = m_ctcssDetector.getDetectedTone(maxToneIndex) ? maxToneIndex + 1 : 0;
} }
} }
}
if (!m_settings.m_audioMute && (!m_settings.m_ctcssOn || m_ctcssIndexSelected == ctcssIndex || m_ctcssIndexSelected == 0)) if (!m_settings.m_audioMute && (!m_settings.m_ctcssOn || m_ctcssIndexSelected == ctcssIndex || m_ctcssIndexSelected == 0))
{ {
@ -227,9 +236,13 @@ void NFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFreque
if ((channelSampleRate != m_channelSampleRate) || force) if ((channelSampleRate != m_channelSampleRate) || force)
{ {
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); m_interpolator.create(16, channelSampleRate, m_settings.m_fmDeviation);
m_interpolatorDistanceRemain = 0; m_interpolatorDistance = Real(channelSampleRate) / Real(m_audioSampleRate);
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate; m_interpolatorDistanceRemain = m_interpolatorDistance;
Real lowCut = -Real(m_settings.m_fmDeviation) / channelSampleRate;
Real hiCut = Real(m_settings.m_fmDeviation) / channelSampleRate;
m_rfFilter.create_filter(lowCut, hiCut);
} }
m_channelSampleRate = channelSampleRate; m_channelSampleRate = channelSampleRate;
@ -256,14 +269,16 @@ void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{ {
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); m_interpolator.create(16, m_channelSampleRate, settings.m_fmDeviation);
m_interpolatorDistanceRemain = 0; m_interpolatorDistance = Real(m_channelSampleRate) / Real(m_audioSampleRate);
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate; m_interpolatorDistanceRemain = m_interpolatorDistance;
} }
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
{ Real lowCut = -Real(settings.m_fmDeviation) / m_channelSampleRate;
m_phaseDiscri.setFMScaling((0.5f *m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor Real hiCut = Real(settings.m_fmDeviation) / m_channelSampleRate;
m_rfFilter.create_filter(lowCut, hiCut);
m_phaseDiscri.setFMScaling(Real(m_audioSampleRate) / (2.0f * settings.m_fmDeviation));
} }
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
@ -313,24 +328,25 @@ void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
qDebug("NFMDemodSink::applyAudioSampleRate: %u m_channelSampleRate: %d", sampleRate, m_channelSampleRate); qDebug("NFMDemodSink::applyAudioSampleRate: %u m_channelSampleRate: %d", sampleRate, m_channelSampleRate);
m_filterTaps = sampleRate / 48 + 1; m_filterTaps = (sampleRate / 48) | 1;
m_ctcssLowpass.create(m_filterTaps, sampleRate, 250.0); m_ctcssLowpass.create((CTCSS_DETECTOR_RATE / 48) | 1, CTCSS_DETECTOR_RATE, 250.0);
m_bandpass.create(m_filterTaps, sampleRate, 300.0, m_settings.m_afBandwidth); m_bandpass.create(m_filterTaps, sampleRate, 300.0, m_settings.m_afBandwidth);
m_lowpass.create(m_filterTaps, sampleRate, m_settings.m_afBandwidth); m_lowpass.create(m_filterTaps, sampleRate, m_settings.m_afBandwidth);
m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate 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_squelchCount = 0; // reset squelch open counter
m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution m_ctcssDetector.setCoefficients(sampleRate/16, CTCSS_DETECTOR_RATE); // 0.5s / 2 Hz resolution
if (sampleRate < 16000) { 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 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 { } 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_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
} }
m_afSquelch.setThreshold(m_squelchLevel);
m_phaseDiscri.setFMScaling((0.5f * sampleRate) / static_cast<float>(m_settings.m_fmDeviation)); m_phaseDiscri.setFMScaling(Real(sampleRate) / (2.0f * m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate); m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/2); m_squelchDelayLine.resize(sampleRate/2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = Real(m_channelSampleRate) / Real(sampleRate); m_interpolatorDistance = Real(m_channelSampleRate) / Real(sampleRate);
m_interpolatorDistanceRemain = m_interpolatorDistance;
m_audioSampleRate = sampleRate; m_audioSampleRate = sampleRate;
} }

View File

@ -24,6 +24,7 @@
#include "dsp/phasediscri.h" #include "dsp/phasediscri.h"
#include "dsp/nco.h" #include "dsp/nco.h"
#include "dsp/interpolator.h" #include "dsp/interpolator.h"
#include "dsp/fftfilt.h"
#include "dsp/firfilter.h" #include "dsp/firfilter.h"
#include "dsp/afsquelch.h" #include "dsp/afsquelch.h"
#include "dsp/agc.h" #include "dsp/agc.h"
@ -37,7 +38,6 @@
class NFMDemodSink : public ChannelSampleSink { class NFMDemodSink : public ChannelSampleSink {
public: public:
NFMDemodSink(); NFMDemodSink();
~NFMDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
@ -105,6 +105,7 @@ private:
NCO m_nco; NCO m_nco;
Interpolator m_interpolator; Interpolator m_interpolator;
fftfilt m_rfFilter;
Real m_interpolatorDistance; Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain; Real m_interpolatorDistanceRemain;
Lowpass<Real> m_ctcssLowpass; Lowpass<Real> m_ctcssLowpass;
@ -120,6 +121,7 @@ private:
Real m_squelchLevel; Real m_squelchLevel;
bool m_squelchOpen; bool m_squelchOpen;
bool m_afSquelchOpen;
double m_magsq; //!< displayed averaged value double m_magsq; //!< displayed averaged value
double m_magsqSum; double m_magsqSum;
double m_magsqPeak; double m_magsqPeak;
@ -128,7 +130,6 @@ private:
MovingAverageUtil<Real, double, 32> m_movingAverage; MovingAverageUtil<Real, double, 32> m_movingAverage;
AFSquelch m_afSquelch; AFSquelch m_afSquelch;
Real m_agcLevel; // AGC will aim to this level
DoubleBufferFIFO<Real> m_squelchDelayLine; DoubleBufferFIFO<Real> m_squelchDelayLine;
PhaseDiscriminators m_phaseDiscri; PhaseDiscriminators m_phaseDiscri;
@ -136,6 +137,8 @@ private:
static const double afSqTones[]; static const double afSqTones[];
static const double afSqTones_lowrate[]; static const double afSqTones_lowrate[];
static const unsigned FFT_FILTER_LENGTH;
static const unsigned CTCSS_DETECTOR_RATE;
void processOneSample(Complex &ci); void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; } MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }

View File

@ -214,9 +214,9 @@ bool AFSquelch::evaluate()
} }
// m_isOpen = ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)); // m_isOpen = ((minPower/maxPower < m_threshold) && (minIndex > maxIndex));
if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition if ((minPower/maxPower < m_threshold) && (minIndex > maxIndex)) // open condition
{ {
if (m_squelchCount < m_samplesAttack + m_samplesDecay) if (m_squelchCount < m_samplesAttack + m_samplesDecay)
{ {
m_squelchCount++; m_squelchCount++;

View File

@ -75,6 +75,14 @@ void fftfilt::init_filter()
// f1 == 0 ==> low pass filter // f1 == 0 ==> low pass filter
// f2 == 0 ==> high pass filter // f2 == 0 ==> high pass filter
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
fftfilt::fftfilt(int len)
{
flen = len;
pass = 0;
window = 0;
init_filter();
}
fftfilt::fftfilt(float f1, float f2, int len) fftfilt::fftfilt(float f1, float f2, int len)
{ {
flen = len; flen = len;

View File

@ -19,6 +19,7 @@ enum {NONE, BLACKMAN, HAMMING, HANNING};
public: public:
typedef std::complex<float> cmplx; typedef std::complex<float> cmplx;
fftfilt(int len);
fftfilt(float f1, float f2, int len); fftfilt(float f1, float f2, int len);
fftfilt(float f2, int len); fftfilt(float f2, int len);
~fftfilt(); ~fftfilt();

View File

@ -42,24 +42,17 @@ public:
m_samples[m_ptr] = sample; m_samples[m_ptr] = sample;
for (size_t i = 0; i < n_taps; ++i) for (int i = 0; i < n_taps; ++i)
{ {
acc += (m_samples[a++] + m_samples[b--]) * m_taps[i]; acc += (m_samples[a] + m_samples[b]) * m_taps[i];
if (a == n_samples) { a = (a == n_samples - 1) ? 0 : a + 1;
a = 0; b = (b == 0) ? n_samples - 1 : b - 1;
}
if (b == -1) {
b = n_samples - 1;
}
} }
acc += m_samples[a] * m_taps[n_taps]; acc += m_samples[a] * m_taps[n_taps];
if (++m_ptr == n_samples) { m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1;
m_ptr = 0;
}
return acc; return acc;
} }