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:
parent
acd0892536
commit
5b83b2a4a8
@ -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)
|
||||
|
@ -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}
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
172
plugins/channelrx/demodam/amdemodbaseband.cpp
Normal file
172
plugins/channelrx/demodam/amdemodbaseband.cpp
Normal 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);
|
||||
}
|
89
plugins/channelrx/demodam/amdemodbaseband.h
Normal file
89
plugins/channelrx/demodam/amdemodbaseband.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
320
plugins/channelrx/demodam/amdemodsink.cpp
Normal file
320
plugins/channelrx/demodam/amdemodsink.cpp
Normal 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;
|
||||
}
|
127
plugins/channelrx/demodam/amdemodsink.h
Normal file
127
plugins/channelrx/demodam/amdemodsink.h
Normal 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
|
@ -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})
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
174
plugins/channelrx/demoddsd/dsddemodbaseband.cpp
Normal file
174
plugins/channelrx/demoddsd/dsddemodbaseband.cpp
Normal 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);
|
||||
}
|
91
plugins/channelrx/demoddsd/dsddemodbaseband.h
Normal file
91
plugins/channelrx/demoddsd/dsddemodbaseband.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
595
plugins/channelrx/demoddsd/dsddemodsink.cpp
Normal file
595
plugins/channelrx/demoddsd/dsddemodsink.cpp
Normal 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
|
||||
}
|
150
plugins/channelrx/demoddsd/dsddemodsink.h
Normal file
150
plugins/channelrx/demoddsd/dsddemodsink.h
Normal 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
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
};
|
||||
|
170
plugins/channelrx/demodnfm/nfmdemodbaseband.cpp
Normal file
170
plugins/channelrx/demodnfm/nfmdemodbaseband.cpp
Normal 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);
|
||||
}
|
89
plugins/channelrx/demodnfm/nfmdemodbaseband.h
Normal file
89
plugins/channelrx/demodnfm/nfmdemodbaseband.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
26
plugins/channelrx/demodnfm/nfmdemodreport.cpp
Normal file
26
plugins/channelrx/demodnfm/nfmdemodreport.cpp
Normal 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()
|
||||
{ }
|
55
plugins/channelrx/demodnfm/nfmdemodreport.h
Normal file
55
plugins/channelrx/demodnfm/nfmdemodreport.h
Normal 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_
|
385
plugins/channelrx/demodnfm/nfmdemodsink.cpp
Normal file
385
plugins/channelrx/demodnfm/nfmdemodsink.cpp
Normal 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;
|
||||
}
|
180
plugins/channelrx/demodnfm/nfmdemodsink.h
Normal file
180
plugins/channelrx/demodnfm/nfmdemodsink.h
Normal 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
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
25
sdrbase/dsp/channelsamplesink.cpp
Normal file
25
sdrbase/dsp/channelsamplesink.cpp
Normal 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()
|
||||
{}
|
37
sdrbase/dsp/channelsamplesink.h
Normal file
37
sdrbase/dsp/channelsamplesink.h
Normal 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_
|
322
sdrbase/dsp/downsamplechannelizer.cpp
Normal file
322
sdrbase/dsp/downsamplechannelizer.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
96
sdrbase/dsp/downsamplechannelizer.h
Normal file
96
sdrbase/dsp/downsamplechannelizer.h
Normal 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
|
@ -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: "
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user