1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-04-04 10:38:45 -04:00

Rx plugins: refactoring of classes (1)

This commit is contained in:
f4exb 2019-11-23 07:39:57 +01:00
parent acd0892536
commit 5b83b2a4a8
40 changed files with 3388 additions and 1959 deletions

View File

@ -3,6 +3,7 @@ project(demod)
add_subdirectory(demodam)
add_subdirectory(demodbfm)
add_subdirectory(demodnfm)
add_subdirectory(demodnfmtest)
add_subdirectory(demodssb)
add_subdirectory(udpsink)
add_subdirectory(demodwfm)

View File

@ -2,14 +2,18 @@ project(am)
set(am_SOURCES
amdemod.cpp
amdemodsettings.cpp
amdemodsettings.cpp
amdemodbaseband.cpp
amdemodsink.cpp
amdemodplugin.cpp
amdemodwebapiadapter.cpp
)
set(am_HEADERS
amdemod.h
amdemodsettings.h
amdemodsettings.h
amdemodbaseband.h
amdemodsink.h
amdemodplugin.h
amdemodwebapiadapter.h
)
@ -24,13 +28,12 @@ if(NOT SERVER_MODE)
${am_SOURCES}
amdemodgui.cpp
amdemodssbdialog.cpp
amdemodgui.ui
amdemodssb.ui
)
set(am_HEADERS
${am_HEADERS}
amdemodgui.h
amdemodgui.h
amdemodssbdialog.h
)
@ -50,8 +53,8 @@ add_library(${TARGET_NAME} SHARED
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)

View File

@ -22,6 +22,7 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
@ -31,19 +32,12 @@
#include "SWGChannelReport.h"
#include "SWGAMDemodReport.h"
#include "dsp/downchannelizer.h"
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/threadedbasebandsamplesink.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplemimo.h"
#include "dsp/fftfilt.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "util/stepfunctions.h"
MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message)
MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message)
const QString AMDemod::m_channelIdURI = "sdrangel.channel.amdemod";
const QString AMDemod::m_channelId = "AMDemod";
@ -52,45 +46,19 @@ const int AMDemod::m_udpBlockSize = 512;
AMDemod::AMDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_inputSampleRate(48000),
m_inputFrequencyOffset(0),
m_running(false),
m_squelchOpen(false),
m_squelchDelayLine(9600),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_volumeAGC(0.003),
m_syncAMAGC(12000, 0.1, 1e-2),
m_audioFifo(48000),
m_settingsMutex(QMutex::Recursive)
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_thread = new QThread(this);
m_basebandSink = new AMDemodBaseband();
m_basebandSink->moveToThread(m_thread);
m_magsq = 0.0;
applySettings(m_settings, true);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024);
SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024);
m_syncAMAGC.setThresholdEnable(false);
m_syncAMAGC.resize(12000, 6000, 0.1);
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
applySettings(m_settings, true);
m_channelizer = new DownChannelizer(this);
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
m_deviceAPI->addChannelSink(m_threadedChannelizer);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_pllFilt.create(101, m_audioSampleRate, 200.0);
m_pll.computeCoefficients(0.05, 0.707, 1000);
m_syncAMBuffIndex = 0;
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
@ -99,13 +67,10 @@ AMDemod::~AMDemod()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo);
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
delete m_threadedChannelizer;
delete m_channelizer;
delete DSBFilter;
delete SSBFilter;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
delete m_basebandSink;
delete m_thread;
}
uint32_t AMDemod::getNumberOfDeviceStreams() const
@ -116,226 +81,31 @@ uint32_t AMDemod::getNumberOfDeviceStreams() const
void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
Complex ci;
if (!m_running) {
return;
}
m_settingsMutex.lock();
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
if (m_audioBufferFill > 0)
{
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill)
{
qDebug("AMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
m_settingsMutex.unlock();
}
void AMDemod::processOneSample(Complex &ci)
{
Real re = ci.real() / SDR_RX_SCALEF;
Real im = ci.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
m_squelchDelayLine.write(magsq);
if (m_magsq < m_squelchLevel)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
else
{
if (m_squelchCount < m_audioSampleRate / 10) {
m_squelchCount++;
}
}
qint16 sample;
m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20);
if (m_squelchOpen && !m_settings.m_audioMute)
{
Real demod;
if (m_settings.m_pll)
{
std::complex<float> s(re, im);
s = m_pllFilt.filter(s);
m_pll.feed(s.real(), s.imag());
float yr = re * m_pll.getImag() - im * m_pll.getReal();
float yi = re * m_pll.getReal() + im * m_pll.getImag();
fftfilt::cmplx *sideband;
std::complex<float> cs(yr, yi);
int n_out;
if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
n_out = DSBFilter->runDSB(cs, &sideband, false);
} else {
n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false);
}
for (int i = 0; i < n_out; i++)
{
float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]);
fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue();
if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
m_syncAMBuff[i] = (z.real() + z.imag());
} else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) {
m_syncAMBuff[i] = (z.real() + z.imag());
} else {
m_syncAMBuff[i] = (z.real() + z.imag());
}
// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f;
// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) {
// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag());
// } else {
// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag());
// }
m_syncAMBuffIndex = 0;
}
m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0;
demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico
// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f);
// m_volumeAGC.feed(demod);
// demod /= (10.0*m_volumeAGC.getValue());
}
else
{
demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20));
m_volumeAGC.feed(demod);
demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue();
}
if (m_settings.m_bandpassEnable)
{
demod = m_bandpass.filter(demod);
demod /= 301.0f;
}
else
{
demod = m_lowpass.filter(demod);
}
Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate);
sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume;
}
else
{
sample = 0;
}
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);
if (res != m_audioBufferFill)
{
qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill);
m_audioFifo.clear();
}
m_audioBufferFill = 0;
}
m_basebandSink->feed(begin, end);
}
void AMDemod::start()
{
qDebug("AMDemod::start");
m_squelchCount = 0;
m_audioFifo.clear();
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
m_running = true;
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
m_basebandSink->reset();
m_thread->start();
}
void AMDemod::stop()
{
qDebug("AMDemod::stop");
m_running = false;
m_thread->exit();
m_thread->wait();
}
bool AMDemod::handleMessage(const Message& cmd)
{
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
{
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
qDebug() << "AMDemod::handleMessage: MsgChannelizerNotification:"
<< " inputSampleRate: " << notif.getSampleRate()
<< " inputFrequencyOffset: " << notif.getFrequencyOffset();
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
{
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
qDebug() << "AMDemod::handleMessage: MsgConfigureChannelizer:"
<< " sampleRate: " << cfg.getSampleRate()
<< " inputFrequencyOffset: " << cfg.getCenterFrequency();
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
cfg.getSampleRate(),
cfg.getCenterFrequency());
return true;
}
else if (MsgConfigureAMDemod::match(cmd))
if (MsgConfigureAMDemod::match(cmd))
{
MsgConfigureAMDemod& cfg = (MsgConfigureAMDemod&) cmd;
qDebug() << "AMDemod::handleMessage: MsgConfigureAMDemod";
@ -343,30 +113,16 @@ bool AMDemod::handleMessage(const Message& cmd)
return true;
}
else if (BasebandSampleSink::MsgThreadedSink::match(cmd))
{
BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd;
const QThread *thread = cfg.getThread();
qDebug("AMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread);
return true;
}
else if (DSPSignalNotification::match(cmd))
{
return true;
}
else if (DSPConfigureAudio::match(cmd))
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "AMDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
qDebug() << "AMDemod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
return true;
return true;
}
else
{
@ -374,65 +130,6 @@ bool AMDemod::handleMessage(const Message& cmd)
}
}
void AMDemod::applyAudioSampleRate(int sampleRate)
{
qDebug("AMDemod::applyAudioSampleRate: sampleRate: %d m_inputSampleRate: %d", sampleRate, m_inputSampleRate);
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
sampleRate, m_settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f);
m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth / 2.0f);
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/5);
DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate);
m_pllFilt.create(101, sampleRate, 200.0);
if (m_settings.m_pll) {
m_volumeAGC.resizeNew(sampleRate, 0.003);
} else {
m_volumeAGC.resizeNew(sampleRate/10, 0.003);
}
m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1);
m_pll.setSampleRate(sampleRate);
m_settingsMutex.unlock();
m_audioSampleRate = sampleRate;
}
void AMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
{
qDebug() << "AMDemod::applyChannelSettings:"
<< " inputSampleRate: " << inputSampleRate
<< " inputFrequencyOffset: " << inputFrequencyOffset
<< " m_audioSampleRate: " << m_audioSampleRate;
if ((m_inputFrequencyOffset != inputFrequencyOffset) ||
(m_inputSampleRate != inputSampleRate) || force)
{
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
}
if ((m_inputSampleRate != inputSampleRate) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
m_settingsMutex.unlock();
}
m_inputSampleRate = inputSampleRate;
m_inputFrequencyOffset = inputFrequencyOffset;
}
void AMDemod::applySettings(const AMDemodSettings& settings, bool force)
{
qDebug() << "AMDemod::applySettings:"
@ -455,66 +152,27 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force)
QList<QString> reverseAPIKeys;
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
(m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f);
m_lowpass.create(301, m_audioSampleRate, settings.m_rfBandwidth / 2.0f);
DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate);
m_settingsMutex.unlock();
if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) {
reverseAPIKeys.append("bandpassEnable");
}
if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((m_settings.m_squelch != settings.m_squelch) || force)
{
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) {
reverseAPIKeys.append("bandpassEnable");
}
if ((m_settings.m_squelch != settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
}
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);
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((m_settings.m_pll != settings.m_pll) || force)
{
if (settings.m_pll)
{
m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003);
m_syncAMBuffIndex = 0;
}
else
{
m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003);
}
reverseAPIKeys.append("pll");
reverseAPIKeys.append("syncAMOperation");
}
if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force)
{
m_syncAMBuffIndex = 0;
reverseAPIKeys.append("pll");
reverseAPIKeys.append("syncAMOperation");
}
@ -536,16 +194,17 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force)
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex);
// apply stream sample rate to itself
applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset);
}
reverseAPIKeys.append("streamIndex");
}
AMDemodBaseband::MsgConfigureAMDemodBaseband *msg = AMDemodBaseband::MsgConfigureAMDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
@ -602,13 +261,6 @@ int AMDemod::webapiSettingsPutPatch(
AMDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
{
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
m_audioSampleRate, settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
}
MsgConfigureAMDemod *msg = MsgConfigureAMDemod::create(settings, force);
m_inputMessageQueue.push(msg);
@ -744,9 +396,9 @@ void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
response.getAmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
response.getAmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0);
response.getAmDemodReport()->setAudioSampleRate(m_audioSampleRate);
response.getAmDemodReport()->setChannelSampleRate(m_inputSampleRate);
response.getAmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0);
response.getAmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
response.getAmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
void AMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force)

View File

@ -21,29 +21,18 @@
#include <vector>
#include <QNetworkRequest>
#include <QMutex>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "util/movingaverage.h"
#include "dsp/agc.h"
#include "dsp/bandpass.h"
#include "dsp/lowpass.h"
#include "dsp/phaselockcomplex.h"
#include "audio/audiofifo.h"
#include "util/message.h"
#include "util/doublebufferfifo.h"
#include "amdemodbaseband.h"
#include "amdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class DownChannelizer;
class ThreadedBasebandSampleSink;
class fftfilt;
class AMDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
@ -71,29 +60,6 @@ public:
{ }
};
class MsgConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getCenterFrequency() const { return m_centerFrequency; }
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
{
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
}
private:
int m_sampleRate;
int m_centerFrequency;
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
};
AMDemod(DeviceAPI *deviceAPI);
~AMDemod();
virtual void destroy() { delete this; }
@ -143,28 +109,14 @@ public:
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
double getMagSq() const { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); }
Real getPllFrequency() const { return m_pll.getFreq(); }
uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); }
double getMagSq() const { return m_basebandSink->getMagSq(); }
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
bool getPllLocked() const { return m_settings.m_pll && m_basebandSink->getPllLocked(); }
Real getPllFrequency() const { return m_basebandSink->getPllFrequency(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
uint32_t getNumberOfDeviceStreams() const;
@ -173,77 +125,21 @@ public:
static const QString m_channelId;
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
enum RateState {
RSInitialFill,
RSRunning
};
DeviceAPI *m_deviceAPI;
ThreadedBasebandSampleSink* m_threadedChannelizer;
DownChannelizer* m_channelizer;
int m_inputSampleRate;
int m_inputFrequencyOffset;
QThread *m_thread;
AMDemodBaseband* m_basebandSink;
AMDemodSettings m_settings;
uint32_t m_audioSampleRate;
bool m_running;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
Real m_squelchLevel;
uint32_t m_squelchCount;
bool m_squelchOpen;
DoubleBufferFIFO<Real> m_squelchDelayLine;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 16> m_movingAverage;
SimpleAGC<4800> m_volumeAGC;
Bandpass<Real> m_bandpass;
Lowpass<Real> m_lowpass;
Lowpass<std::complex<float> > m_pllFilt;
PhaseLockComplex m_pll;
fftfilt* DSBFilter;
fftfilt* SSBFilter;
Real m_syncAMBuff[2*1024];
uint32_t m_syncAMBuffIndex;
MagAGC m_syncAMAGC;
AudioVector m_audioBuffer;
uint32_t m_audioBufferFill;
AudioFifo m_audioFifo;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
static const int m_udpBlockSize;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QMutex m_settingsMutex;
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const AMDemodSettings& settings, bool force = false);
void applyAudioSampleRate(int sampleRate);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMDemodSettings& settings, bool force);
void processOneSample(Complex &ci);
private slots:
void networkManagerFinished(QNetworkReply *reply);

View File

@ -0,0 +1,172 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downsamplechannelizer.h"
#include "amdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(AMDemodBaseband::MsgConfigureAMDemodBaseband, Message)
AMDemodBaseband::AMDemodBaseband() :
m_mutex(QMutex::Recursive)
{
qDebug("AMDemodBaseband::AMDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownSampleChannelizer(&m_sink);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&AMDemodBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
AMDemodBaseband::~AMDemodBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
delete m_channelizer;
}
void AMDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void AMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.write(begin, end);
}
void AMDemodBaseband::handleData()
{
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void AMDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool AMDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureAMDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureAMDemodBaseband& cfg = (MsgConfigureAMDemodBaseband&) cmd;
qDebug() << "AMDemodBaseband::handleMessage: MsgConfigureAMDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "AMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
return true;
}
else
{
return false;
}
}
void AMDemodBaseband::applySettings(const AMDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
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_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_sink.getAudioSampleRate() != audioSampleRate)
{
m_sink.applyAudioSampleRate(audioSampleRate);
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int AMDemodBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void AMDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
}

View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AMDEMODBASEBAND_H
#define INCLUDE_AMDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "amdemodsink.h"
class DownSampleChannelizer;
class AMDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureAMDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AMDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAMDemodBaseband* create(const AMDemodSettings& settings, bool force)
{
return new MsgConfigureAMDemodBaseband(settings, force);
}
private:
AMDemodSettings m_settings;
bool m_force;
MsgConfigureAMDemodBaseband(const AMDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
AMDemodBaseband();
~AMDemodBaseband();
void reset();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); }
void setBasebandSampleRate(int sampleRate);
double getMagSq() const { return m_sink.getMagSq(); }
bool getPllLocked() const { return m_sink.getPllLocked(); }
Real getPllFrequency() const { return m_sink.getPllFrequency(); }
private:
SampleSinkFifo m_sampleFifo;
DownSampleChannelizer *m_channelizer;
AMDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
AMDemodSettings m_settings;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const AMDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_AMDEMODBASEBAND_H

View File

@ -325,11 +325,6 @@ void AMDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AMDemod::MsgConfigureChannelizer* channelConfigMsg = AMDemod::MsgConfigureChannelizer::create(
m_amDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency());
m_amDemod->getInputMessageQueue()->push(channelConfigMsg);
AMDemod::MsgConfigureAMDemod* message = AMDemod::MsgConfigureAMDemod::create( m_settings, force);
m_amDemod->getInputMessageQueue()->push(message);
}

View File

@ -10,7 +10,7 @@
const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = {
QString("AM Demodulator"),
QString("4.11.6"),
QString("4.12.2"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,

View File

@ -0,0 +1,320 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <QDebug>
#include <complex.h>
#include "audio/audiooutput.h"
#include "dsp/fftfilt.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "amdemodsink.h"
AMDemodSink::AMDemodSink() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_squelchOpen(false),
m_squelchDelayLine(9600),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_volumeAGC(0.003),
m_syncAMAGC(12000, 0.1, 1e-2),
m_audioFifo(48000)
{
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_magsq = 0.0;
DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024);
SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024);
m_syncAMAGC.setThresholdEnable(false);
m_syncAMAGC.resize(12000, 6000, 0.1);
m_pllFilt.create(101, m_audioSampleRate, 200.0);
m_pll.computeCoefficients(0.05, 0.707, 1000);
m_syncAMBuffIndex = 0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
AMDemodSink::~AMDemodSink()
{
delete DSBFilter;
delete SSBFilter;
}
void AMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
if (m_audioBufferFill > 0)
{
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
if (res != m_audioBufferFill) {
qDebug("AMDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
}
void AMDemodSink::processOneSample(Complex &ci)
{
Real re = ci.real() / SDR_RX_SCALEF;
Real im = ci.imag() / SDR_RX_SCALEF;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
m_squelchDelayLine.write(magsq);
if (m_magsq < m_squelchLevel)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
else
{
if (m_squelchCount < m_audioSampleRate / 10) {
m_squelchCount++;
}
}
qint16 sample;
m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20);
if (m_squelchOpen && !m_settings.m_audioMute)
{
Real demod;
if (m_settings.m_pll)
{
std::complex<float> s(re, im);
s = m_pllFilt.filter(s);
m_pll.feed(s.real(), s.imag());
float yr = re * m_pll.getImag() - im * m_pll.getReal();
float yi = re * m_pll.getReal() + im * m_pll.getImag();
fftfilt::cmplx *sideband;
std::complex<float> cs(yr, yi);
int n_out;
if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
n_out = DSBFilter->runDSB(cs, &sideband, false);
} else {
n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false);
}
for (int i = 0; i < n_out; i++)
{
float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]);
fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue();
if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) {
m_syncAMBuff[i] = (z.real() + z.imag());
} else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) {
m_syncAMBuff[i] = (z.real() + z.imag());
} else {
m_syncAMBuff[i] = (z.real() + z.imag());
}
m_syncAMBuffIndex = 0;
}
m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0;
demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico
}
else
{
demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20));
m_volumeAGC.feed(demod);
demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue();
}
if (m_settings.m_bandpassEnable)
{
demod = m_bandpass.filter(demod);
demod /= 301.0f;
}
else
{
demod = m_lowpass.filter(demod);
}
Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate);
sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume;
}
else
{
sample = 0;
}
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);
if (res != m_audioBufferFill)
{
qDebug("AMDemodSink::processOneSample: %u/%u audio samples written", res, m_audioBufferFill);
m_audioFifo.clear();
}
m_audioBufferFill = 0;
}
}
void AMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "AMDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset
<< " m_audioSampleRate: " << m_audioSampleRate;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void AMDemodSink::applySettings(const AMDemodSettings& settings, bool force)
{
qDebug() << "AMDemodSink::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_audioMute: " << settings.m_audioMute
<< " m_bandpassEnable: " << settings.m_bandpassEnable
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_pll: " << settings.m_pll
<< " m_syncAMOperation: " << (int) settings.m_syncAMOperation
<< " force: " << force;
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
(m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f);
m_lowpass.create(301, m_audioSampleRate, settings.m_rfBandwidth / 2.0f);
DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate);
}
if ((m_settings.m_squelch != settings.m_squelch) || force) {
m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch);
}
if ((m_settings.m_pll != settings.m_pll) || force)
{
if (settings.m_pll)
{
m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003);
m_syncAMBuffIndex = 0;
}
else
{
m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003);
}
}
if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) {
m_syncAMBuffIndex = 0;
}
m_settings = settings;
}
void AMDemodSink::applyAudioSampleRate(int sampleRate)
{
qDebug("AMDemodSink::applyAudioSampleRate: sampleRate: %d m_channelSampleRate: %d", sampleRate, m_channelSampleRate);
m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate;
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f);
m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth / 2.0f);
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/5);
DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate);
m_pllFilt.create(101, sampleRate, 200.0);
if (m_settings.m_pll) {
m_volumeAGC.resizeNew(sampleRate, 0.003);
} else {
m_volumeAGC.resizeNew(sampleRate/10, 0.003);
}
m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1);
m_pll.setSampleRate(sampleRate);
m_audioSampleRate = sampleRate;
}

View File

@ -0,0 +1,127 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AMDEMODSINK_H
#define INCLUDE_AMDEMODSINK_H
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/agc.h"
#include "dsp/bandpass.h"
#include "dsp/lowpass.h"
#include "dsp/phaselockcomplex.h"
#include "audio/audiofifo.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "amdemodsettings.h"
class fftfilt;
class AMDemodSink : public ChannelSampleSink {
public:
AMDemodSink();
~AMDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const AMDemodSettings& settings, bool force = false);
void applyAudioSampleRate(int sampleRate);
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
double getMagSq() const { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); }
Real getPllFrequency() const { return m_pll.getFreq(); }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
enum RateState {
RSInitialFill,
RSRunning
};
int m_channelSampleRate;
int m_channelFrequencyOffset;
AMDemodSettings m_settings;
uint32_t m_audioSampleRate;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
Real m_squelchLevel;
uint32_t m_squelchCount;
bool m_squelchOpen;
DoubleBufferFIFO<Real> m_squelchDelayLine;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 16> m_movingAverage;
SimpleAGC<4800> m_volumeAGC;
Bandpass<Real> m_bandpass;
Lowpass<Real> m_lowpass;
Lowpass<std::complex<float> > m_pllFilt;
PhaseLockComplex m_pll;
fftfilt* DSBFilter;
fftfilt* SSBFilter;
Real m_syncAMBuff[2*1024];
uint32_t m_syncAMBuffIndex;
MagAGC m_syncAMAGC;
AudioVector m_audioBuffer;
AudioFifo m_audioFifo;
uint32_t m_audioBufferFill;
void processOneSample(Complex &ci);
};
#endif // INCLUDE_AMDEMODSINK_H

View File

@ -5,6 +5,8 @@ set(dsddemod_SOURCES
dsddemodplugin.cpp
dsddemodbaudrates.cpp
dsddemodsettings.cpp
dsddemodsink.cpp
dsddemodbaseband.cpp
dsddemodwebapiadapter.cpp
dsddecoder.cpp
)
@ -14,6 +16,8 @@ set(dsddemod_HEADERS
dsddemodplugin.h
dsddemodbaudrates.h
dsddemodsettings.h
dsddemodsink.h
dsddemodbaseband.h
dsddemodwebapiadapter.h
dsddecoder.h
)
@ -29,7 +33,6 @@ if(NOT SERVER_MODE)
${dsddemod_SOURCES}
dsddemodgui.cpp
dsdstatustextdialog.cpp
dsddemodgui.ui
dsdstatustextdialog.ui
)
@ -59,12 +62,12 @@ if(ENABLE_EXTERNAL_LIBRARIES)
endif()
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${LIBDSDCC_LIBRARIES}
${LIBMBE_LIBRARY}
${LIBDSDCC_LIBRARIES}
${LIBMBE_LIBRARY}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -26,6 +26,7 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include "SWGChannelSettings.h"
#include "SWGDSDDemodSettings.h"
@ -33,20 +34,14 @@
#include "SWGDSDDemodReport.h"
#include "SWGRDSReport.h"
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/threadedbasebandsamplesink.h"
#include "dsp/downchannelizer.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "dsddemod.h"
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message)
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureMyPosition, Message)
const QString DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod";
const QString DSDDemod::m_channelId = "DSDDemod";
@ -55,50 +50,17 @@ const int DSDDemod::m_udpBlockSize = 512;
DSDDemod::DSDDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_inputSampleRate(48000),
m_inputFrequencyOffset(0),
m_interpolatorDistance(0.0f),
m_interpolatorDistanceRemain(0.0f),
m_sampleCount(0),
m_squelchCount(0),
m_squelchGate(0),
m_squelchLevel(1e-4),
m_squelchOpen(false),
m_squelchDelayLine(24000),
m_audioFifo1(48000),
m_audioFifo2(48000),
m_scopeXY(0),
m_scopeEnabled(true),
m_dsdDecoder(),
m_signalFormat(signalFormatNone),
m_settingsMutex(QMutex::Recursive)
m_basebandSampleRate(0)
{
qDebug("DSDDemod::DSDDemod");
setObjectName(m_channelId);
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_thread = new QThread(this);
m_basebandSink = new DSDDemodBaseband();
m_basebandSink->moveToThread(m_thread);
m_sampleBuffer = new FixReal[1<<17]; // 128 kS
m_sampleBufferIndex = 0;
m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8);
m_magsq = 0.0f;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo1, getInputMessageQueue());
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo2, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
applySettings(m_settings, true);
m_channelizer = new DownChannelizer(this);
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
m_deviceAPI->addChannelSink(m_threadedChannelizer);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
@ -107,20 +69,10 @@ DSDDemod::~DSDDemod()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
delete[] m_sampleBuffer;
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo1);
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo2);
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
delete m_threadedChannelizer;
delete m_channelizer;
}
void DSDDemod::configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude)
{
Message* cmd = MsgConfigureMyPosition::create(myLatitude, myLongitude);
messageQueue->push(cmd);
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
delete m_basebandSink;
delete m_thread;
}
uint32_t DSDDemod::getNumberOfDeviceStreams() const
@ -131,269 +83,33 @@ uint32_t DSDDemod::getNumberOfDeviceStreams() const
void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
Complex ci;
int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol();
m_settingsMutex.lock();
m_scopeSampleBuffer.clear();
m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
FixReal sample, delayedSample;
qint16 sampleDSD;
Real re = ci.real() / SDR_RX_SCALED;
Real im = ci.imag() / SDR_RX_SCALED;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0]
m_sampleCount++;
// AF processing
if (m_movingAverage.asDouble() > m_squelchLevel)
{
if (m_squelchGate > 0)
{
if (m_squelchCount < m_squelchGate*2) {
m_squelchCount++;
}
m_squelchDelayLine.write(demod);
m_squelchOpen = m_squelchCount > m_squelchGate;
}
else
{
m_squelchOpen = true;
}
}
else
{
if (m_squelchGate > 0)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
m_squelchDelayLine.write(0);
m_squelchOpen = m_squelchCount > m_squelchGate;
}
else
{
m_squelchOpen = false;
}
}
if (m_squelchOpen)
{
if (m_squelchGate > 0)
{
sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples
sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size
}
else
{
sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples
sample = demod * SDR_RX_SCALEF; // scale to sample size
}
}
else
{
sampleDSD = 0;
sample = 0;
}
m_dsdDecoder.pushSample(sampleDSD);
if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod
sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort;
}
if (m_sampleBufferIndex < (1<<17)-1) {
m_sampleBufferIndex++;
} else {
m_sampleBufferIndex = 0;
}
m_sampleBuffer[m_sampleBufferIndex] = sample;
if (m_sampleBufferIndex < samplesPerSymbol) {
delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap
} else {
delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol];
}
if (m_settings.m_syncOrConstellation)
{
Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84);
m_scopeSampleBuffer.push_back(s);
}
else
{
Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate)
m_scopeSampleBuffer.push_back(s);
}
if (DSPEngine::instance()->hasDVSerialSupport())
{
if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1())
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_dsdDecoder.getMbeDVFrame1(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo1);
}
m_dsdDecoder.resetMbeDV1();
}
if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2())
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_dsdDecoder.getMbeDVFrame2(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo2);
}
m_dsdDecoder.resetMbeDV2();
}
}
// if (DSPEngine::instance()->hasDVSerialSupport() && m_dsdDecoder.mbeDVReady1())
// {
// if (!m_settings.m_audioMute)
// {
// DSPEngine::instance()->pushMbeFrame(m_dsdDecoder.getMbeDVFrame1(), m_dsdDecoder.getMbeRateIndex(), m_settings.m_volume, &m_audioFifo1);
// }
//
// m_dsdDecoder.resetMbeDV1();
// }
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
if (!DSPEngine::instance()->hasDVSerialSupport())
{
if (m_settings.m_slot1On)
{
int nbAudioSamples;
short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples);
if (nbAudioSamples > 0)
{
if (!m_settings.m_audioMute) {
m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples);
}
m_dsdDecoder.resetAudio1();
}
}
if (m_settings.m_slot2On)
{
int nbAudioSamples;
short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples);
if (nbAudioSamples > 0)
{
if (!m_settings.m_audioMute) {
m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples);
}
m_dsdDecoder.resetAudio2();
}
}
// int nbAudioSamples;
// short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples);
//
// if (nbAudioSamples > 0)
// {
// if (!m_settings.m_audioMute) {
// uint res = m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples, 10);
// }
//
// m_dsdDecoder.resetAudio1();
// }
}
if ((m_scopeXY != 0) && (m_scopeEnabled))
{
m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth
}
m_settingsMutex.unlock();
m_basebandSink->feed(begin, end);
}
void DSDDemod::start()
{
m_audioFifo1.clear();
m_audioFifo2.clear();
m_phaseDiscri.reset();
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
qDebug() << "DSDDemod::start";
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
m_basebandSink->reset();
m_thread->start();
}
void DSDDemod::stop()
{
qDebug() << "DSDDemod::stop";
m_thread->exit();
m_thread->wait();
}
bool DSDDemod::handleMessage(const Message& cmd)
{
qDebug() << "DSDDemod::handleMessage";
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
{
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
qDebug() << "DSDDemod::handleMessage: MsgChannelizerNotification: inputSampleRate: " << notif.getSampleRate()
<< " inputFrequencyOffset: " << notif.getFrequencyOffset();
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
{
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
qDebug("DSDDemod::handleMessage: MsgConfigureChannelizer");
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
cfg.getSampleRate(),
cfg.getCenterFrequency());
return true;
}
else if (MsgConfigureDSDDemod::match(cmd))
if (MsgConfigureDSDDemod::match(cmd))
{
MsgConfigureDSDDemod& cfg = (MsgConfigureDSDDemod&) cmd;
qDebug("DSDDemod::handleMessage: MsgConfigureDSDDemod: m_rfBandwidth");
@ -402,33 +118,16 @@ bool DSDDemod::handleMessage(const Message& cmd)
return true;
}
else if (MsgConfigureMyPosition::match(cmd))
{
MsgConfigureMyPosition& cfg = (MsgConfigureMyPosition&) cmd;
m_dsdDecoder.setMyPoint(cfg.getMyLatitude(), cfg.getMyLongitude());
return true;
}
else if (DSPConfigureAudio::match(cmd))
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
uint32_t sampleRate = cfg.getSampleRate();
qDebug() << "DSDDemod::handleMessage: DSPConfigureAudio:"
<< " sampleRate: " << sampleRate;
if (sampleRate != m_audioSampleRate) {
applyAudioSampleRate(sampleRate);
}
return true;
}
else if (BasebandSampleSink::MsgThreadedSink::match(cmd))
{
return true;
}
else if (DSPSignalNotification::match(cmd))
{
return true;
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "DSDDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
return true;
}
else
{
@ -436,45 +135,6 @@ bool DSDDemod::handleMessage(const Message& cmd)
}
}
void DSDDemod::applyAudioSampleRate(int sampleRate)
{
int upsampling = sampleRate / 8000;
qDebug("DSDDemod::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling);
if (sampleRate % 8000 != 0) {
qDebug("DSDDemod::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s");
}
m_dsdDecoder.setUpsampling(upsampling);
m_audioSampleRate = sampleRate;
}
void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
{
qDebug() << "DSDDemod::applyChannelSettings:"
<< " inputSampleRate: " << inputSampleRate
<< " inputFrequencyOffset: " << inputFrequencyOffset;
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
(inputSampleRate != m_inputSampleRate) || force)
{
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
}
if ((inputSampleRate != m_inputSampleRate) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, inputSampleRate, (m_settings.m_rfBandwidth) / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) inputSampleRate / (Real) 48000;
m_settingsMutex.unlock();
}
m_inputSampleRate = inputSampleRate;
m_inputFrequencyOffset = inputFrequencyOffset;
}
void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
{
qDebug() << "DSDDemod::applySettings: "
@ -527,87 +187,38 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
if ((settings.m_traceLengthMutliplier != m_settings.m_traceLengthMutliplier) || force) {
reverseAPIKeys.append("traceLengthMutliplier");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) 48000;
//m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation);
m_settingsMutex.unlock();
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation));
}
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force)
{
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) {
reverseAPIKeys.append("squelchGate");
m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_squelch != m_settings.m_squelch) || force)
{
if ((settings.m_squelch != m_settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
// input is a value in dB
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
}
if ((settings.m_volume != m_settings.m_volume) || force)
{
if ((settings.m_volume != m_settings.m_volume) || force) {
reverseAPIKeys.append("volume");
m_dsdDecoder.setAudioGain(settings.m_volume);
}
if ((settings.m_baudRate != m_settings.m_baudRate) || force)
{
if ((settings.m_baudRate != m_settings.m_baudRate) || force) {
reverseAPIKeys.append("baudRate");
m_dsdDecoder.setBaudRate(settings.m_baudRate);
}
if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force)
{
if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) {
reverseAPIKeys.append("enableCosineFiltering");
m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering);
}
if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force)
{
if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) {
reverseAPIKeys.append("tdmaStereo");
m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo);
}
if ((settings.m_pllLock != m_settings.m_pllLock) || force)
{
if ((settings.m_pllLock != m_settings.m_pllLock) || force) {
reverseAPIKeys.append("pllLock");
m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock);
}
if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force)
{
if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) {
reverseAPIKeys.append("highPassFilter");
m_dsdDecoder.useHPMbelib(settings.m_highPassFilter);
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
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_audioFifo1, getInputMessageQueue(), audioDeviceIndex);
audioDeviceManager->addAudioSink(&m_audioFifo2, getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_audioSampleRate != audioSampleRate) {
applyAudioSampleRate(audioSampleRate);
}
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
@ -615,16 +226,17 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex);
// apply stream sample rate to itself
applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset);
}
reverseAPIKeys.append("streamIndex");
}
DSDDemodBaseband::MsgConfigureDSDDemodBaseband *msg = DSDDemodBaseband::MsgConfigureDSDDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
@ -660,195 +272,6 @@ bool DSDDemod::deserialize(const QByteArray& data)
}
}
const char *DSDDemod::updateAndGetStatusText()
{
formatStatusText();
return m_formatStatusText;
}
void DSDDemod::formatStatusText()
{
switch (getDecoder().getSyncType())
{
case DSDcc::DSDDecoder::DSDSyncDMRDataMS:
case DSDcc::DSDDecoder::DSDSyncDMRDataP:
case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS:
case DSDcc::DSDDecoder::DSDSyncDMRVoiceP:
if (m_signalFormat != signalFormatDMR)
{
strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________");
}
switch (getDecoder().getStationType())
{
case DSDcc::DSDDecoder::DSDBaseStation:
memcpy(&m_formatStatusText[5], "BS ", 3);
break;
case DSDcc::DSDDecoder::DSDMobileStation:
memcpy(&m_formatStatusText[5], "MS ", 3);
break;
default:
memcpy(&m_formatStatusText[5], "NA ", 3);
break;
}
memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26);
memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26);
m_signalFormat = signalFormatDMR;
break;
case DSDcc::DSDDecoder::DSDSyncDStarHeaderN:
case DSDcc::DSDDecoder::DSDSyncDStarHeaderP:
case DSDcc::DSDDecoder::DSDSyncDStarN:
case DSDcc::DSDDecoder::DSDSyncDStarP:
if (m_signalFormat != signalFormatDStar)
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._");
// MY UR RPT1 RPT2 Info Loc Target
}
{
const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1();
const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2();
const std::string& mySign = getDecoder().getDStarDecoder().getMySign();
const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign();
if (rpt1.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[23], rpt1.c_str(), 8);
}
if (rpt2.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[32], rpt2.c_str(), 8);
}
if (yrSign.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[14], yrSign.c_str(), 8);
}
if (mySign.length() > 0) { // 0 or 13
memcpy(&m_formatStatusText[0], mySign.c_str(), 13);
}
memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20);
memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6);
snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f",
getDecoder().getDStarDecoder().getBearing(),
getDecoder().getDStarDecoder().getDistance());
}
m_formatStatusText[82] = '\0';
m_signalFormat = signalFormatDStar;
break;
case DSDcc::DSDDecoder::DSDSyncDPMR:
snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d",
DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()],
getDecoder().getDPMRDecoder().getColorCode(),
getDecoder().getDPMRDecoder().getOwnId(),
getDecoder().getDPMRDecoder().getCalledId());
m_signalFormat = signalFormatDPMR;
break;
case DSDcc::DSDDecoder::DSDSyncNXDNP:
case DSDcc::DSDDecoder::DSDSyncNXDNN:
if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH)
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// RC r cc mm llllll ssss
snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X",
getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H",
getDecoder().getNXDNDecoder().getRAN(),
getDecoder().getNXDNDecoder().getMessageType(),
getDecoder().getNXDNDecoder().getLocationId(),
getDecoder().getNXDNDecoder().getServicesFlag());
}
else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH)
|| (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH))
{
if (getDecoder().getNXDNDecoder().isIdle()) {
snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr());
}
else
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// Rx r cc mm sssss>gddddd
snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d",
getDecoder().getNXDNDecoder().getRFChannelStr(),
getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H",
getDecoder().getNXDNDecoder().getRAN(),
getDecoder().getNXDNDecoder().getMessageType(),
getDecoder().getNXDNDecoder().getSourceId(),
getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I',
getDecoder().getNXDNDecoder().getDestinationId());
}
}
else
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// RU
snprintf(m_formatStatusText, 82, "RU");
}
m_signalFormat = signalFormatNXDN;
break;
case DSDcc::DSDDecoder::DSDSyncYSF:
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444
if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError)
{
snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]);
}
else
{
snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError());
}
snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c",
DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()],
DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()],
getDecoder().getYSFDecoder().getFICH().getBlockTotal(),
getDecoder().getYSFDecoder().getFICH().getFrameTotal(),
(getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'),
(getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L'));
if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled())
{
snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode());
}
else
{
strncpy(&m_formatStatusText[14], "---", 82-14);
}
char dest[13];
if (getDecoder().getYSFDecoder().radioIdMode())
{
snprintf(dest, 12, "%-5s:%-5s",
getDecoder().getYSFDecoder().getDestId(),
getDecoder().getYSFDecoder().getSrcId());
}
else
{
snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest());
}
snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s",
getDecoder().getYSFDecoder().getSrc(),
dest,
getDecoder().getYSFDecoder().getUplink(),
getDecoder().getYSFDecoder().getDownlink(),
getDecoder().getYSFDecoder().getRem4());
m_signalFormat = signalFormatYSF;
break;
default:
m_signalFormat = signalFormatNone;
m_formatStatusText[0] = '\0';
break;
}
m_formatStatusText[82] = '\0'; // guard
}
int DSDDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
@ -870,13 +293,6 @@ int DSDDemod::webapiSettingsPutPatch(
DSDDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
{
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
m_audioSampleRate, settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
}
MsgConfigureDSDDemod *msg = MsgConfigureDSDDemod::create(settings, force);
m_inputMessageQueue.push(msg);
@ -1051,9 +467,9 @@ void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
response.getDsdDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
response.getDsdDemodReport()->setAudioSampleRate(m_audioSampleRate);
response.getDsdDemodReport()->setChannelSampleRate(m_inputSampleRate);
response.getDsdDemodReport()->setSquelch(m_squelchOpen ? 1 : 0);
response.getDsdDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
response.getDsdDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
response.getDsdDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0);
response.getDsdDemodReport()->setPllLocked(getDecoder().getSymbolPLLLocked() ? 1 : 0);
response.getDsdDemodReport()->setSlot1On(getDecoder().getVoice1On() ? 1 : 0);
response.getDsdDemodReport()->setSlot2On(getDecoder().getVoice2On() ? 1 : 0);

View File

@ -21,31 +21,20 @@
#include <vector>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/bandpass.h"
#include "dsp/afsquelch.h"
#include "util/movingaverage.h"
#include "dsp/afsquelch.h"
#include "audio/audiofifo.h"
#include "util/message.h"
#include "util/doublebufferfifo.h"
#include "dsddemodsettings.h"
#include "dsddecoder.h"
#include "dsddemodbaseband.h"
class QNetworkAccessManager;
class QNetworkReply;
class DeviceAPI;
class ThreadedBasebandSampleSink;
class DownChannelizer;
class QThread;
class DownSampleChannelizer;
class DSDDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
@ -73,35 +62,9 @@ public:
{ }
};
class MsgConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getCenterFrequency() const { return m_centerFrequency; }
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
{
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
}
private:
int m_sampleRate;
int m_centerFrequency;
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
};
DSDDemod(DeviceAPI *deviceAPI);
~DSDDemod();
virtual void destroy() { delete this; }
void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; }
void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude);
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
@ -125,31 +88,6 @@ public:
return m_settings.m_inputFrequencyOffset;
}
double getMagSq() { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
const DSDDecoder& getDecoder() const { return m_dsdDecoder; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
const char *updateAndGetStatusText();
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
@ -174,115 +112,30 @@ public:
SWGSDRangel::SWGChannelSettings& response);
uint32_t getNumberOfDeviceStreams() const;
void setScopeXYSink(BasebandSampleSink* sampleSink) { m_basebandSink->setScopeXYSink(sampleSink); }
void configureMyPosition(float myLatitude, float myLongitude) { m_basebandSink->configureMyPosition(myLatitude, myLongitude); }
double getMagSq() { return m_basebandSink->getMagSq(); }
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
const DSDDecoder& getDecoder() const { return m_basebandSink->getDecoder(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); }
const char *updateAndGetStatusText() { return m_basebandSink->updateAndGetStatusText(); }
static const QString m_channelIdURI;
static const QString m_channelId;
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
typedef enum
{
signalFormatNone,
signalFormatDMR,
signalFormatDStar,
signalFormatDPMR,
signalFormatYSF,
signalFormatNXDN
} SignalFormat; //!< Used for status text formatting
class MsgConfigureMyPosition : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getMyLatitude() const { return m_myLatitude; }
float getMyLongitude() const { return m_myLongitude; }
static MsgConfigureMyPosition* create(float myLatitude, float myLongitude)
{
return new MsgConfigureMyPosition(myLatitude, myLongitude);
}
private:
float m_myLatitude;
float m_myLongitude;
MsgConfigureMyPosition(float myLatitude, float myLongitude) :
m_myLatitude(myLatitude),
m_myLongitude(myLongitude)
{}
};
enum RateState {
RSInitialFill,
RSRunning
};
DeviceAPI *m_deviceAPI;
ThreadedBasebandSampleSink* m_threadedChannelizer;
DownChannelizer* m_channelizer;
int m_inputSampleRate;
int m_inputFrequencyOffset;
QThread *m_thread;
DSDDemodBaseband *m_basebandSink;
DSDDemodSettings m_settings;
quint32 m_audioSampleRate;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
int m_sampleCount;
int m_squelchCount;
int m_squelchGate;
double m_squelchLevel;
bool m_squelchOpen;
DoubleBufferFIFO<Real> m_squelchDelayLine;
MovingAverageUtil<Real, double, 16> m_movingAverage;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
SampleVector m_scopeSampleBuffer;
AudioVector m_audioBuffer;
uint m_audioBufferFill;
FixReal *m_sampleBuffer; //!< samples ring buffer
int m_sampleBufferIndex;
int m_scaleFromShort;
AudioFifo m_audioFifo1;
AudioFifo m_audioFifo2;
BasebandSampleSink* m_scopeXY;
bool m_scopeEnabled;
DSDDecoder m_dsdDecoder;
char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text
SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type
PhaseDiscriminators m_phaseDiscri;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QMutex m_settingsMutex;
static const int m_udpBlockSize;
void applyAudioSampleRate(int sampleRate);
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const DSDDemodSettings& settings, bool force = false);
void formatStatusText();
void applySettings(const DSDDemodSettings& settings, bool force = false);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force);

View File

@ -0,0 +1,174 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downsamplechannelizer.h"
#include "dsddemodbaseband.h"
MESSAGE_CLASS_DEFINITION(DSDDemodBaseband::MsgConfigureDSDDemodBaseband, Message)
DSDDemodBaseband::DSDDemodBaseband() :
m_mutex(QMutex::Recursive)
{
qDebug("DSDDemodBaseband::DSDDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownSampleChannelizer(&m_sink);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&DSDDemodBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo1(), getInputMessageQueue());
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue());
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
DSDDemodBaseband::~DSDDemodBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo1());
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo2());
delete m_channelizer;
}
void DSDDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void DSDDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void DSDDemodBaseband::handleData()
{
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void DSDDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool DSDDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureDSDDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureDSDDemodBaseband& cfg = (MsgConfigureDSDDemodBaseband&) cmd;
qDebug() << "DSDDemodBaseband::handleMessage: MsgConfigureDSDDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "DSDDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
return true;
}
else
{
return false;
}
}
void DSDDemodBaseband::applySettings(const DSDDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
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_sink.getAudioFifo1(), getInputMessageQueue(), audioDeviceIndex);
audioDeviceManager->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_sink.getAudioSampleRate() != audioSampleRate)
{
m_sink.applyAudioSampleRate(audioSampleRate);
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int DSDDemodBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void DSDDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
}

View File

@ -0,0 +1,91 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSDDEMODBASEBAND_H
#define INCLUDE_DSDDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "dsddemodsink.h"
class DownSampleChannelizer;
class DSDDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureDSDDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DSDDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureDSDDemodBaseband* create(const DSDDemodSettings& settings, bool force)
{
return new MsgConfigureDSDDemodBaseband(settings, force);
}
private:
DSDDemodSettings m_settings;
bool m_force;
MsgConfigureDSDDemodBaseband(const DSDDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
DSDDemodBaseband();
~DSDDemodBaseband();
void reset();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;
unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); }
double getMagSq() { return m_sink.getMagSq(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
void setBasebandSampleRate(int sampleRate);
void setScopeXYSink(BasebandSampleSink* scopeSink) { m_sink.setScopeXYSink(scopeSink); }
void configureMyPosition(float myLatitude, float myLongitude) { m_sink.configureMyPosition(myLatitude, myLongitude); }
const DSDDecoder& getDecoder() const { return m_sink.getDecoder(); }
const char *updateAndGetStatusText() { return m_sink.updateAndGetStatusText(); }
private:
SampleSinkFifo m_sampleFifo;
DownSampleChannelizer *m_channelizer;
DSDDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
DSDDemodSettings m_settings;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const DSDDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_DSDDEMODBASEBAND_H

View File

@ -417,7 +417,7 @@ void DSDDemodGUI::updateMyPosition()
if ((m_myLatitude != latitude) || (m_myLongitude != longitude))
{
m_dsdDemod->configureMyPosition(m_dsdDemod->getInputMessageQueue(), latitude, longitude);
m_dsdDemod->configureMyPosition(latitude, longitude);
m_myLatitude = latitude;
m_myLongitude = longitude;
}
@ -500,10 +500,6 @@ void DSDDemodGUI::applySettings(bool force)
{
qDebug() << "DSDDemodGUI::applySettings";
DSDDemod::MsgConfigureChannelizer* channelConfigMsg = DSDDemod::MsgConfigureChannelizer::create(
48000, m_channelMarker.getCenterFrequency());
m_dsdDemod->getInputMessageQueue()->push(channelConfigMsg);
DSDDemod::MsgConfigureDSDDemod* message = DSDDemod::MsgConfigureDSDDemod::create( m_settings, force);
m_dsdDemod->getInputMessageQueue()->push(message);
}

View File

@ -0,0 +1,595 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <string.h>
#include <stdio.h>
#include <complex.h>
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGChannelSettings.h"
#include "SWGDSDDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGDSDDemodReport.h"
#include "SWGRDSReport.h"
#include "dsp/dspengine.h"
#include "dsp/basebandsamplesink.h"
#include "audio/audiooutput.h"
#include "util/db.h"
#include "dsddemodsink.h"
DSDDemodSink::DSDDemodSink() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_interpolatorDistance(0.0f),
m_interpolatorDistanceRemain(0.0f),
m_sampleCount(0),
m_squelchCount(0),
m_squelchGate(0),
m_squelchLevel(1e-4),
m_squelchOpen(false),
m_squelchDelayLine(24000),
m_audioFifo1(48000),
m_audioFifo2(48000),
m_scopeXY(0),
m_scopeEnabled(true),
m_dsdDecoder(),
m_signalFormat(signalFormatNone)
{
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_sampleBuffer = new FixReal[1<<17]; // 128 kS
m_sampleBufferIndex = 0;
m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8);
m_magsq = 0.0f;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
DSDDemodSink::~DSDDemodSink()
{
delete[] m_sampleBuffer;
}
void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{ Complex ci;
int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol();
m_scopeSampleBuffer.clear();
m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
FixReal sample, delayedSample;
qint16 sampleDSD;
Real re = ci.real() / SDR_RX_SCALED;
Real im = ci.imag() / SDR_RX_SCALED;
Real magsq = re*re + im*im;
m_movingAverage(magsq);
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0]
m_sampleCount++;
// AF processing
if (m_movingAverage.asDouble() > m_squelchLevel)
{
if (m_squelchGate > 0)
{
if (m_squelchCount < m_squelchGate*2) {
m_squelchCount++;
}
m_squelchDelayLine.write(demod);
m_squelchOpen = m_squelchCount > m_squelchGate;
}
else
{
m_squelchOpen = true;
}
}
else
{
if (m_squelchGate > 0)
{
if (m_squelchCount > 0) {
m_squelchCount--;
}
m_squelchDelayLine.write(0);
m_squelchOpen = m_squelchCount > m_squelchGate;
}
else
{
m_squelchOpen = false;
}
}
if (m_squelchOpen)
{
if (m_squelchGate > 0)
{
sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples
sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size
}
else
{
sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples
sample = demod * SDR_RX_SCALEF; // scale to sample size
}
}
else
{
sampleDSD = 0;
sample = 0;
}
m_dsdDecoder.pushSample(sampleDSD);
if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod
sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort;
}
if (m_sampleBufferIndex < (1<<17)-1) {
m_sampleBufferIndex++;
} else {
m_sampleBufferIndex = 0;
}
m_sampleBuffer[m_sampleBufferIndex] = sample;
if (m_sampleBufferIndex < samplesPerSymbol) {
delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap
} else {
delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol];
}
if (m_settings.m_syncOrConstellation)
{
Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84);
m_scopeSampleBuffer.push_back(s);
}
else
{
Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate)
m_scopeSampleBuffer.push_back(s);
}
if (DSPEngine::instance()->hasDVSerialSupport())
{
if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1())
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_dsdDecoder.getMbeDVFrame1(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo1);
}
m_dsdDecoder.resetMbeDV1();
}
if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2())
{
if (!m_settings.m_audioMute)
{
DSPEngine::instance()->pushMbeFrame(
m_dsdDecoder.getMbeDVFrame2(),
m_dsdDecoder.getMbeRateIndex(),
m_settings.m_volume * 10.0,
m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
m_settings.m_highPassFilter,
m_audioSampleRate/8000, // upsample from native 8k
&m_audioFifo2);
}
m_dsdDecoder.resetMbeDV2();
}
}
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
if (!DSPEngine::instance()->hasDVSerialSupport())
{
if (m_settings.m_slot1On)
{
int nbAudioSamples;
short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples);
if (nbAudioSamples > 0)
{
if (!m_settings.m_audioMute) {
m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples);
}
m_dsdDecoder.resetAudio1();
}
}
if (m_settings.m_slot2On)
{
int nbAudioSamples;
short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples);
if (nbAudioSamples > 0)
{
if (!m_settings.m_audioMute) {
m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples);
}
m_dsdDecoder.resetAudio2();
}
}
}
if ((m_scopeXY != 0) && (m_scopeEnabled))
{
m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth
}
}
void DSDDemodSink::applyAudioSampleRate(int sampleRate)
{
int upsampling = sampleRate / 8000;
qDebug("DSDDemodSink::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling);
if (sampleRate % 8000 != 0) {
qDebug("DSDDemodSink::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s");
}
m_dsdDecoder.setUpsampling(upsampling);
m_audioSampleRate = sampleRate;
}
void DSDDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "DSDDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " inputFrequencyOffset: " << channelFrequencyOffset;
if ((channelFrequencyOffset != m_channelFrequencyOffset) ||
(channelSampleRate != m_channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, (m_settings.m_rfBandwidth) / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) 48000;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void DSDDemodSink::applySettings(const DSDDemodSettings& settings, bool force)
{
qDebug() << "DSDDemodSink::applySettings: "
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fmDeviation: " << settings.m_fmDeviation
<< " m_demodGain: " << settings.m_demodGain
<< " m_volume: " << settings.m_volume
<< " m_baudRate: " << settings.m_baudRate
<< " m_squelchGate" << settings.m_squelchGate
<< " m_squelch: " << settings.m_squelch
<< " m_audioMute: " << settings.m_audioMute
<< " m_enableCosineFiltering: " << settings.m_enableCosineFiltering
<< " m_syncOrConstellation: " << settings.m_syncOrConstellation
<< " m_slot1On: " << settings.m_slot1On
<< " m_slot2On: " << settings.m_slot2On
<< " m_tdmaStereo: " << settings.m_tdmaStereo
<< " m_pllLock: " << settings.m_pllLock
<< " m_highPassFilter: "<< settings.m_highPassFilter
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_traceLengthMutliplier: " << settings.m_traceLengthMutliplier
<< " m_traceStroke: " << settings.m_traceStroke
<< " m_traceDecay: " << settings.m_traceDecay
<< " m_streamIndex: " << settings.m_streamIndex
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_interpolator.create(16, m_channelSampleRate, (settings.m_rfBandwidth) / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) 48000;
//m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation);
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation));
}
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force)
{
m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_squelch != m_settings.m_squelch) || force)
{
// input is a value in dB
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
}
if ((settings.m_volume != m_settings.m_volume) || force)
{
m_dsdDecoder.setAudioGain(settings.m_volume);
}
if ((settings.m_baudRate != m_settings.m_baudRate) || force)
{
m_dsdDecoder.setBaudRate(settings.m_baudRate);
}
if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force)
{
m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering);
}
if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force)
{
m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo);
}
if ((settings.m_pllLock != m_settings.m_pllLock) || force)
{
m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock);
}
if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force)
{
m_dsdDecoder.useHPMbelib(settings.m_highPassFilter);
}
m_settings = settings;
}
void DSDDemodSink::configureMyPosition(float myLatitude, float myLongitude)
{
m_dsdDecoder.setMyPoint(myLatitude, myLongitude);
}
const char *DSDDemodSink::updateAndGetStatusText()
{
formatStatusText();
return m_formatStatusText;
}
void DSDDemodSink::formatStatusText()
{
switch (getDecoder().getSyncType())
{
case DSDcc::DSDDecoder::DSDSyncDMRDataMS:
case DSDcc::DSDDecoder::DSDSyncDMRDataP:
case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS:
case DSDcc::DSDDecoder::DSDSyncDMRVoiceP:
if (m_signalFormat != signalFormatDMR)
{
strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________");
}
switch (getDecoder().getStationType())
{
case DSDcc::DSDDecoder::DSDBaseStation:
memcpy(&m_formatStatusText[5], "BS ", 3);
break;
case DSDcc::DSDDecoder::DSDMobileStation:
memcpy(&m_formatStatusText[5], "MS ", 3);
break;
default:
memcpy(&m_formatStatusText[5], "NA ", 3);
break;
}
memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26);
memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26);
m_signalFormat = signalFormatDMR;
break;
case DSDcc::DSDDecoder::DSDSyncDStarHeaderN:
case DSDcc::DSDDecoder::DSDSyncDStarHeaderP:
case DSDcc::DSDDecoder::DSDSyncDStarN:
case DSDcc::DSDDecoder::DSDSyncDStarP:
if (m_signalFormat != signalFormatDStar)
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._");
// MY UR RPT1 RPT2 Info Loc Target
}
{
const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1();
const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2();
const std::string& mySign = getDecoder().getDStarDecoder().getMySign();
const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign();
if (rpt1.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[23], rpt1.c_str(), 8);
}
if (rpt2.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[32], rpt2.c_str(), 8);
}
if (yrSign.length() > 0) { // 0 or 8
memcpy(&m_formatStatusText[14], yrSign.c_str(), 8);
}
if (mySign.length() > 0) { // 0 or 13
memcpy(&m_formatStatusText[0], mySign.c_str(), 13);
}
memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20);
memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6);
snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f",
getDecoder().getDStarDecoder().getBearing(),
getDecoder().getDStarDecoder().getDistance());
}
m_formatStatusText[82] = '\0';
m_signalFormat = signalFormatDStar;
break;
case DSDcc::DSDDecoder::DSDSyncDPMR:
snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d",
DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()],
getDecoder().getDPMRDecoder().getColorCode(),
getDecoder().getDPMRDecoder().getOwnId(),
getDecoder().getDPMRDecoder().getCalledId());
m_signalFormat = signalFormatDPMR;
break;
case DSDcc::DSDDecoder::DSDSyncNXDNP:
case DSDcc::DSDDecoder::DSDSyncNXDNN:
if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH)
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// RC r cc mm llllll ssss
snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X",
getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H",
getDecoder().getNXDNDecoder().getRAN(),
getDecoder().getNXDNDecoder().getMessageType(),
getDecoder().getNXDNDecoder().getLocationId(),
getDecoder().getNXDNDecoder().getServicesFlag());
}
else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH)
|| (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH))
{
if (getDecoder().getNXDNDecoder().isIdle()) {
snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr());
}
else
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// Rx r cc mm sssss>gddddd
snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d",
getDecoder().getNXDNDecoder().getRFChannelStr(),
getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H",
getDecoder().getNXDNDecoder().getRAN(),
getDecoder().getNXDNDecoder().getMessageType(),
getDecoder().getNXDNDecoder().getSourceId(),
getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I',
getDecoder().getNXDNDecoder().getDestinationId());
}
}
else
{
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// RU
snprintf(m_formatStatusText, 82, "RU");
}
m_signalFormat = signalFormatNXDN;
break;
case DSDcc::DSDDecoder::DSDSyncYSF:
// 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8
// 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0..
// C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444
if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError)
{
snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]);
}
else
{
snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError());
}
snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c",
DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()],
DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()],
getDecoder().getYSFDecoder().getFICH().getBlockTotal(),
getDecoder().getYSFDecoder().getFICH().getFrameTotal(),
(getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'),
(getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L'));
if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled())
{
snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode());
}
else
{
strncpy(&m_formatStatusText[14], "---", 82-14);
}
char dest[13];
if (getDecoder().getYSFDecoder().radioIdMode())
{
snprintf(dest, 12, "%-5s:%-5s",
getDecoder().getYSFDecoder().getDestId(),
getDecoder().getYSFDecoder().getSrcId());
}
else
{
snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest());
}
snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s",
getDecoder().getYSFDecoder().getSrc(),
dest,
getDecoder().getYSFDecoder().getUplink(),
getDecoder().getYSFDecoder().getDownlink(),
getDecoder().getYSFDecoder().getRem4());
m_signalFormat = signalFormatYSF;
break;
default:
m_signalFormat = signalFormatNone;
m_formatStatusText[0] = '\0';
break;
}
m_formatStatusText[82] = '\0'; // guard
}

View File

@ -0,0 +1,150 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSDDEMODSINK_H
#define INCLUDE_DSDDEMODSINK_H
#include "dsp/channelsamplesink.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/bandpass.h"
#include "dsp/afsquelch.h"
#include "dsp/afsquelch.h"
#include "audio/audiofifo.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "dsddemodsettings.h"
#include "dsddecoder.h"
class BasebandSampleSink;
class DSDDemodSink : public ChannelSampleSink {
public:
DSDDemodSink();
~DSDDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyAudioSampleRate(int sampleRate);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const DSDDemodSettings& settings, bool force = false);
AudioFifo *getAudioFifo1() { return &m_audioFifo1; }
AudioFifo *getAudioFifo2() { return &m_audioFifo2; }
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
void setScopeXYSink(BasebandSampleSink* scopeSink) { m_scopeXY = scopeSink; }
void configureMyPosition(float myLatitude, float myLongitude);
double getMagSq() { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
const DSDDecoder& getDecoder() const { return m_dsdDecoder; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
const char *updateAndGetStatusText();
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
typedef enum
{
signalFormatNone,
signalFormatDMR,
signalFormatDStar,
signalFormatDPMR,
signalFormatYSF,
signalFormatNXDN
} SignalFormat; //!< Used for status text formatting
enum RateState {
RSInitialFill,
RSRunning
};
int m_channelSampleRate;
int m_channelFrequencyOffset;
DSDDemodSettings m_settings;
quint32 m_audioSampleRate;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
int m_sampleCount;
int m_squelchCount;
int m_squelchGate;
double m_squelchLevel;
bool m_squelchOpen;
DoubleBufferFIFO<Real> m_squelchDelayLine;
MovingAverageUtil<Real, double, 16> m_movingAverage;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
SampleVector m_scopeSampleBuffer;
AudioVector m_audioBuffer;
uint m_audioBufferFill;
FixReal *m_sampleBuffer; //!< samples ring buffer
int m_sampleBufferIndex;
int m_scaleFromShort;
AudioFifo m_audioFifo1;
AudioFifo m_audioFifo2;
BasebandSampleSink* m_scopeXY;
bool m_scopeEnabled;
DSDDecoder m_dsdDecoder;
char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text
SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type
PhaseDiscriminators m_phaseDiscri;
void formatStatusText();
};
#endif // INCLUDE_DSDDEMODSINK_H

View File

@ -4,14 +4,20 @@ set(nfm_SOURCES
nfmdemod.cpp
nfmdemodsettings.cpp
nfmdemodwebapiadapter.cpp
nfmplugin.cpp
nfmplugin.cpp
nfmdemodreport.cpp
nfmdemodsink.cpp
nfmdemodbaseband.cpp
)
set(nfm_HEADERS
nfmdemod.h
nfmdemodsettings.h
nfmdemodwebapiadapter.h
nfmplugin.h
nfmplugin.h
nfmdemodreport.h
nfmdemodsink.h
nfmdemodbaseband.h
)
include_directories(

View File

@ -24,81 +24,43 @@
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include "SWGChannelSettings.h"
#include "SWGNFMDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGNFMDemodReport.h"
#include "dsp/downchannelizer.h"
#include "util/stepfunctions.h"
#include "util/db.h"
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/threadedbasebandsamplesink.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "nfmdemod.h"
MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message)
MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(NFMDemod::MsgReportCTCSSFreq, Message)
const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod";
const QString NFMDemod::m_channelId = "NFMDemod";
static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0};
static const double afSqTones_lowrate[2] = {1000.0, 3500.0};
const int NFMDemod::m_udpBlockSize = 512;
NFMDemod::NFMDemod(DeviceAPI *devieAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(devieAPI),
m_inputSampleRate(48000),
m_inputFrequencyOffset(0),
m_running(false),
m_ctcssIndex(0),
m_sampleCount(0),
m_squelchCount(0),
m_squelchGate(4800),
m_squelchLevel(-990),
m_squelchOpen(false),
m_afSquelchOpen(false),
m_magsq(0.0f),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_afSquelch(),
m_squelchDelayLine(24000),
m_audioFifo(48000),
m_settingsMutex(QMutex::Recursive)
m_basebandSampleRate(0)
{
qDebug("NFMDemod::NFMDemod");
setObjectName(m_channelId);
m_audioBuffer.resize(1<<14);
m_audioBufferFill = 0;
m_thread = new QThread(this);
m_basebandSink = new NFMDemodBaseband();
m_basebandSink->moveToThread(m_thread);
m_agcLevel = 1.0;
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_discriCompensation = (m_audioSampleRate/48000.0f);
m_discriCompensation *= sqrt(m_discriCompensation);
m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution
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
m_ctcssLowpass.create(301, m_audioSampleRate, 250.0);
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
applySettings(m_settings, true);
m_channelizer = new DownChannelizer(this);
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
m_deviceAPI->addChannelSink(m_threadedChannelizer);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
@ -109,43 +71,10 @@ NFMDemod::~NFMDemod()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo);
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
delete m_threadedChannelizer;
delete m_channelizer;
}
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;
}
if(y < 0) {
return(-angle);
} else {
return(angle);
}
}
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;
m_deviceAPI->removeChannelSink(this);
delete m_basebandSink;
delete m_thread;
}
uint32_t NFMDemod::getNumberOfDeviceStreams() const
@ -156,245 +85,31 @@ uint32_t NFMDemod::getNumberOfDeviceStreams() const
void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
Complex ci;
if (!m_running) {
return;
}
m_settingsMutex.lock();
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
m_settingsMutex.unlock();
}
void NFMDemod::processOneSample(Complex &ci)
{
qint16 sample;
double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
Real deviation;
Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
m_movingAverage(magsq);
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
m_sampleCount++;
// AF processing
if (m_settings.m_deltaSquelch)
{
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
}
}
if (m_afSquelchOpen)
{
m_squelchDelayLine.write(demod * m_discriCompensation);
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
}
else
{
m_squelchDelayLine.write(0);
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
}
else
{
if ((Real) m_movingAverage < m_squelchLevel)
{
m_squelchDelayLine.write(0);
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
else
{
m_squelchDelayLine.write(demod * m_discriCompensation);
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
}
}
m_squelchOpen = (m_squelchCount > m_squelchGate);
if (m_settings.m_audioMute)
{
sample = 0;
}
else
{
if (m_squelchOpen)
{
if (m_settings.m_ctcssOn)
{
Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation);
if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
{
if (m_ctcssDetector.analyze(&ctcss_sample))
{
int maxToneIndex;
if (m_ctcssDetector.getDetectedTone(maxToneIndex))
{
if (maxToneIndex+1 != m_ctcssIndex)
{
if (getMessageQueueToGUI()) {
MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = maxToneIndex+1;
}
}
else
{
if (m_ctcssIndex != 0)
{
if (getMessageQueueToGUI()) {
MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = 0;
}
}
}
}
}
if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex))
{
sample = 0;
}
else
{
if (m_settings.m_highPass) {
sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume;
} else {
sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f;
}
}
}
else
{
if (m_ctcssIndex != 0)
{
if (getMessageQueueToGUI()) {
MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = 0;
}
sample = 0;
}
}
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);
if (res != m_audioBufferFill) {
qDebug("NFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
m_basebandSink->feed(begin, end);
}
void NFMDemod::start()
{
qDebug() << "NFMDemod::start";
m_squelchCount = 0;
m_audioFifo.clear();
m_phaseDiscri.reset();
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
m_running = true;
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
m_basebandSink->reset();
m_thread->start();
}
void NFMDemod::stop()
{
qDebug() << "NFMDemod::stop";
m_running = false;
m_thread->exit();
m_thread->wait();
}
bool NFMDemod::handleMessage(const Message& cmd)
{
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
{
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
qDebug() << "NFMDemod::handleMessage: DownChannelizer::MsgChannelizerNotification";
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
return true;
}
else if (MsgConfigureChannelizer::match(cmd))
{
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
qDebug() << "NFMDemod::handleMessage: MsgConfigureChannelizer:"
<< " sampleRate: " << cfg.getSampleRate()
<< " centerFrequency: " << cfg.getCenterFrequency();
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
cfg.getSampleRate(),
cfg.getCenterFrequency());
return true;
}
else if (MsgConfigureNFMDemod::match(cmd))
if (MsgConfigureNFMDemod::match(cmd))
{
MsgConfigureNFMDemod& cfg = (MsgConfigureNFMDemod&) cmd;
qDebug() << "NFMDemod::handleMessage: MsgConfigureNFMDemod";
@ -403,29 +118,15 @@ bool NFMDemod::handleMessage(const Message& cmd)
return true;
}
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;
}
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;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "NFMDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
return true;
}
else
@ -434,70 +135,6 @@ bool NFMDemod::handleMessage(const Message& cmd)
}
}
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();
m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
m_ctcssLowpass.create(301, sampleRate, 250.0);
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
m_lowpass.create(301, 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_squelchCount = 0; // reset squelch open counter
m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
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);
m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/2);
m_settingsMutex.unlock();
m_audioSampleRate = sampleRate;
}
void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
{
qDebug() << "NFMDemod::applyChannelSettings:"
<< " inputSampleRate: " << inputSampleRate
<< " inputFrequencyOffset: " << inputFrequencyOffset;
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
(inputSampleRate != m_inputSampleRate) || force)
{
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
}
if ((inputSampleRate != m_inputSampleRate) || force)
{
m_settingsMutex.lock();
m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
m_settingsMutex.unlock();
}
m_inputSampleRate = inputSampleRate;
m_inputFrequencyOffset = inputFrequencyOffset;
}
void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
{
qDebug() << "NFMDemod::applySettings:"
@ -542,86 +179,32 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
if ((settings.m_title != m_settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
m_settingsMutex.lock();
m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
m_settingsMutex.unlock();
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
}
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
{
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) {
reverseAPIKeys.append("afBandwidth");
m_settingsMutex.lock();
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth);
m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth);
m_settingsMutex.unlock();
}
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force)
{
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) {
reverseAPIKeys.append("squelchGate");
m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_squelch != m_settings.m_squelch) || force) {
reverseAPIKeys.append("squelch");
}
if ((settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) {
reverseAPIKeys.append("deltaSquelch");
}
if ((settings.m_squelch != m_settings.m_squelch) ||
(settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force)
{
if (settings.m_deltaSquelch)
{ // input is a value in negative centis
m_squelchLevel = (- settings.m_squelch) / 100.0;
m_afSquelch.setThreshold(m_squelchLevel);
m_afSquelch.reset();
}
else
{ // input is a value in deci-Bels
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
m_movingAverage.reset();
}
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force)
{
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) {
reverseAPIKeys.append("ctcssIndex");
setSelectedCtcssIndex(settings.m_ctcssIndex);
}
if ((settings.m_highPass != m_settings.m_highPass) || force) {
reverseAPIKeys.append("highPass");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
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);
}
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
@ -629,16 +212,18 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex);
m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex);
// apply stream sample rate to itself
applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset);
}
reverseAPIKeys.append("streamIndex");
}
NFMDemodBaseband::MsgConfigureNFMDemodBaseband *msg = NFMDemodBaseband::MsgConfigureNFMDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
@ -667,10 +252,6 @@ bool NFMDemod::deserialize(const QByteArray& data)
success = false;
}
NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create(
m_audioSampleRate, m_settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
@ -698,13 +279,6 @@ int NFMDemod::webapiSettingsPutPatch(
NFMDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
{
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
m_audioSampleRate, settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(channelConfigMsg);
}
MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(settings, force);
m_inputMessageQueue.push(msg);
@ -849,10 +423,20 @@ void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
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);
response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate);
response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate);
int nbCtcssToneFrequencies;
const Real *ctcssToneFrequencies = m_basebandSink->getCtcssToneSet(nbCtcssToneFrequencies);
response.getNfmDemodReport()->setCtcssTone(
m_settings.m_ctcssOn ?
m_settings.m_ctcssIndex < 0 ?
0
: m_settings.m_ctcssIndex < nbCtcssToneFrequencies ?
ctcssToneFrequencies[m_settings.m_ctcssIndex-1]
: 0
: 0
);
response.getNfmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0);
response.getNfmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
response.getNfmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
void NFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force)

View File

@ -16,36 +16,24 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_NFMDEMOD_H
#define INCLUDE_NFMDEMOD_H
#ifndef INCLUDE_NFMTESTDEMOD_H
#define INCLUDE_NFMTESTDEMOD_H
#include <vector>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/bandpass.h"
#include "dsp/afsquelch.h"
#include "dsp/agc.h"
#include "dsp/ctcssdetector.h"
#include "audio/audiofifo.h"
#include "util/message.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "nfmdemodbaseband.h"
#include "nfmdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class ThreadedBasebandSampleSink;
class DownChannelizer;
class NFMDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
@ -73,54 +61,11 @@ public:
{ }
};
class MsgConfigureChannelizer : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
int getCenterFrequency() const { return m_centerFrequency; }
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
{
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
}
private:
int m_sampleRate;
int m_centerFrequency;
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
};
class MsgReportCTCSSFreq : public Message {
MESSAGE_CLASS_DECLARATION
public:
Real getFrequency() const { return m_freq; }
static MsgReportCTCSSFreq* create(Real freq)
{
return new MsgReportCTCSSFreq(freq);
}
private:
Real m_freq;
MsgReportCTCSSFreq(Real freq) :
Message(),
m_freq(freq)
{ }
};
NFMDemod(DeviceAPI *deviceAPI);
~NFMDemod();
virtual void destroy() { delete this; }
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positive);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
@ -165,35 +110,11 @@ public:
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
const Real *getCtcssToneSet(int& nbTones) const {
nbTones = m_ctcssDetector.getNTones();
return m_ctcssDetector.getToneSet();
}
void setSelectedCtcssIndex(int selectedCtcssIndex) {
m_ctcssIndexSelected = selectedCtcssIndex;
}
Real getMag() { return m_magsq; }
bool getSquelchOpen() const { return m_squelchOpen; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
const Real *getCtcssToneSet(int& nbTones) const { m_basebandSink->getCtcssToneSet(nbTones); }
void setSelectedCtcssIndex(int selectedCtcssIndex) { m_basebandSink->setSelectedCtcssIndex(selectedCtcssIndex); }
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); }
void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); }
uint32_t getNumberOfDeviceStreams() const;
@ -201,82 +122,26 @@ public:
static const QString m_channelId;
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
enum RateState {
RSInitialFill,
RSRunning
};
enum RateState {
RSInitialFill,
RSRunning
};
DeviceAPI* m_deviceAPI;
ThreadedBasebandSampleSink* m_threadedChannelizer;
DownChannelizer* m_channelizer;
int m_inputSampleRate;
int m_inputFrequencyOffset;
QThread *m_thread;
NFMDemodBaseband* m_basebandSink;
NFMDemodSettings m_settings;
uint32_t m_audioSampleRate;
float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s)
bool m_running;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
Lowpass<Real> m_ctcssLowpass;
Bandpass<Real> m_bandpass;
Lowpass<Real> m_lowpass;
CTCSSDetector m_ctcssDetector;
int m_ctcssIndex; // 0 for nothing detected
int m_ctcssIndexSelected;
int m_sampleCount;
int m_squelchCount;
int m_squelchGate;
Real m_squelchLevel;
bool m_squelchOpen;
bool m_afSquelchOpen;
double m_magsq; //!< displayed averaged value
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 32> m_movingAverage;
AFSquelch m_afSquelch;
Real m_agcLevel; // AGC will aim to this level
DoubleBufferFIFO<Real> m_squelchDelayLine;
AudioVector m_audioBuffer;
uint m_audioBufferFill;
AudioFifo m_audioFifo;
QMutex m_settingsMutex;
PhaseDiscriminators m_phaseDiscri;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
static const int m_udpBlockSize;
// void apply(bool force = false);
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const NFMDemodSettings& settings, bool force = false);
void applyAudioSampleRate(int sampleRate);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMDemodSettings& settings, bool force);
void processOneSample(Complex &ci);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};

View File

@ -0,0 +1,170 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downsamplechannelizer.h"
#include "nfmdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(NFMDemodBaseband::MsgConfigureNFMDemodBaseband, Message)
NFMDemodBaseband::NFMDemodBaseband() :
m_mutex(QMutex::Recursive)
{
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownSampleChannelizer(&m_sink);
qDebug("NFMDemodBaseband::NFMDemodBaseband");
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&NFMDemodBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
NFMDemodBaseband::~NFMDemodBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
delete m_channelizer;
}
void NFMDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void NFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void NFMDemodBaseband::handleData()
{
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void NFMDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool NFMDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureNFMDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureNFMDemodBaseband& cfg = (MsgConfigureNFMDemodBaseband&) cmd;
qDebug() << "NFMDemodBaseband::handleMessage: MsgConfigureNFMDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "NFMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
return true;
}
else
{
return false;
}
}
void NFMDemodBaseband::applySettings(const NFMDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
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_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (m_sink.getAudioSampleRate() != audioSampleRate)
{
m_sink.applyAudioSampleRate(audioSampleRate);
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int NFMDemodBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void NFMDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
}

View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_NFMDEMODBASEBAND_H
#define INCLUDE_NFMDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "nfmdemodsink.h"
class DownSampleChannelizer;
class NFMDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureNFMDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const NFMDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureNFMDemodBaseband* create(const NFMDemodSettings& settings, bool force)
{
return new MsgConfigureNFMDemodBaseband(settings, force);
}
private:
NFMDemodSettings m_settings;
bool m_force;
MsgConfigureNFMDemodBaseband(const NFMDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
NFMDemodBaseband();
~NFMDemodBaseband();
void reset();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
void setSelectedCtcssIndex(int selectedCtcssIndex) { m_sink.setSelectedCtcssIndex(selectedCtcssIndex); }
bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
const Real *getCtcssToneSet(int& nbTones) const { return m_sink.getCtcssToneSet(nbTones); }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); }
unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); }
void setBasebandSampleRate(int sampleRate);
private:
SampleSinkFifo m_sampleFifo;
DownSampleChannelizer *m_channelizer;
NFMDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
NFMDemodSettings m_settings;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const NFMDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_NFMDEMODBASEBAND_H

View File

@ -1,5 +1,3 @@
#include "nfmdemodgui.h"
#include "device/deviceuiset.h"
#include <QDockWidget>
#include <QMainWindow>
@ -15,7 +13,10 @@
#include "gui/audioselectdialog.h"
#include "dsp/dspengine.h"
#include "mainwindow.h"
#include "nfmdemodreport.h"
#include "nfmdemod.h"
#include "nfmdemodgui.h"
NFMDemodGUI* NFMDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
@ -75,12 +76,10 @@ bool NFMDemodGUI::deserialize(const QByteArray& data)
bool NFMDemodGUI::handleMessage(const Message& message)
{
if (NFMDemod::MsgReportCTCSSFreq::match(message))
if (NFMDemodReport::MsgReportCTCSSFreq::match(message))
{
//qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq");
NFMDemod::MsgReportCTCSSFreq& report = (NFMDemod::MsgReportCTCSSFreq&) message;
NFMDemodReport::MsgReportCTCSSFreq& report = (NFMDemodReport::MsgReportCTCSSFreq&) message;
setCtcssFreq(report.getFrequency());
//qDebug("NFMDemodGUI::handleMessage: MsgReportCTCSSFreq: %f", report.getFrequency());
return true;
}
else if (NFMDemod::MsgConfigureNFMDemod::match(message))
@ -366,10 +365,6 @@ void NFMDemodGUI::applySettings(bool force)
{
qDebug() << "NFMDemodGUI::applySettings";
NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create(
48000, m_channelMarker.getCenterFrequency());
m_nfmDemod->getInputMessageQueue()->push(channelConfigMsg);
NFMDemod::MsgConfigureNFMDemod* message = NFMDemod::MsgConfigureNFMDemod::create( m_settings, force);
m_nfmDemod->getInputMessageQueue()->push(message);
}

View File

@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 "nfmdemodreport.h"
MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message)
NFMDemodReport::NFMDemodReport()
{ }
NFMDemodReport::~NFMDemodReport()
{ }

View File

@ -0,0 +1,55 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_
#define PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_
#include <QObject>
#include "dsp/dsptypes.h"
#include "util/message.h"
class NFMDemodReport : public QObject
{
Q_OBJECT
public:
class MsgReportCTCSSFreq : public Message {
MESSAGE_CLASS_DECLARATION
public:
Real getFrequency() const { return m_freq; }
static MsgReportCTCSSFreq* create(Real freq)
{
return new MsgReportCTCSSFreq(freq);
}
private:
Real m_freq;
MsgReportCTCSSFreq(Real freq) :
Message(),
m_freq(freq)
{ }
};
public:
NFMDemodReport();
~NFMDemodReport();
};
#endif // PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_

View File

@ -0,0 +1,385 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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 <stdio.h>
#include <complex.h>
#include <QTime>
#include <QDebug>
#include "util/stepfunctions.h"
#include "util/db.h"
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "nfmdemodreport.h"
#include "nfmdemodsink.h"
const double NFMDemodSink::afSqTones[] = {1000.0, 6000.0}; // {1200.0, 8000.0};
const double NFMDemodSink::afSqTones_lowrate[] = {1000.0, 3500.0};
NFMDemodSink::NFMDemodSink() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_audioSampleRate(48000),
m_audioBufferFill(0),
m_audioFifo(48000),
m_ctcssIndex(0),
m_sampleCount(0),
m_squelchCount(0),
m_squelchGate(4800),
m_squelchLevel(-990),
m_squelchOpen(false),
m_afSquelchOpen(false),
m_magsq(0.0f),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_afSquelch(),
m_squelchDelayLine(24000),
m_messageQueueToGUI(nullptr)
{
m_agcLevel = 1.0;
m_audioBuffer.resize(1<<14);
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
NFMDemodSink::~NFMDemodSink()
{
}
void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void NFMDemodSink::processOneSample(Complex &ci)
{
qint16 sample;
double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag();
Real deviation;
Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
m_movingAverage(magsq);
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
m_sampleCount++;
// AF processing
if (m_settings.m_deltaSquelch)
{
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
}
}
if (m_afSquelchOpen)
{
m_squelchDelayLine.write(demod * m_discriCompensation);
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
}
else
{
m_squelchDelayLine.write(0);
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
}
else
{
if ((Real) m_movingAverage < m_squelchLevel)
{
m_squelchDelayLine.write(0);
if (m_squelchCount > 0) {
m_squelchCount--;
}
}
else
{
m_squelchDelayLine.write(demod * m_discriCompensation);
if (m_squelchCount < 2*m_squelchGate) {
m_squelchCount++;
}
}
}
m_squelchOpen = (m_squelchCount > m_squelchGate);
if (m_settings.m_audioMute)
{
sample = 0;
}
else
{
if (m_squelchOpen)
{
if (m_settings.m_ctcssOn)
{
Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation);
if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k
{
if (m_ctcssDetector.analyze(&ctcss_sample))
{
int maxToneIndex;
if (m_ctcssDetector.getDetectedTone(maxToneIndex))
{
if (maxToneIndex+1 != m_ctcssIndex)
{
if (getMessageQueueToGUI())
{
NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = maxToneIndex+1;
}
}
else
{
if (m_ctcssIndex != 0)
{
if (getMessageQueueToGUI())
{
NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = 0;
}
}
}
}
}
if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex))
{
sample = 0;
}
else
{
if (m_settings.m_highPass) {
sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume;
} else {
sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f;
}
}
}
else
{
if (m_ctcssIndex != 0)
{
if (getMessageQueueToGUI())
{
NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0);
getMessageQueueToGUI()->push(msg);
}
m_ctcssIndex = 0;
}
sample = 0;
}
}
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);
if (res != m_audioBufferFill)
{
qDebug("NFMDemodSink::feed: %u/%u audio samples written", res, m_audioBufferFill);
qDebug("NFMDemodSink::feed: m_audioSampleRate: %u m_channelSampleRate: %d", m_audioSampleRate, m_channelSampleRate);
}
m_audioBufferFill = 0;
}
}
void NFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "NFMDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((channelFrequencyOffset != m_channelFrequencyOffset) ||
(channelSampleRate != m_channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
{
qDebug() << "NFMDemodSink::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_highPass: " << settings.m_highPass
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast<float>(settings.m_fmDeviation)); // integrate 4x factor
}
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
{
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth);
m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth);
}
if ((settings.m_squelchGate != m_settings.m_squelchGate) || force)
{
m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_squelch != m_settings.m_squelch) ||
(settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force)
{
if (settings.m_deltaSquelch)
{ // input is a value in negative centis
m_squelchLevel = (- settings.m_squelch) / 100.0;
m_afSquelch.setThreshold(m_squelchLevel);
m_afSquelch.reset();
}
else
{ // input is a value in deci-Bels
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
m_movingAverage.reset();
}
m_squelchCount = 0; // reset squelch open counter
}
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) {
setSelectedCtcssIndex(settings.m_ctcssIndex);
}
m_settings = settings;
}
void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
{
qDebug("NFMDemodSink::applyAudioSampleRate: %u m_channelSampleRate: %d", sampleRate, m_channelSampleRate);
m_ctcssLowpass.create(301, sampleRate, 250.0);
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
m_lowpass.create(301, 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_squelchCount = 0; // reset squelch open counter
m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution
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);
m_phaseDiscri.setFMScaling(sampleRate / static_cast<float>(m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/2);
m_audioSampleRate = sampleRate;
}

View File

@ -0,0 +1,180 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_NFMDEMODSINK_H
#define INCLUDE_NFMDEMODSINK_H
#include <vector>
#include "dsp/channelsamplesink.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/lowpass.h"
#include "dsp/bandpass.h"
#include "dsp/afsquelch.h"
#include "dsp/agc.h"
#include "dsp/ctcssdetector.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "audio/audiofifo.h"
#include "nfmdemodsettings.h"
class NFMDemodSink : public ChannelSampleSink {
public:
NFMDemodSink();
~NFMDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
const Real *getCtcssToneSet(int& nbTones) const {
nbTones = m_ctcssDetector.getNTones();
return m_ctcssDetector.getToneSet();
}
void setSelectedCtcssIndex(int selectedCtcssIndex) {
m_ctcssIndexSelected = selectedCtcssIndex;
}
bool getSquelchOpen() const { return m_squelchOpen; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const NFMDemodSettings& settings, bool force = false);
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
void applyAudioSampleRate(unsigned int sampleRate);
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
enum RateState {
RSInitialFill,
RSRunning
};
int m_channelSampleRate;
int m_channelFrequencyOffset;
NFMDemodSettings m_settings;
quint32 m_audioSampleRate;
AudioVector m_audioBuffer;
uint m_audioBufferFill;
AudioFifo m_audioFifo;
float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s)
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
Lowpass<Real> m_ctcssLowpass;
Bandpass<Real> m_bandpass;
Lowpass<Real> m_lowpass;
CTCSSDetector m_ctcssDetector;
int m_ctcssIndex; // 0 for nothing detected
int m_ctcssIndexSelected;
int m_sampleCount;
int m_squelchCount;
int m_squelchGate;
Real m_squelchLevel;
bool m_squelchOpen;
bool m_afSquelchOpen;
double m_magsq; //!< displayed averaged value
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 32> m_movingAverage;
AFSquelch m_afSquelch;
Real m_agcLevel; // AGC will aim to this level
DoubleBufferFIFO<Real> m_squelchDelayLine;
PhaseDiscriminators m_phaseDiscri;
MessageQueue *m_messageQueueToGUI;
static const double afSqTones[];
static const double afSqTones_lowrate[];
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
inline 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;
}
if(y < 0) {
return(-angle);
} else {
return(angle);
}
}
inline 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;
}
};
#endif // INCLUDE_NFMDEMODSINK_H

View File

@ -11,7 +11,7 @@
const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
QString("NFM Demodulator"),
QString("4.11.6"),
QString("4.12.2"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,

View File

@ -63,9 +63,11 @@ set(sdrbase_SOURCES
dsp/afsquelch.cpp
dsp/agc.cpp
dsp/downchannelizer.cpp
dsp/downsamplechannelizer.cpp
dsp/upchannelizer.cpp
dsp/channelmarker.cpp
dsp/ctcssdetector.cpp
dsp/channelsamplesink.cpp
dsp/channelsamplesource.cpp
dsp/cwkeyer.cpp
dsp/cwkeyersettings.cpp
@ -178,8 +180,10 @@ set(sdrbase_HEADERS
dsp/afsquelch.h
dsp/autocorrector.h
dsp/downchannelizer.h
dsp/downsamplechannelizer.h
dsp/upchannelizer.h
dsp/channelmarker.h
dsp/channelsamplesink.h
dsp/channelsamplesource.h
dsp/complex.h
dsp/cwkeyer.h

View File

@ -107,6 +107,24 @@ void DeviceAPI::removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIn
}
}
void DeviceAPI::addChannelSink(BasebandSampleSink* sink, int streamIndex)
{
if (m_deviceSourceEngine) {
m_deviceSourceEngine->addSink(sink);
} else if (m_deviceMIMOEngine) {
m_deviceMIMOEngine->addChannelSink(sink);
}
}
void DeviceAPI::removeChannelSink(BasebandSampleSink* sink, int streamIndex)
{
(void) streamIndex;
if (m_deviceSourceEngine) {
m_deviceSourceEngine->removeSink(sink);
}
}
void DeviceAPI::addChannelSource(BasebandSampleSource* source, int streamIndex)
{
if (m_deviceSinkEngine) {

View File

@ -73,6 +73,8 @@ public:
void addChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0); //!< Add a channel sink (Rx)
void removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0); //!< Remove a channel sink (Rx)
void addChannelSink(BasebandSampleSink* sink, int streamIndex = 0); //!< Add a channel sink (Rx)
void removeChannelSink(BasebandSampleSink* sink, int streamIndex = 0); //!< Remove a channel sink (Rx)
void addChannelSource(BasebandSampleSource* sink, int streamIndex = 0); //!< Add a channel source (Tx)
void removeChannelSource(BasebandSampleSource* sink, int streamIndex = 0); //!< Remove a channel source (Tx)
void addMIMOChannel(MIMOChannel* channel); //!< Add a MIMO channel (n Rx and m Tx combination)

View File

@ -0,0 +1,25 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// 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 "channelsamplesink.h"
ChannelSampleSink::ChannelSampleSink()
{}
ChannelSampleSink::~ChannelSampleSink()
{}

View File

@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
// This is a lightweight channel sample source interface
#ifndef SDRBASE_DSP_CHANNELSAMPLESINK_H_
#define SDRBASE_DSP_CHANNELSAMPLESINK_H_
#include "export.h"
#include "dsptypes.h"
class Message;
class SDRBASE_API ChannelSampleSink {
public:
ChannelSampleSink();
virtual ~ChannelSampleSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) = 0;
};
#endif // SDRBASE_DSP_CHANNELSAMPLESINK_H_

View File

@ -0,0 +1,322 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2019 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 //
// (at your option) any later version. //
// //
// 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 <QString>
#include <QDebug>
#include "dsp/inthalfbandfilter.h"
#include "dsp/dspcommands.h"
#include "dsp/hbfilterchainconverter.h"
#include "downsamplechannelizer.h"
DownSampleChannelizer::DownSampleChannelizer(ChannelSampleSink* sampleSink) :
m_filterChainSetMode(false),
m_sampleSink(sampleSink),
m_basebandSampleRate(0),
m_requestedOutputSampleRate(0),
m_requestedCenterFrequency(0),
m_channelSampleRate(0),
m_channelFrequencyOffset(0),
m_log2Decim(0),
m_filterChainHash(0)
{
}
DownSampleChannelizer::~DownSampleChannelizer()
{
freeFilterChain();
}
void DownSampleChannelizer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
if (m_sampleSink == 0)
{
m_sampleBuffer.clear();
return;
}
if (m_filterStages.size() == 0) // optimization when no downsampling is done anyway
{
m_sampleSink->feed(begin, end);
}
else
{
for (SampleVector::const_iterator sample = begin; sample != end; ++sample)
{
Sample s(*sample);
FilterStages::iterator stage = m_filterStages.begin();
for (; stage != m_filterStages.end(); ++stage)
{
#ifndef SDR_RX_SAMPLE_24BIT
s.m_real /= 2; // avoid saturation on 16 bit samples
s.m_imag /= 2;
#endif
if (!(*stage)->work(&s)) {
break;
}
}
if(stage == m_filterStages.end())
{
#ifdef SDR_RX_SAMPLE_24BIT
s.m_real /= (1<<(m_filterStages.size())); // on 32 bit samples there is enough headroom to just divide the final result
s.m_imag /= (1<<(m_filterStages.size()));
#endif
m_sampleBuffer.push_back(s);
}
}
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end());
m_sampleBuffer.clear();
}
}
void DownSampleChannelizer::setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency)
{
m_requestedOutputSampleRate = requestedSampleRate;
m_requestedCenterFrequency = requestedCenterFrequency;
applyChannelization();
}
void DownSampleChannelizer::setBasebandSampleRate(int basebandSampleRate, bool decim)
{
m_basebandSampleRate = basebandSampleRate;
if (decim) {
applyDecimation();
} else {
applyChannelization();
}
}
void DownSampleChannelizer::applyChannelization()
{
m_filterChainSetMode = false;
if (m_basebandSampleRate == 0)
{
qDebug() << "DownSampleChannelizer::applyChannelization: aborting (in=0)"
<< " in (baseband):" << m_basebandSampleRate
<< " req:" << m_requestedOutputSampleRate
<< " out (channel):" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
return;
}
freeFilterChain();
m_channelFrequencyOffset = createFilterChain(
m_basebandSampleRate / -2, m_basebandSampleRate / 2,
m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2);
m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size());
qDebug() << "DownChannelizer::applyChannelization done:"
<< " in (baseband):" << m_basebandSampleRate
<< " req:" << m_requestedOutputSampleRate
<< " out (channel):" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
}
void DownSampleChannelizer::setDecimation(unsigned int log2Decim, unsigned int filterChainHash)
{
m_log2Decim = log2Decim;
m_filterChainHash = filterChainHash;
applyDecimation();
}
void DownSampleChannelizer::applyDecimation()
{
m_filterChainSetMode = true;
std::vector<unsigned int> stageIndexes;
m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Decim, m_filterChainHash, stageIndexes);
m_requestedCenterFrequency = m_channelFrequencyOffset;
freeFilterChain();
m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes);
m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size());
m_requestedOutputSampleRate = m_channelSampleRate;
qDebug() << "UpChannelizer::applyInterpolation:"
<< " m_log2Interp:" << m_log2Decim
<< " m_filterChainHash:" << m_filterChainHash
<< " out:" << m_basebandSampleRate
<< " in:" << m_channelSampleRate
<< " fc:" << m_channelFrequencyOffset;
}
#ifdef SDR_RX_SAMPLE_24BIT
DownSampleChannelizer::FilterStage::FilterStage(Mode mode) :
m_filter(new IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>),
m_workFunction(0),
m_mode(mode),
m_sse(true)
{
switch(mode) {
case ModeCenter:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateCenter;
break;
case ModeLowerHalf:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateLowerHalf;
break;
case ModeUpperHalf:
m_workFunction = &IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateUpperHalf;
break;
}
}
#else
DownSampleChannelizer::FilterStage::FilterStage(Mode mode) :
m_filter(new IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>),
m_workFunction(0),
m_mode(mode),
m_sse(true)
{
switch(mode) {
case ModeCenter:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateCenter;
break;
case ModeLowerHalf:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateLowerHalf;
break;
case ModeUpperHalf:
m_workFunction = &IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::workDecimateUpperHalf;
break;
}
}
#endif
DownSampleChannelizer::FilterStage::~FilterStage()
{
delete m_filter;
}
bool DownSampleChannelizer::signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const
{
//qDebug(" testing signal [%f, %f], channel [%f, %f]", sigStart, sigEnd, chanStart, chanEnd);
if(sigEnd <= sigStart)
return false;
if(chanEnd <= chanStart)
return false;
return (sigStart <= chanStart) && (sigEnd >= chanEnd);
}
Real DownSampleChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd)
{
Real sigBw = sigEnd - sigStart;
Real rot = sigBw / 4;
//qDebug("DownChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f), Channel [%.1f, %.1f], Rot %.1f", sigStart, sigEnd, sigBw, chanStart, chanEnd, rot);
// check if it fits into the left half
if(signalContainsChannel(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd))
{
//qDebug("DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)");
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd);
}
// check if it fits into the right half
if(signalContainsChannel(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd))
{
//qDebug("DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)");
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd);
}
// check if it fits into the center
if(signalContainsChannel(sigStart + rot, sigEnd - rot, chanStart, chanEnd))
{
//qDebug("DownChannelizer::createFilterChain: -> take center half (decimate by 2)");
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd);
}
Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart);
//qDebug("DownChannelizer::createFilterChain: -> complete (final BW %.1f, frequency offset %.1f)", sigBw, ofs);
return ofs;
}
double DownSampleChannelizer::setFilterChain(const std::vector<unsigned int>& stageIndexes)
{
// filters are described from lower to upper level but the chain is constructed the other way round
std::vector<unsigned int>::const_reverse_iterator rit = stageIndexes.rbegin();
double ofs = 0.0, ofs_stage = 0.25;
// Each index is a base 3 number with 0 = low, 1 = center, 2 = high
// Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting
// algorithms will go from LSD to MSD. This explains the reverse order.
for (; rit != stageIndexes.rend(); ++rit)
{
if (*rit == 0)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf));
ofs -= ofs_stage;
qDebug("DownSampleChannelizer::setFilterChain: lower half: ofs: %f", ofs);
}
else if (*rit == 1)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter));
qDebug("DownSampleChannelizer::setFilterChain: center: ofs: %f", ofs);
}
else if (*rit == 2)
{
m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf));
ofs += ofs_stage;
qDebug("DownSampleChannelizer::setFilterChain: upper half: ofs: %f", ofs);
}
}
return ofs;
}
void DownSampleChannelizer::freeFilterChain()
{
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
delete *it;
m_filterStages.clear();
}
void DownSampleChannelizer::debugFilterChain()
{
qDebug("DownChannelizer::debugFilterChain: %lu stages", m_filterStages.size());
for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it)
{
switch ((*it)->m_mode)
{
case FilterStage::ModeCenter:
qDebug("DownChannelizer::debugFilterChain: center %s", (*it)->m_sse ? "sse" : "no_sse");
break;
case FilterStage::ModeLowerHalf:
qDebug("DownChannelizer::debugFilterChain: lower %s", (*it)->m_sse ? "sse" : "no_sse");
break;
case FilterStage::ModeUpperHalf:
qDebug("DownChannelizer::debugFilterChain: upper %s", (*it)->m_sse ? "sse" : "no_sse");
break;
default:
qDebug("DownChannelizer::debugFilterChain: none %s", (*it)->m_sse ? "sse" : "no_sse");
break;
}
}
}

View File

@ -0,0 +1,96 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2019 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 //
// (at your option) any later version. //
// //
// 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/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_DSP_DOWNSAMPLECHANNELIZER_H
#define SDRBASE_DSP_DOWNSAMPLECHANNELIZER_H
#include <list>
#include <vector>
#include "export.h"
#include "util/message.h"
#include "dsp/inthalfbandfiltereo.h"
#include "channelsamplesink.h"
#define DOWNCHANNELIZER_HB_FILTER_ORDER 48
class SDRBASE_API DownSampleChannelizer : public ChannelSampleSink {
public:
DownSampleChannelizer(ChannelSampleSink* sampleSink);
virtual ~DownSampleChannelizer();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setDecimation(unsigned int log2Decim, unsigned int filterChainHash); //!< Define channelizer with decimation factor and filter chain definition
void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband)
void setBasebandSampleRate(int basebandSampleRate, bool decim = false); //!< decim: true => use direct decimation false => use channel configuration
int getChannelSampleRate() const { return m_channelSampleRate; }
int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; }
protected:
struct FilterStage {
enum Mode {
ModeCenter,
ModeLowerHalf,
ModeUpperHalf
};
#ifdef SDR_RX_SAMPLE_24BIT
typedef bool (IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>::*WorkFunction)(Sample* s);
IntHalfbandFilterEO<qint64, qint64, DOWNCHANNELIZER_HB_FILTER_ORDER>* m_filter;
#else
typedef bool (IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>::*WorkFunction)(Sample* s);
IntHalfbandFilterEO<qint32, qint32, DOWNCHANNELIZER_HB_FILTER_ORDER>* m_filter;
#endif
WorkFunction m_workFunction;
Mode m_mode;
bool m_sse;
FilterStage(Mode mode);
~FilterStage();
bool work(Sample* sample)
{
return (m_filter->*m_workFunction)(sample);
}
};
typedef std::list<FilterStage*> FilterStages;
FilterStages m_filterStages;
bool m_filterChainSetMode;
ChannelSampleSink* m_sampleSink; //!< Demodulator
int m_basebandSampleRate;
int m_requestedOutputSampleRate;
int m_requestedCenterFrequency;
int m_channelSampleRate;
int m_channelFrequencyOffset;
unsigned int m_log2Decim;
unsigned int m_filterChainHash;
SampleVector m_sampleBuffer;
void applyChannelization();
void applyDecimation();
bool signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const;
Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd);
double setFilterChain(const std::vector<unsigned int>& stageIndexes);
void freeFilterChain();
void debugFilterChain();
};
#endif // SDRBASE_DSP_DOWNCHANNELIZER_H

View File

@ -191,6 +191,26 @@ void DSPDeviceMIMOEngine::removeChannelSink(ThreadedBasebandSampleSink* sink, in
m_syncMessenger.sendWait(cmd);
}
void DSPDeviceMIMOEngine::addChannelSink(BasebandSampleSink* sink, int index)
{
qDebug() << "DSPDeviceMIMOEngine::addChannelSink: "
<< sink->objectName().toStdString().c_str()
<< " at: "
<< index;
AddBasebandSampleSink cmd(sink, index);
m_syncMessenger.sendWait(cmd);
}
void DSPDeviceMIMOEngine::removeChannelSink(BasebandSampleSink* sink, int index)
{
qDebug() << "DSPDeviceMIMOEngine::removeChannelSink: "
<< sink->objectName().toStdString().c_str()
<< " at: "
<< index;
RemoveBasebandSampleSink cmd(sink, index);
m_syncMessenger.sendWait(cmd);
}
void DSPDeviceMIMOEngine::addMIMOChannel(MIMOChannel *channel)
{
qDebug() << "DSPDeviceMIMOEngine::addMIMOChannel: "

View File

@ -262,6 +262,8 @@ public:
void addChannelSource(BasebandSampleSource* source, int index = 0); //!< Add a channel source
void removeChannelSource(BasebandSampleSource* source, int index = 0); //!< Remove a channel source
void addChannelSink(BasebandSampleSink* sink, int index = 0); //!< Add a channel sink
void removeChannelSink(BasebandSampleSink* sink, int index = 0); //!< Remove a channel sink
void addChannelSink(ThreadedBasebandSampleSink* sink, int index = 0); //!< Add a channel sink that will run on its own thread
void removeChannelSink(ThreadedBasebandSampleSink* sink, int index = 0); //!< Remove a channel sink that runs on its own thread
void addMIMOChannel(MIMOChannel *channel); //!< Add a MIMO channel

View File

@ -31,6 +31,14 @@ void SampleSinkFifo::create(unsigned int s)
m_size = m_data.size();
}
void SampleSinkFifo::reset()
{
m_suppressed = -1;
m_fill = 0;
m_head = 0;
m_tail = 0;
}
SampleSinkFifo::SampleSinkFifo(QObject* parent) :
QObject(parent),
m_data()
@ -274,3 +282,8 @@ unsigned int SampleSinkFifo::readCommit(unsigned int count)
return count;
}
unsigned int SampleSinkFifo::getSizePolicy(unsigned int sampleRate)
{
return (sampleRate/100)*64; // .64s
}

View File

@ -49,6 +49,7 @@ public:
~SampleSinkFifo();
bool setSize(int size);
void reset();
inline unsigned int size() const { return m_size; }
inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; }
@ -61,6 +62,7 @@ public:
SampleVector::iterator* part1Begin, SampleVector::iterator* part1End,
SampleVector::iterator* part2Begin, SampleVector::iterator* part2End);
unsigned int readCommit(unsigned int count);
static unsigned int getSizePolicy(unsigned int sampleRate);
signals:
void dataReady();