///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 F4EXB //
// written by Edouard Griffiths //
// //
// 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 . //
///////////////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include
#include "dsddemod.h"
#include "dsddemodgui.h"
#include "audio/audiooutput.h"
#include "dsp/channelizer.h"
#include "dsp/pidcontroller.h"
#include "dsp/dspengine.h"
static const Real afSqTones[2] = {1200.0, 6400.0}; // {1200.0, 8000.0};
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message)
DSDDemod::DSDDemod(SampleSink* sampleSink) :
m_sampleCount(0),
m_squelchCount(0),
m_squelchOpen(false),
m_audioFifo(4, 48000),
m_fmExcursion(24),
m_settingsMutex(QMutex::Recursive),
m_scope(sampleSink),
m_scopeEnabled(true),
m_dsdDecoder()
{
setObjectName("DSDDemod");
m_samplesBuffer = new qint16[1<<18]; // 128 k Samples is the maximum size of all input devices sample buffers (Airspy or HackRF) = 2^(17+1) for 2 byte samples
m_config.m_inputSampleRate = 96000;
m_config.m_inputFrequencyOffset = 0;
m_config.m_rfBandwidth = 100;
m_config.m_demodGain = 100;
m_config.m_fmDeviation = 100;
m_config.m_squelchGate = 5; // 10s of ms at 48000 Hz sample rate. Corresponds to 2400 for AGC attack
m_config.m_squelch = -30.0;
m_config.m_volume = 1.0;
m_config.m_audioMute = false;
m_config.m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate();
apply();
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_movingAverage.resize(16, 0);
DSPEngine::instance()->addAudioSink(&m_audioFifo);
}
DSDDemod::~DSDDemod()
{
DSPEngine::instance()->removeAudioSink(&m_audioFifo);
delete[] m_samplesBuffer;
}
void DSDDemod::configure(MessageQueue* messageQueue,
int rfBandwidth,
int demodGain,
int fmDeviation,
int volume,
int squelchGate,
Real squelch,
bool audioMute)
{
Message* cmd = MsgConfigureDSDDemod::create(rfBandwidth,
demodGain,
fmDeviation,
volume,
squelchGate,
squelch,
audioMute);
messageQueue->push(cmd);
}
void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
Complex ci;
m_settingsMutex.lock();
m_samplesBufferIndex = 0;
m_scopeSampleBuffer.clear();
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
qint16 sample;
m_magsq = ((ci.real()*ci.real() + ci.imag()*ci.imag())) / (Real) (1<<30);
m_movingAverage.feed(m_magsq);
Real demod = 32768.0f * m_phaseDiscri.phaseDiscriminator(ci) * ((float) m_running.m_demodGain / 100.0f);
m_sampleCount++;
// AF processing
if (getMagSq() > m_squelchLevel)
{
if (m_squelchCount < m_squelchGate)
{
m_squelchCount++;
}
}
else
{
m_squelchCount = 0;
}
m_squelchOpen = m_squelchCount == m_squelchGate;
if (m_squelchOpen)
{
sample = demod;
}
else
{
sample = 0;
}
Sample s(sample, 0.0);
m_scopeSampleBuffer.push_back(s);
m_samplesBuffer[m_samplesBufferIndex++] = sample;
// if (m_running.m_audioMute)
// {
// m_audioBuffer[m_audioBufferFill].l = 0;
// m_audioBuffer[m_audioBufferFill].r = 0;
// }
// else
// {
// m_audioBuffer[m_audioBufferFill].l = sample;
// m_audioBuffer[m_audioBufferFill].r = sample;
// }
//
// ++m_audioBufferFill;
//
// if (m_audioBufferFill >= m_audioBuffer.size())
// {
// uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10);
//
// if (res != m_audioBufferFill)
// {
// qDebug("DSDDemod::feed: %u/%u audio samples written", res, m_audioBufferFill);
// }
//
// m_audioBufferFill = 0;
// }
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
// if (m_audioBufferFill > 0)
// {
// uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10);
//
// if (res != m_audioBufferFill)
// {
// qDebug("NFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
// }
//
// m_audioBufferFill = 0;
// }
m_dsdDecoder.popAudioSamples(&m_audioFifo, m_running.m_audioMute);
m_dsdDecoder.pushSamples(m_samplesBuffer, m_samplesBufferIndex);
if ((m_scope != 0) && (m_scopeEnabled))
{
m_scope->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth
}
m_settingsMutex.unlock();
}
void DSDDemod::start()
{
m_audioFifo.clear();
m_phaseDiscri.reset();
m_dsdDecoder.start();
}
void DSDDemod::stop()
{
m_dsdDecoder.stop();
}
bool DSDDemod::handleMessage(const Message& cmd)
{
qDebug() << "DSDDemod::handleMessage";
if (Channelizer::MsgChannelizerNotification::match(cmd))
{
Channelizer::MsgChannelizerNotification& notif = (Channelizer::MsgChannelizerNotification&) cmd;
m_config.m_inputSampleRate = notif.getSampleRate();
m_config.m_inputFrequencyOffset = notif.getFrequencyOffset();
apply();
qDebug() << "DSDDemod::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << m_config.m_inputSampleRate
<< " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset;
return true;
}
else if (MsgConfigureDSDDemod::match(cmd))
{
MsgConfigureDSDDemod& cfg = (MsgConfigureDSDDemod&) cmd;
m_config.m_rfBandwidth = cfg.getRFBandwidth();
m_config.m_demodGain = cfg.getDemodGain();
m_config.m_fmDeviation = cfg.getFMDeviation();
m_config.m_volume = cfg.getVolume();
m_config.m_squelchGate = cfg.getSquelchGate();
m_config.m_squelch = cfg.getSquelch();
m_config.m_audioMute = cfg.getAudioMute();
apply();
qDebug() << "DSDDemod::handleMessage: MsgConfigureDSDDemod: m_rfBandwidth: " << m_config.m_rfBandwidth * 100
<< " m_demodGain: " << m_config.m_demodGain / 100.0
<< " m_fmDeviation: " << m_config.m_fmDeviation * 100
<< " m_volume: " << m_config.m_volume / 10.0
<< " m_squelchGate" << m_config.m_squelchGate
<< " m_squelch: " << m_config.m_squelch
<< " m_audioMute: " << m_config.m_audioMute;
return true;
}
else
{
return false;
}
}
void DSDDemod::apply()
{
if ((m_config.m_inputFrequencyOffset != m_running.m_inputFrequencyOffset) ||
(m_config.m_inputSampleRate != m_running.m_inputSampleRate))
{
m_nco.setFreq(-m_config.m_inputFrequencyOffset, m_config.m_inputSampleRate);
}
if ((m_config.m_inputSampleRate != m_running.m_inputSampleRate) ||
(m_config.m_rfBandwidth != m_running.m_rfBandwidth))
{
m_settingsMutex.lock();
m_interpolator.create(16, m_config.m_inputSampleRate, (m_config.m_rfBandwidth * 100) / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_config.m_inputSampleRate / (Real) m_config.m_audioSampleRate;
m_phaseDiscri.setFMScaling((float) m_config.m_rfBandwidth / (float) m_config.m_fmDeviation);
m_settingsMutex.unlock();
}
if (m_config.m_fmDeviation != m_running.m_fmDeviation)
{
m_phaseDiscri.setFMScaling((float) m_config.m_rfBandwidth / (float) m_config.m_fmDeviation);
}
if (m_config.m_squelchGate != m_running.m_squelchGate)
{
m_squelchGate = 480 * m_config.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
}
if (m_config.m_squelch != m_running.m_squelch)
{
// input is a value in tenths of dB
m_squelchLevel = std::pow(10.0, m_config.m_squelch / 100.0);
//m_squelchLevel *= m_squelchLevel;
}
m_running.m_inputSampleRate = m_config.m_inputSampleRate;
m_running.m_inputFrequencyOffset = m_config.m_inputFrequencyOffset;
m_running.m_rfBandwidth = m_config.m_rfBandwidth;
m_running.m_demodGain = m_config.m_demodGain;
m_running.m_fmDeviation = m_config.m_fmDeviation;
m_running.m_squelchGate = m_config.m_squelchGate;
m_running.m_squelch = m_config.m_squelch;
m_running.m_volume = m_config.m_volume;
m_running.m_audioSampleRate = m_config.m_audioSampleRate;
m_running.m_audioMute = m_config.m_audioMute;
}