mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-22 16:08:39 -05:00
Rx plugins: refactoring of classes (2)
This commit is contained in:
parent
0873672a74
commit
735f1cdbb4
@ -3,6 +3,9 @@ project(bfm)
|
||||
set(bfm_SOURCES
|
||||
bfmdemod.cpp
|
||||
bfmdemodsettings.cpp
|
||||
bfmdemodsink.cpp
|
||||
bfmdemodbaseband.cpp
|
||||
bfmdemodreport.cpp
|
||||
bfmdemodwebapiadapter.cpp
|
||||
bfmplugin.cpp
|
||||
rdsdemod.cpp
|
||||
@ -14,6 +17,9 @@ set(bfm_SOURCES
|
||||
set(bfm_HEADERS
|
||||
bfmdemod.h
|
||||
bfmdemodsettings.h
|
||||
bfmdemodsink.h
|
||||
bfmdemodbaseband.h
|
||||
bfmdemodreport.h
|
||||
bfmdemodwebapiadapter.h
|
||||
bfmplugin.h
|
||||
rdsdemod.h
|
||||
@ -31,7 +37,6 @@ if(NOT SERVER_MODE)
|
||||
set(bfm_SOURCES
|
||||
${bfm_SOURCES}
|
||||
bfmdemodgui.cpp
|
||||
|
||||
bfmdemodgui.ui
|
||||
)
|
||||
set(bfm_HEADERS
|
||||
|
@ -17,14 +17,13 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "boost/format.hpp"
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGBFMDemodSettings.h"
|
||||
@ -32,79 +31,34 @@
|
||||
#include "SWGBFMDemodReport.h"
|
||||
#include "SWGRDSReport.h"
|
||||
|
||||
#include "audio/audiooutput.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/downchannelizer.h"
|
||||
#include "dsp/threadedbasebandsamplesink.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "rdsparser.h"
|
||||
#include "bfmdemod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(BFMDemod::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(BFMDemod::MsgReportChannelSampleRateChanged, Message)
|
||||
MESSAGE_CLASS_DEFINITION(BFMDemod::MsgConfigureBFMDemod, Message)
|
||||
|
||||
const QString BFMDemod::m_channelIdURI = "sdrangel.channel.bfm";
|
||||
const QString BFMDemod::m_channelId = "BFMDemod";
|
||||
const Real BFMDemod::default_deemphasis = 50.0; // 50 us
|
||||
const int BFMDemod::m_udpBlockSize = 512;
|
||||
|
||||
BFMDemod::BFMDemod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_inputSampleRate(384000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_audioFifo(250000),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_pilotPLL(19000/384000, 50/384000, 0.01),
|
||||
m_deemphasisFilterX(default_deemphasis * 48000 * 1.0e-6),
|
||||
m_deemphasisFilterY(default_deemphasis * 48000 * 1.0e-6),
|
||||
m_fmExcursion(default_excursion)
|
||||
m_basebandSampleRate(0)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSink = new BFMDemodBaseband();
|
||||
m_basebandSink->moveToThread(m_thread);
|
||||
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
|
||||
m_squelchLevel = 0;
|
||||
m_squelchState = 0;
|
||||
|
||||
m_interpolatorDistance = 0.0f;
|
||||
m_interpolatorDistanceRemain = 0.0f;
|
||||
|
||||
m_interpolatorRDSDistance = 0.0f;
|
||||
m_interpolatorRDSDistanceRemain = 0.0f;
|
||||
|
||||
m_interpolatorStereoDistance = 0.0f;
|
||||
m_interpolatorStereoDistanceRemain = 0.0f;
|
||||
|
||||
m_sampleSink = 0;
|
||||
m_m1Arg = 0;
|
||||
|
||||
m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, filtFftLen);
|
||||
|
||||
m_deemphasisFilterX.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
|
||||
m_deemphasisFilterY.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
|
||||
m_phaseDiscri.setFMScaling(384000/m_fmExcursion);
|
||||
|
||||
m_audioBuffer.resize(16384);
|
||||
m_audioBufferFill = 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();
|
||||
@ -116,13 +70,10 @@ BFMDemod::~BFMDemod()
|
||||
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 m_rfFilter;
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
delete m_basebandSink;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
uint32_t BFMDemod::getNumberOfDeviceStreams() const
|
||||
@ -133,217 +84,31 @@ uint32_t BFMDemod::getNumberOfDeviceStreams() const
|
||||
void BFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
||||
{
|
||||
(void) firstOfBurst;
|
||||
Complex ci, cs, cr;
|
||||
fftfilt::cmplx *rf;
|
||||
int rf_out;
|
||||
double msq;
|
||||
Real demod;
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
||||
{
|
||||
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
rf_out = m_rfFilter->runFilt(c, &rf); // filter RF before demod
|
||||
|
||||
for (int i =0 ; i <rf_out; i++)
|
||||
{
|
||||
msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag();
|
||||
m_magsqSum += msq;
|
||||
|
||||
if (msq > m_magsqPeak) {
|
||||
m_magsqPeak = msq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (msq >= m_squelchLevel)
|
||||
{
|
||||
if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate
|
||||
m_squelchState++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchState > 0) {
|
||||
m_squelchState--;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_squelchState > m_settings.m_rfBandwidth / 20) { // squelch open
|
||||
demod = m_phaseDiscri.phaseDiscriminator(rf[i]);
|
||||
} else {
|
||||
demod = 0;
|
||||
}
|
||||
|
||||
if (!m_settings.m_showPilot) {
|
||||
m_sampleBuffer.push_back(Sample(demod * SDR_RX_SCALEF, 0.0));
|
||||
}
|
||||
|
||||
if (m_settings.m_rdsActive)
|
||||
{
|
||||
//Complex r(demod * 2.0 * std::cos(3.0 * m_pilotPLLSamples[3]), 0.0);
|
||||
Complex r(demod * 2.0 * std::cos(3.0 * m_pilotPLLSamples[3]), 0.0);
|
||||
|
||||
if (m_interpolatorRDS.decimate(&m_interpolatorRDSDistanceRemain, r, &cr))
|
||||
{
|
||||
bool bit;
|
||||
|
||||
if (m_rdsDemod.process(cr.real(), bit))
|
||||
{
|
||||
if (m_rdsDecoder.frameSync(bit)) {
|
||||
m_rdsParser.parseGroup(m_rdsDecoder.getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
m_interpolatorRDSDistanceRemain += m_interpolatorRDSDistance;
|
||||
}
|
||||
}
|
||||
|
||||
Real sampleStereo = 0.0f;
|
||||
|
||||
// Process stereo if stereo mode is selected
|
||||
|
||||
if (m_settings.m_audioStereo)
|
||||
{
|
||||
m_pilotPLL.process(demod, m_pilotPLLSamples);
|
||||
|
||||
if (m_settings.m_showPilot) {
|
||||
m_sampleBuffer.push_back(Sample(m_pilotPLLSamples[1] * SDR_RX_SCALEF, 0.0)); // debug 38 kHz pilot
|
||||
}
|
||||
|
||||
if (m_settings.m_lsbStereo)
|
||||
{
|
||||
// 1.17 * 0.7 = 0.819
|
||||
Complex s(demod * m_pilotPLLSamples[1], demod * m_pilotPLLSamples[2]);
|
||||
|
||||
if (m_interpolatorStereo.decimate(&m_interpolatorStereoDistanceRemain, s, &cs))
|
||||
{
|
||||
sampleStereo = cs.real() + cs.imag();
|
||||
m_interpolatorStereoDistanceRemain += m_interpolatorStereoDistance;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Complex s(demod * 1.17 * m_pilotPLLSamples[1], 0);
|
||||
|
||||
if (m_interpolatorStereo.decimate(&m_interpolatorStereoDistanceRemain, s, &cs))
|
||||
{
|
||||
sampleStereo = cs.real();
|
||||
m_interpolatorStereoDistanceRemain += m_interpolatorStereoDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Complex e(demod, 0);
|
||||
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci))
|
||||
{
|
||||
if (m_settings.m_audioStereo)
|
||||
{
|
||||
Real deemph_l, deemph_r; // Pre-emphasis is applied on each channel before multiplexing
|
||||
m_deemphasisFilterX.process(ci.real() + sampleStereo, deemph_l);
|
||||
m_deemphasisFilterY.process(ci.real() - sampleStereo, deemph_r);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
Real deemph;
|
||||
m_deemphasisFilterX.process(ci.real(), deemph);
|
||||
quint16 sample = (qint16)(deemph * (1<<12) * m_settings.m_volume);
|
||||
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("BFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_audioBufferFill > 0)
|
||||
{
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill) {
|
||||
qDebug("BFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
if (m_sampleSink != 0) {
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
|
||||
void BFMDemod::start()
|
||||
{
|
||||
m_squelchState = 0;
|
||||
m_audioFifo.clear();
|
||||
m_phaseDiscri.reset();
|
||||
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug() << "BFMDemod::start";
|
||||
|
||||
if (m_basebandSampleRate != 0) {
|
||||
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||
}
|
||||
|
||||
m_basebandSink->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void BFMDemod::stop()
|
||||
{
|
||||
qDebug() << "BFMDemod::stop";
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
bool BFMDemod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
|
||||
|
||||
qDebug() << "BFMDemod::handleMessage: MsgChannelizerNotification:"
|
||||
<< " inputSampleRate: " << notif.getSampleRate()
|
||||
<< " inputFrequencyOffset: " << notif.getFrequencyOffset();
|
||||
|
||||
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportChannelSampleRateChanged *msg = MsgReportChannelSampleRateChanged::create(getSampleRate());
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
|
||||
qDebug() << "BFMDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureBFMDemod::match(cmd))
|
||||
if (MsgConfigureBFMDemod::match(cmd))
|
||||
{
|
||||
MsgConfigureBFMDemod& cfg = (MsgConfigureBFMDemod&) cmd;
|
||||
qDebug() << "BFMDemod::handleMessage: MsgConfigureBFMDemod";
|
||||
@ -352,105 +117,21 @@ bool BFMDemod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
|
||||
qDebug() << "BFMDemod::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;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "BFMDemod::handleMessage: passed: " << cmd.getIdentifier();
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
// Forward to the sink
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "BFMDemod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
|
||||
if (m_sampleSink != 0)
|
||||
{
|
||||
return m_sampleSink->handleMessage(cmd);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BFMDemod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("BFMDemod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolator.create(16, m_inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_inputSampleRate / sampleRate;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, m_inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) m_inputSampleRate / sampleRate;
|
||||
m_interpolatorStereoDistance = (Real) m_inputSampleRate / (Real) sampleRate;
|
||||
|
||||
m_deemphasisFilterX.configure(default_deemphasis * sampleRate * 1.0e-6);
|
||||
m_deemphasisFilterY.configure(default_deemphasis * sampleRate * 1.0e-6);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void BFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "BFMDemod::applyChannelSettings:"
|
||||
<< " inputSampleRate: " << inputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(inputSampleRate != m_inputSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
|
||||
}
|
||||
|
||||
if ((inputSampleRate != m_inputSampleRate) || force)
|
||||
{
|
||||
m_pilotPLL.configure(19000.0/inputSampleRate, 50.0/inputSampleRate, 0.01);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolator.create(16, inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) inputSampleRate / m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) inputSampleRate / m_audioSampleRate;
|
||||
m_interpolatorStereoDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorRDS.create(4, inputSampleRate, 600.0);
|
||||
m_interpolatorRDSDistanceRemain = (Real) inputSampleRate / 250000.0;
|
||||
m_interpolatorRDSDistance = (Real) inputSampleRate / 250000.0;
|
||||
|
||||
Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / inputSampleRate;
|
||||
Real hiCut = (m_settings.m_rfBandwidth / 2.0) / inputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_phaseDiscri.setFMScaling(inputSampleRate / m_fmExcursion);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_inputSampleRate = inputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force)
|
||||
@ -490,63 +171,17 @@ void BFMDemod::applySettings(const BFMDemodSettings& settings, bool force)
|
||||
if ((settings.m_rdsActive != m_settings.m_rdsActive) || force) {
|
||||
reverseAPIKeys.append("rdsActive");
|
||||
}
|
||||
|
||||
if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force)
|
||||
{
|
||||
m_pilotPLL.configure(19000.0/m_inputSampleRate, 50.0/m_inputSampleRate, 0.01);
|
||||
}
|
||||
|
||||
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_interpolator.create(16, m_inputSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_inputSampleRate / m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, m_inputSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) m_inputSampleRate / m_audioSampleRate;
|
||||
m_interpolatorStereoDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorRDS.create(4, m_inputSampleRate, 600.0);
|
||||
m_interpolatorRDSDistanceRemain = (Real) m_inputSampleRate / 250000.0;
|
||||
m_interpolatorRDSDistance = (Real) m_inputSampleRate / 250000.0;
|
||||
|
||||
m_lowpass.create(21, m_audioSampleRate, settings.m_afBandwidth);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
|
||||
reverseAPIKeys.append("rfBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate;
|
||||
Real hiCut = (settings.m_rfBandwidth / 2.0) / m_inputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_phaseDiscri.setFMScaling(m_inputSampleRate / m_fmExcursion);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_squelch != m_settings.m_squelch) || force)
|
||||
{
|
||||
if ((settings.m_squelch != m_settings.m_squelch) || force) {
|
||||
reverseAPIKeys.append("squelch");
|
||||
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
|
||||
}
|
||||
|
||||
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)
|
||||
@ -554,16 +189,17 @@ void BFMDemod::applySettings(const BFMDemodSettings& 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");
|
||||
}
|
||||
|
||||
BFMDemodBaseband::MsgConfigureBFMDemodBaseband *msg = BFMDemodBaseband::MsgConfigureBFMDemodBaseband::create(settings, force);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -620,13 +256,6 @@ int BFMDemod::webapiSettingsPutPatch(
|
||||
BFMDemodSettings settings = m_settings;
|
||||
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
||||
|
||||
if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
|
||||
{
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
}
|
||||
|
||||
MsgConfigureBFMDemod *msg = MsgConfigureBFMDemod::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
@ -760,9 +389,9 @@ void BFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
|
||||
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||
|
||||
response.getBfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
|
||||
response.getBfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0);
|
||||
response.getBfmDemodReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getBfmDemodReport()->setChannelSampleRate(m_inputSampleRate);
|
||||
response.getBfmDemodReport()->setSquelch(m_basebandSink->getSquelchState() > 0 ? 1 : 0);
|
||||
response.getBfmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
|
||||
response.getBfmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
|
||||
response.getBfmDemodReport()->setPilotLocked(getPilotLock() ? 1 : 0);
|
||||
response.getBfmDemodReport()->setPilotPowerDb(CalcDb::dbPower(getPilotLevel()));
|
||||
|
||||
|
@ -26,27 +26,15 @@
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "dsp/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/phaselock.h"
|
||||
#include "dsp/filterrc.h"
|
||||
#include "dsp/phasediscri.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "rdsparser.h"
|
||||
#include "rdsdecoder.h"
|
||||
#include "rdsdemod.h"
|
||||
#include "bfmdemodbaseband.h"
|
||||
#include "bfmdemodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSink;
|
||||
class DownChannelizer;
|
||||
|
||||
namespace SWGSDRangel {
|
||||
class SWGRDSReport;
|
||||
@ -78,55 +66,12 @@ 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 MsgReportChannelSampleRateChanged : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
|
||||
static MsgReportChannelSampleRateChanged* create(int sampleRate)
|
||||
{
|
||||
return new MsgReportChannelSampleRateChanged(sampleRate);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
|
||||
MsgReportChannelSampleRateChanged(int sampleRate) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate)
|
||||
{ }
|
||||
};
|
||||
|
||||
BFMDemod(DeviceAPI *deviceAPI);
|
||||
virtual ~BFMDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_basebandSink->setSpectrumSink(spectrumSink); }
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_basebandSink->setMessageQueueToGUI(messageQueue); }
|
||||
|
||||
int getSampleRate() const { return m_inputSampleRate; }
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
@ -149,36 +94,20 @@ public:
|
||||
return m_settings.m_inputFrequencyOffset;
|
||||
}
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
double getMagSq() const { return m_basebandSink->getMagSq(); }
|
||||
|
||||
bool getPilotLock() const { return m_pilotPLL.locked(); }
|
||||
Real getPilotLevel() const { return m_pilotPLL.get_pilot_level(); }
|
||||
bool getPilotLock() const { return m_basebandSink->getPilotLock(); }
|
||||
Real getPilotLevel() const { return m_basebandSink->getPilotLevel(); }
|
||||
|
||||
Real getDecoderQua() const { return m_rdsDecoder.m_qua; }
|
||||
bool getDecoderSynced() const { return m_rdsDecoder.synced(); }
|
||||
Real getDemodAcc() const { return m_rdsDemod.m_report.acc; }
|
||||
Real getDemodQua() const { return m_rdsDemod.m_report.qua; }
|
||||
Real getDemodFclk() const { return m_rdsDemod.m_report.fclk; }
|
||||
Real getDecoderQua() const { return m_basebandSink->getDecoderQua(); }
|
||||
bool getDecoderSynced() const { return m_basebandSink->getDecoderSynced(); }
|
||||
Real getDemodAcc() const { return m_basebandSink->getDemodAcc(); }
|
||||
Real getDemodQua() const { return m_basebandSink->getDemodQua(); }
|
||||
Real getDemodFclk() const { return m_basebandSink->getDemodFclk(); }
|
||||
|
||||
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;
|
||||
}
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RDSParser& getRDSParser() { return m_rdsParser; }
|
||||
RDSParser& getRDSParser() { return m_basebandSink->getRDSParser(); }
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
@ -203,104 +132,23 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
static int requiredBW(int rfBW)
|
||||
{
|
||||
if (rfBW <= 48000) {
|
||||
return 48000;
|
||||
} else {
|
||||
return (3*rfBW)/2;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
RSRunning
|
||||
};
|
||||
|
||||
DeviceAPI *m_deviceAPI;
|
||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
||||
DownChannelizer* m_channelizer;
|
||||
|
||||
int m_inputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
BFMDemodBaseband* m_basebandSink;
|
||||
BFMDemodSettings m_settings;
|
||||
quint32 m_audioSampleRate;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator; //!< Interpolator between fixed demod bandwidth and audio bandwidth (rational)
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
|
||||
Interpolator m_interpolatorStereo; //!< Twin Interpolator for stereo subcarrier
|
||||
Real m_interpolatorStereoDistance;
|
||||
Real m_interpolatorStereoDistanceRemain;
|
||||
|
||||
Interpolator m_interpolatorRDS; //!< Twin Interpolator for stereo subcarrier
|
||||
Real m_interpolatorRDSDistance;
|
||||
Real m_interpolatorRDSDistanceRemain;
|
||||
|
||||
Lowpass<Real> m_lowpass;
|
||||
fftfilt* m_rfFilter;
|
||||
static const int filtFftLen = 1024;
|
||||
|
||||
Real m_squelchLevel;
|
||||
int m_squelchState;
|
||||
|
||||
Real m_m1Arg; //!> x^-1 real sample
|
||||
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
|
||||
BasebandSampleSink* m_sampleSink;
|
||||
AudioFifo m_audioFifo;
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
RDSPhaseLock m_pilotPLL;
|
||||
Real m_pilotPLLSamples[4];
|
||||
|
||||
RDSDemod m_rdsDemod;
|
||||
RDSDecoder m_rdsDecoder;
|
||||
RDSParser m_rdsParser;
|
||||
|
||||
LowPassFilterRC m_deemphasisFilterX;
|
||||
LowPassFilterRC m_deemphasisFilterY;
|
||||
static const Real default_deemphasis;
|
||||
|
||||
Real m_fmExcursion;
|
||||
static const int default_excursion = 750000; // +/- 75 kHz
|
||||
|
||||
PhaseDiscriminators m_phaseDiscri;
|
||||
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||
|
||||
static const int m_udpBlockSize;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const BFMDemodSettings& settings, bool force = false);
|
||||
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
190
plugins/channelrx/demodbfm/bfmdemodbaseband.cpp
Normal file
190
plugins/channelrx/demodbfm/bfmdemodbaseband.cpp
Normal file
@ -0,0 +1,190 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "bfmdemodreport.h"
|
||||
#include "bfmdemodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(BFMDemodBaseband::MsgConfigureBFMDemodBaseband, Message)
|
||||
|
||||
BFMDemodBaseband::BFMDemodBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownSampleChannelizer(&m_sink);
|
||||
|
||||
qDebug("BFMDemodBaseband::BFMDemodBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&BFMDemodBaseband::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()));
|
||||
}
|
||||
|
||||
BFMDemodBaseband::~BFMDemodBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void BFMDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void BFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void BFMDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
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 BFMDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BFMDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureBFMDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureBFMDemodBaseband& cfg = (MsgConfigureBFMDemodBaseband&) cmd;
|
||||
qDebug() << "BFMDemodBaseband::handleMessage: MsgConfigureBFMDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "BFMDemodBaseband::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());
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
BFMDemodReport::MsgReportChannelSampleRateChanged *msg = BFMDemodReport::MsgReportChannelSampleRateChanged::create(m_channelizer->getChannelSampleRate());
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BFMDemodBaseband::applySettings(const BFMDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|
||||
|| (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(BFMDemodSettings::requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
BFMDemodReport::MsgReportChannelSampleRateChanged *msg = BFMDemodReport::MsgReportChannelSampleRateChanged::create(m_channelizer->getChannelSampleRate());
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
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_sink.applySettings(settings, force);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int BFMDemodBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
|
||||
void BFMDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
BFMDemodReport::MsgReportChannelSampleRateChanged *msg = BFMDemodReport::MsgReportChannelSampleRateChanged::create(m_channelizer->getChannelSampleRate());
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
}
|
101
plugins/channelrx/demodbfm/bfmdemodbaseband.h
Normal file
101
plugins/channelrx/demodbfm/bfmdemodbaseband.h
Normal file
@ -0,0 +1,101 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_BFMDEMODBASEBAND_H
|
||||
#define INCLUDE_BFMDEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "bfmdemodsink.h"
|
||||
|
||||
class DownSampleChannelizer;
|
||||
|
||||
class BFMDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureBFMDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const BFMDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureBFMDemodBaseband* create(const BFMDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureBFMDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
BFMDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureBFMDemodBaseband(const BFMDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
BFMDemodBaseband();
|
||||
~BFMDemodBaseband();
|
||||
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 setBasebandSampleRate(int sampleRate);
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_sink.setSpectrumSink(spectrumSink); }
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
|
||||
|
||||
unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); }
|
||||
int getSquelchState() const { return m_sink.getSquelchState(); }
|
||||
double getMagSq() const { return m_sink.getMagSq(); }
|
||||
bool getPilotLock() const { return m_sink.getPilotLock(); }
|
||||
Real getPilotLevel() const { return m_sink.getPilotLevel(); }
|
||||
Real getDecoderQua() const { return m_sink.getDecoderQua(); }
|
||||
bool getDecoderSynced() const { return m_sink.getDecoderSynced(); }
|
||||
Real getDemodAcc() const { return m_sink.getDemodAcc(); }
|
||||
Real getDemodQua() const { return m_sink.getDemodQua(); }
|
||||
Real getDemodFclk() const { return m_sink.getDemodFclk(); }
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
|
||||
RDSParser& getRDSParser() { return m_sink.getRDSParser(); }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownSampleChannelizer *m_channelizer;
|
||||
BFMDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
BFMDemodSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
|
||||
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const BFMDemodSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_BFMDEMODBASEBAND_H
|
@ -41,6 +41,7 @@
|
||||
#include "gui/audioselectdialog.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "bfmdemodreport.h"
|
||||
#include "bfmdemodsettings.h"
|
||||
#include "bfmdemod.h"
|
||||
#include "rdstmc.h"
|
||||
@ -111,11 +112,11 @@ bool BFMDemodGUI::deserialize(const QByteArray& data)
|
||||
|
||||
bool BFMDemodGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (BFMDemod::MsgReportChannelSampleRateChanged::match(message))
|
||||
if (BFMDemodReport::MsgReportChannelSampleRateChanged::match(message))
|
||||
{
|
||||
BFMDemod::MsgReportChannelSampleRateChanged& report = (BFMDemod::MsgReportChannelSampleRateChanged&) message;
|
||||
BFMDemodReport::MsgReportChannelSampleRateChanged& report = (BFMDemodReport::MsgReportChannelSampleRateChanged&) message;
|
||||
m_rate = report.getSampleRate();
|
||||
qDebug("BFMDemodGUI::handleMessage: MsgReportChannelSampleRateChanged: %d S/s", m_rate);
|
||||
qDebug("BFMDemodGUI::handleMessage: BFMDemodReport::MsgReportChannelSampleRateChanged: %d S/s", m_rate);
|
||||
ui->glSpectrum->setCenterFrequency(m_rate / 4);
|
||||
ui->glSpectrum->setSampleRate(m_rate / 2);
|
||||
return true;
|
||||
@ -389,7 +390,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
|
||||
m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum);
|
||||
m_bfmDemod = (BFMDemod*) rxChannel; //new BFMDemod(m_deviceUISet->m_deviceSourceAPI);
|
||||
m_bfmDemod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
m_bfmDemod->setSampleSink(m_spectrumVis);
|
||||
m_bfmDemod->setSpectrumSink(m_spectrumVis);
|
||||
|
||||
ui->glSpectrum->setCenterFrequency(m_rate / 4);
|
||||
ui->glSpectrum->setSampleRate(m_rate / 2);
|
||||
@ -455,11 +456,6 @@ void BFMDemodGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
BFMDemod::MsgConfigureChannelizer *msgChan = BFMDemod::MsgConfigureChannelizer::create(
|
||||
BFMDemod::requiredBW(m_settings.m_rfBandwidth),
|
||||
m_settings.m_inputFrequencyOffset);
|
||||
m_bfmDemod->getInputMessageQueue()->push(msgChan);
|
||||
|
||||
BFMDemod::MsgConfigureBFMDemod* msgConfig = BFMDemod::MsgConfigureBFMDemod::create( m_settings, force);
|
||||
m_bfmDemod->getInputMessageQueue()->push(msgConfig);
|
||||
}
|
||||
|
26
plugins/channelrx/demodbfm/bfmdemodreport.cpp
Normal file
26
plugins/channelrx/demodbfm/bfmdemodreport.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 "bfmdemodreport.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(BFMDemodReport::MsgReportChannelSampleRateChanged, Message)
|
||||
|
||||
BFMDemodReport::BFMDemodReport()
|
||||
{}
|
||||
|
||||
BFMDemodReport::~BFMDemodReport()
|
||||
{}
|
50
plugins/channelrx/demodbfm/bfmdemodreport.h
Normal file
50
plugins/channelrx/demodbfm/bfmdemodreport.h
Normal file
@ -0,0 +1,50 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_BFMDEMODREPORT_H
|
||||
#define INCLUDE_BFMDEMODREPORT_H
|
||||
|
||||
#include "util/message.h"
|
||||
|
||||
class BFMDemodReport
|
||||
{
|
||||
public:
|
||||
class MsgReportChannelSampleRateChanged : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
|
||||
static MsgReportChannelSampleRateChanged* create(int sampleRate)
|
||||
{
|
||||
return new MsgReportChannelSampleRateChanged(sampleRate);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
|
||||
MsgReportChannelSampleRateChanged(int sampleRate) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate)
|
||||
{ }
|
||||
};
|
||||
|
||||
BFMDemodReport();
|
||||
~BFMDemodReport();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_BFMDEMODREPORT_H
|
@ -58,6 +58,15 @@ struct BFMDemodSettings
|
||||
|
||||
static int getRFBW(int index);
|
||||
static int getRFBWIndex(int rfbw);
|
||||
|
||||
static int requiredBW(int rfBW)
|
||||
{
|
||||
if (rfBW <= 48000) {
|
||||
return 48000;
|
||||
} else {
|
||||
return (3*rfBW)/2;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
357
plugins/channelrx/demodbfm/bfmdemodsink.cpp
Normal file
357
plugins/channelrx/demodbfm/bfmdemodsink.cpp
Normal file
@ -0,0 +1,357 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "boost/format.hpp"
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
|
||||
#include "audio/audiooutput.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "rdsparser.h"
|
||||
#include "bfmdemodsink.h"
|
||||
|
||||
const Real BFMDemodSink::default_deemphasis = 50.0; // 50 us
|
||||
const int BFMDemodSink::default_excursion = 750000; // +/- 75 kHz
|
||||
|
||||
BFMDemodSink::BFMDemodSink() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_audioSampleRate(48000),
|
||||
m_audioBufferFill(0),
|
||||
m_audioFifo(48000),
|
||||
m_pilotPLL(19000/384000, 50/384000, 0.01),
|
||||
m_deemphasisFilterX(default_deemphasis * 48000 * 1.0e-6),
|
||||
m_deemphasisFilterY(default_deemphasis * 48000 * 1.0e-6),
|
||||
m_fmExcursion(default_excursion)
|
||||
{
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
|
||||
m_squelchLevel = 0;
|
||||
m_squelchState = 0;
|
||||
|
||||
m_interpolatorDistance = 0.0f;
|
||||
m_interpolatorDistanceRemain = 0.0f;
|
||||
|
||||
m_interpolatorRDSDistance = 0.0f;
|
||||
m_interpolatorRDSDistanceRemain = 0.0f;
|
||||
|
||||
m_interpolatorStereoDistance = 0.0f;
|
||||
m_interpolatorStereoDistanceRemain = 0.0f;
|
||||
|
||||
m_spectrumSink = nullptr;
|
||||
m_m1Arg = 0;
|
||||
|
||||
m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, filtFftLen);
|
||||
|
||||
m_deemphasisFilterX.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
|
||||
m_deemphasisFilterY.configure(default_deemphasis * m_audioSampleRate * 1.0e-6);
|
||||
m_phaseDiscri.setFMScaling(384000/m_fmExcursion);
|
||||
|
||||
m_audioBuffer.resize(16384);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
BFMDemodSink::~BFMDemodSink()
|
||||
{
|
||||
delete m_rfFilter;
|
||||
}
|
||||
|
||||
void BFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
Complex ci, cs, cr;
|
||||
fftfilt::cmplx *rf;
|
||||
int rf_out;
|
||||
double msq;
|
||||
Real demod;
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
||||
{
|
||||
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
rf_out = m_rfFilter->runFilt(c, &rf); // filter RF before demod
|
||||
|
||||
for (int i =0 ; i <rf_out; i++)
|
||||
{
|
||||
msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag();
|
||||
m_magsqSum += msq;
|
||||
|
||||
if (msq > m_magsqPeak) {
|
||||
m_magsqPeak = msq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (msq >= m_squelchLevel)
|
||||
{
|
||||
if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate
|
||||
m_squelchState++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchState > 0) {
|
||||
m_squelchState--;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_squelchState > m_settings.m_rfBandwidth / 20) { // squelch open
|
||||
demod = m_phaseDiscri.phaseDiscriminator(rf[i]);
|
||||
} else {
|
||||
demod = 0;
|
||||
}
|
||||
|
||||
if (!m_settings.m_showPilot) {
|
||||
m_sampleBuffer.push_back(Sample(demod * SDR_RX_SCALEF, 0.0));
|
||||
}
|
||||
|
||||
if (m_settings.m_rdsActive)
|
||||
{
|
||||
//Complex r(demod * 2.0 * std::cos(3.0 * m_pilotPLLSamples[3]), 0.0);
|
||||
Complex r(demod * 2.0 * std::cos(3.0 * m_pilotPLLSamples[3]), 0.0);
|
||||
|
||||
if (m_interpolatorRDS.decimate(&m_interpolatorRDSDistanceRemain, r, &cr))
|
||||
{
|
||||
bool bit;
|
||||
|
||||
if (m_rdsDemod.process(cr.real(), bit))
|
||||
{
|
||||
if (m_rdsDecoder.frameSync(bit)) {
|
||||
m_rdsParser.parseGroup(m_rdsDecoder.getGroup());
|
||||
}
|
||||
}
|
||||
|
||||
m_interpolatorRDSDistanceRemain += m_interpolatorRDSDistance;
|
||||
}
|
||||
}
|
||||
|
||||
Real sampleStereo = 0.0f;
|
||||
|
||||
// Process stereo if stereo mode is selected
|
||||
|
||||
if (m_settings.m_audioStereo)
|
||||
{
|
||||
m_pilotPLL.process(demod, m_pilotPLLSamples);
|
||||
|
||||
if (m_settings.m_showPilot) {
|
||||
m_sampleBuffer.push_back(Sample(m_pilotPLLSamples[1] * SDR_RX_SCALEF, 0.0)); // debug 38 kHz pilot
|
||||
}
|
||||
|
||||
if (m_settings.m_lsbStereo)
|
||||
{
|
||||
// 1.17 * 0.7 = 0.819
|
||||
Complex s(demod * m_pilotPLLSamples[1], demod * m_pilotPLLSamples[2]);
|
||||
|
||||
if (m_interpolatorStereo.decimate(&m_interpolatorStereoDistanceRemain, s, &cs))
|
||||
{
|
||||
sampleStereo = cs.real() + cs.imag();
|
||||
m_interpolatorStereoDistanceRemain += m_interpolatorStereoDistance;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Complex s(demod * 1.17 * m_pilotPLLSamples[1], 0);
|
||||
|
||||
if (m_interpolatorStereo.decimate(&m_interpolatorStereoDistanceRemain, s, &cs))
|
||||
{
|
||||
sampleStereo = cs.real();
|
||||
m_interpolatorStereoDistanceRemain += m_interpolatorStereoDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Complex e(demod, 0);
|
||||
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci))
|
||||
{
|
||||
if (m_settings.m_audioStereo)
|
||||
{
|
||||
Real deemph_l, deemph_r; // Pre-emphasis is applied on each channel before multiplexing
|
||||
m_deemphasisFilterX.process(ci.real() + sampleStereo, deemph_l);
|
||||
m_deemphasisFilterY.process(ci.real() - sampleStereo, deemph_r);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(deemph_l * (1<<12) * m_settings.m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(deemph_r * (1<<12) * m_settings.m_volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
Real deemph;
|
||||
m_deemphasisFilterX.process(ci.real(), deemph);
|
||||
quint16 sample = (qint16)(deemph * (1<<12) * m_settings.m_volume);
|
||||
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("BFMDemodSink::feed: %u/%u audio samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_audioBufferFill > 0)
|
||||
{
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill) {
|
||||
qDebug("BFMDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
void BFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("BFMDemodSink::applyAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_interpolator.create(16, m_channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_channelSampleRate / sampleRate;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, m_channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) m_channelSampleRate / sampleRate;
|
||||
m_interpolatorStereoDistance = (Real) m_channelSampleRate / (Real) sampleRate;
|
||||
|
||||
m_deemphasisFilterX.configure(default_deemphasis * sampleRate * 1.0e-6);
|
||||
m_deemphasisFilterY.configure(default_deemphasis * sampleRate * 1.0e-6);
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void BFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "BFMDemodSink::applyChannelSettings:"
|
||||
<< " channelSampleRate: " << channelSampleRate
|
||||
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
||||
|
||||
if((channelFrequencyOffset != m_channelFrequencyOffset) ||
|
||||
(channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
m_pilotPLL.configure(19000.0/channelSampleRate, 50.0/channelSampleRate, 0.01);
|
||||
|
||||
m_interpolator.create(16, channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) channelSampleRate / m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) channelSampleRate / m_audioSampleRate;
|
||||
m_interpolatorStereoDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorRDS.create(4, channelSampleRate, 600.0);
|
||||
m_interpolatorRDSDistanceRemain = (Real) channelSampleRate / 250000.0;
|
||||
m_interpolatorRDSDistance = (Real) channelSampleRate / 250000.0;
|
||||
|
||||
Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
|
||||
Real hiCut = (m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_phaseDiscri.setFMScaling(channelSampleRate / m_fmExcursion);
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void BFMDemodSink::applySettings(const BFMDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "BFMDemodSink::applySettings: MsgConfigureBFMDemod:"
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_rfBandwidth: " << settings.m_rfBandwidth
|
||||
<< " m_afBandwidth: " << settings.m_afBandwidth
|
||||
<< " m_volume: " << settings.m_volume
|
||||
<< " m_squelch: " << settings.m_squelch
|
||||
<< " m_audioStereo: " << settings.m_audioStereo
|
||||
<< " m_lsbStereo: " << settings.m_lsbStereo
|
||||
<< " m_showPilot: " << settings.m_showPilot
|
||||
<< " m_rdsActive: " << settings.m_rdsActive
|
||||
<< " m_audioDeviceName: " << settings.m_audioDeviceName
|
||||
<< " m_streamIndex: " << settings.m_streamIndex
|
||||
<< " m_useReverseAPI: " << settings.m_useReverseAPI
|
||||
<< " force: " << force;
|
||||
|
||||
if ((settings.m_audioStereo && (settings.m_audioStereo != m_settings.m_audioStereo)) || force) {
|
||||
m_pilotPLL.configure(19000.0/m_channelSampleRate, 50.0/m_channelSampleRate, 0.01);
|
||||
}
|
||||
|
||||
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
|
||||
{
|
||||
m_interpolator.create(16, m_channelSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_channelSampleRate / m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorStereo.create(16, m_channelSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorStereoDistanceRemain = (Real) m_channelSampleRate / m_audioSampleRate;
|
||||
m_interpolatorStereoDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
|
||||
|
||||
m_interpolatorRDS.create(4, m_channelSampleRate, 600.0);
|
||||
m_interpolatorRDSDistanceRemain = (Real) m_channelSampleRate / 250000.0;
|
||||
m_interpolatorRDSDistance = (Real) m_channelSampleRate / 250000.0;
|
||||
|
||||
m_lowpass.create(21, m_audioSampleRate, settings.m_afBandwidth);
|
||||
}
|
||||
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
|
||||
Real hiCut = (settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_phaseDiscri.setFMScaling(m_channelSampleRate / m_fmExcursion);
|
||||
}
|
||||
|
||||
if ((settings.m_squelch != m_settings.m_squelch) || force) {
|
||||
m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
162
plugins/channelrx/demodbfm/bfmdemodsink.h
Normal file
162
plugins/channelrx/demodbfm/bfmdemodsink.h
Normal file
@ -0,0 +1,162 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_BFMDEMODSINK_H
|
||||
#define INCLUDE_BFMDEMODSINK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "dsp/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/phaselock.h"
|
||||
#include "dsp/filterrc.h"
|
||||
#include "dsp/phasediscri.h"
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
#include "rdsparser.h"
|
||||
#include "rdsdecoder.h"
|
||||
#include "rdsdemod.h"
|
||||
#include "bfmdemodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
|
||||
class BFMDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
BFMDemodSink();
|
||||
~BFMDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; }
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
bool getPilotLock() const { return m_pilotPLL.locked(); }
|
||||
Real getPilotLevel() const { return m_pilotPLL.get_pilot_level(); }
|
||||
|
||||
Real getDecoderQua() const { return m_rdsDecoder.m_qua; }
|
||||
bool getDecoderSynced() const { return m_rdsDecoder.synced(); }
|
||||
Real getDemodAcc() const { return m_rdsDemod.m_report.acc; }
|
||||
Real getDemodQua() const { return m_rdsDemod.m_report.qua; }
|
||||
Real getDemodFclk() const { return m_rdsDemod.m_report.fclk; }
|
||||
int getSquelchState() const { return m_squelchState; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
RDSParser& getRDSParser() { return m_rdsParser; }
|
||||
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
void applySettings(const BFMDemodSettings& settings, bool force = false);
|
||||
|
||||
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;
|
||||
BFMDemodSettings m_settings;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator; //!< Interpolator between fixed demod bandwidth and audio bandwidth (rational)
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
|
||||
Interpolator m_interpolatorStereo; //!< Twin Interpolator for stereo subcarrier
|
||||
Real m_interpolatorStereoDistance;
|
||||
Real m_interpolatorStereoDistanceRemain;
|
||||
|
||||
Interpolator m_interpolatorRDS; //!< Twin Interpolator for stereo subcarrier
|
||||
Real m_interpolatorRDSDistance;
|
||||
Real m_interpolatorRDSDistanceRemain;
|
||||
|
||||
Lowpass<Real> m_lowpass;
|
||||
fftfilt* m_rfFilter;
|
||||
static const int filtFftLen = 1024;
|
||||
|
||||
Real m_squelchLevel;
|
||||
int m_squelchState;
|
||||
|
||||
Real m_m1Arg; //!> x^-1 real sample
|
||||
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
|
||||
RDSPhaseLock m_pilotPLL;
|
||||
Real m_pilotPLLSamples[4];
|
||||
|
||||
RDSDemod m_rdsDemod;
|
||||
RDSDecoder m_rdsDecoder;
|
||||
RDSParser m_rdsParser;
|
||||
|
||||
LowPassFilterRC m_deemphasisFilterX;
|
||||
LowPassFilterRC m_deemphasisFilterY;
|
||||
static const Real default_deemphasis;
|
||||
|
||||
Real m_fmExcursion;
|
||||
static const int default_excursion;
|
||||
|
||||
PhaseDiscriminators m_phaseDiscri;
|
||||
|
||||
BasebandSampleSink *m_spectrumSink;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_BFMDEMODSINK_H
|
@ -30,7 +30,7 @@
|
||||
|
||||
const PluginDescriptor BFMPlugin::m_pluginDescriptor = {
|
||||
QString("Broadcast FM Demodulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.2"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
@ -10,6 +10,9 @@ set(datv_SOURCES
|
||||
datvideostream.cpp
|
||||
datvudpstream.cpp
|
||||
datvideorender.cpp
|
||||
datvdemodreport.cpp
|
||||
datvdemodsink.cpp
|
||||
datvdemodbaseband.cpp
|
||||
leansdr/dvb.cpp
|
||||
leansdr/filtergen.cpp
|
||||
leansdr/framework.cpp
|
||||
@ -29,6 +32,9 @@ set(datv_HEADERS
|
||||
datvideorender.h
|
||||
datvconstellation.h
|
||||
datvdvbs2constellation.h
|
||||
datvdemodreport.h
|
||||
datvdemodsink.h
|
||||
datvdemodbaseband.h
|
||||
leansdr/dvb.h
|
||||
leansdr/dvbs2.h
|
||||
leansdr/filtergen.h
|
||||
|
@ -55,7 +55,7 @@ DATVDemod::DATVDemod(DeviceAPI *deviceAPI) :
|
||||
m_modcodModulation(-1),
|
||||
m_modcodCodeRate(-1),
|
||||
m_enmModulation(DATVDemodSettings::BPSK /*DATV_FM1*/),
|
||||
m_sampleRate(1024000),
|
||||
m_channelSampleRate(1024000),
|
||||
m_objSettingsMutex(QMutex::NonRecursive)
|
||||
{
|
||||
setObjectName("DATVDemod");
|
||||
@ -556,12 +556,12 @@ void DATVDemod::InitDATVFramework()
|
||||
<< " RollOff: " << m_settings.m_rollOff
|
||||
<< " Viterbi: " << m_settings.m_viterbi
|
||||
<< " Excursion: " << m_settings.m_excursion
|
||||
<< " Sample rate: " << m_sampleRate;
|
||||
<< " Sample rate: " << m_channelSampleRate;
|
||||
|
||||
m_objCfg.standard = m_settings.m_standard;
|
||||
|
||||
m_objCfg.fec = (leansdr::code_rate) getLeanDVBCodeRateFromDATV(m_settings.m_fec);
|
||||
m_objCfg.Fs = (float) m_sampleRate;
|
||||
m_objCfg.Fs = (float) m_channelSampleRate;
|
||||
m_objCfg.Fm = (float) m_settings.m_symbolRate;
|
||||
m_objCfg.fastlock = m_settings.m_fastLock;
|
||||
|
||||
@ -886,14 +886,14 @@ void DATVDemod::InitDATVS2Framework()
|
||||
<< " RollOff: " << m_settings.m_rollOff
|
||||
<< " Viterbi: " << m_settings.m_viterbi
|
||||
<< " Excursion: " << m_settings.m_excursion
|
||||
<< " Sample rate: " << m_sampleRate ;
|
||||
<< " Sample rate: " << m_channelSampleRate ;
|
||||
|
||||
|
||||
|
||||
m_objCfg.standard = m_settings.m_standard;
|
||||
|
||||
m_objCfg.fec = (leansdr::code_rate) getLeanDVBCodeRateFromDATV(m_settings.m_fec);
|
||||
m_objCfg.Fs = (float) m_sampleRate;
|
||||
m_objCfg.Fs = (float) m_channelSampleRate;
|
||||
m_objCfg.Fm = (float) m_settings.m_symbolRate;
|
||||
m_objCfg.fastlock = m_settings.m_fastLock;
|
||||
|
||||
@ -1310,8 +1310,8 @@ bool DATVDemod::handleMessage(const Message& cmd)
|
||||
<< " m_intSampleRate: " << objNotif.getSampleRate()
|
||||
<< " m_intFrequencyOffset: " << objNotif.getFrequencyOffset();
|
||||
|
||||
m_inputFrequencyOffset = objNotif.getFrequencyOffset();
|
||||
applyChannelSettings(m_sampleRate /*objNotif.getSampleRate()*/, m_inputFrequencyOffset);
|
||||
m_channelFrequencyOffset = objNotif.getFrequencyOffset();
|
||||
applyChannelSettings(m_channelSampleRate /*objNotif.getSampleRate()*/, m_channelFrequencyOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1326,8 +1326,8 @@ bool DATVDemod::handleMessage(const Message& cmd)
|
||||
qDebug() << "DATVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getInputSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
|
||||
m_sampleRate = m_channelizer->getInputSampleRate();
|
||||
applyChannelSettings(m_sampleRate /*objNotif.getSampleRate()*/, m_inputFrequencyOffset);
|
||||
m_channelSampleRate = m_channelizer->getInputSampleRate();
|
||||
applyChannelSettings(m_channelSampleRate /*objNotif.getSampleRate()*/, m_channelFrequencyOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1341,9 +1341,9 @@ bool DATVDemod::handleMessage(const Message& cmd)
|
||||
}
|
||||
else if(DSPSignalNotification::match(cmd))
|
||||
{
|
||||
m_sampleRate = m_channelizer->getInputSampleRate();
|
||||
qDebug("DATVDemod::handleMessage: DSPSignalNotification: sent sample rate: %d", m_sampleRate);
|
||||
applyChannelSettings(m_sampleRate /*objNotif.getSampleRate()*/, m_inputFrequencyOffset);
|
||||
m_channelSampleRate = m_channelizer->getInputSampleRate();
|
||||
qDebug("DATVDemod::handleMessage: DSPSignalNotification: sent sample rate: %d", m_channelSampleRate);
|
||||
applyChannelSettings(m_channelSampleRate /*objNotif.getSampleRate()*/, m_channelFrequencyOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1362,7 +1362,7 @@ void DATVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffs
|
||||
bool callApplySettings = false;
|
||||
|
||||
if ((m_settings.m_centerFrequency != inputFrequencyOffset) ||
|
||||
(m_sampleRate != inputSampleRate) || force)
|
||||
(m_channelSampleRate != inputSampleRate) || force)
|
||||
{
|
||||
m_objNCO.setFreq(-(float) inputFrequencyOffset, (float) inputSampleRate);
|
||||
qDebug("DATVDemod::applyChannelSettings: NCO: IF: %d <> TF: %d ISR: %d",
|
||||
@ -1370,7 +1370,7 @@ void DATVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffs
|
||||
callApplySettings = true;
|
||||
}
|
||||
|
||||
if ((m_sampleRate != inputSampleRate) || force)
|
||||
if ((m_channelSampleRate != inputSampleRate) || force)
|
||||
{
|
||||
//Bandpass filter shaping
|
||||
Real fltLowCut = -((float) m_settings.m_rfBandwidth / 2.0) / (float) inputSampleRate;
|
||||
@ -1378,7 +1378,7 @@ void DATVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffs
|
||||
m_objRFFilter->create_filter(fltLowCut, fltHiCut);
|
||||
}
|
||||
|
||||
m_sampleRate = inputSampleRate;
|
||||
m_channelSampleRate = inputSampleRate;
|
||||
m_settings.m_centerFrequency = inputFrequencyOffset;
|
||||
|
||||
if (callApplySettings) {
|
||||
@ -1391,9 +1391,9 @@ void DATVDemod::applySettings(const DATVDemodSettings& settings, bool force)
|
||||
QString msg = tr("DATVDemod::applySettings: force: %1").arg(force);
|
||||
settings.debug(msg);
|
||||
|
||||
qDebug("DATVDemod::applySettings: m_sampleRate: %d", m_sampleRate);
|
||||
qDebug("DATVDemod::applySettings: m_channelSampleRate: %d", m_channelSampleRate);
|
||||
|
||||
if (m_sampleRate == 0) {
|
||||
if (m_channelSampleRate == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1435,15 +1435,15 @@ void DATVDemod::applySettings(const DATVDemodSettings& settings, bool force)
|
||||
{
|
||||
|
||||
//Bandpass filter shaping
|
||||
Real fltLowCut = -((float) settings.m_rfBandwidth / 2.0) / (float) m_sampleRate;
|
||||
Real fltHiCut = ((float) settings.m_rfBandwidth / 2.0) / (float) m_sampleRate;
|
||||
Real fltLowCut = -((float) settings.m_rfBandwidth / 2.0) / (float) m_channelSampleRate;
|
||||
Real fltHiCut = ((float) settings.m_rfBandwidth / 2.0) / (float) m_channelSampleRate;
|
||||
m_objRFFilter->create_filter(fltLowCut, fltHiCut);
|
||||
}
|
||||
|
||||
if ((m_settings.m_centerFrequency != settings.m_centerFrequency)
|
||||
|| force)
|
||||
{
|
||||
m_objNCO.setFreq(-(float) settings.m_centerFrequency, (float) m_sampleRate);
|
||||
m_objNCO.setFreq(-(float) settings.m_centerFrequency, (float) m_channelSampleRate);
|
||||
}
|
||||
|
||||
if ((m_settings.m_udpTS != settings.m_udpTS) || force) {
|
||||
@ -1468,7 +1468,7 @@ void DATVDemod::applySettings(const DATVDemodSettings& settings, bool force)
|
||||
|
||||
int DATVDemod::GetSampleRate()
|
||||
{
|
||||
return m_sampleRate;
|
||||
return m_channelSampleRate;
|
||||
}
|
||||
|
||||
DATVDemodSettings::DATVCodeRate DATVDemod::getCodeRateFromLeanDVBCode(int leanDVBCodeRate)
|
||||
|
@ -24,17 +24,6 @@ class DeviceAPI;
|
||||
class ThreadedBasebandSampleSink;
|
||||
class DownChannelizer;
|
||||
|
||||
#define rfFilterFftLength 1024
|
||||
|
||||
//LeanSDR
|
||||
#include "leansdr/framework.h"
|
||||
#include "leansdr/generic.h"
|
||||
#include "leansdr/dvb.h"
|
||||
#include "leansdr/filtergen.h"
|
||||
|
||||
#include "leansdr/hdlc.h"
|
||||
#include "leansdr/iess.h"
|
||||
|
||||
#include "datvconstellation.h"
|
||||
#include "datvdvbs2constellation.h"
|
||||
#include "datvvideoplayer.h"
|
||||
@ -59,68 +48,12 @@ class DownChannelizer;
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include "datvdemodbaseband.h"
|
||||
|
||||
// enum DATVModulation { BPSK, QPSK, PSK8, APSK16, APSK32, APSK64E, QAM16, QAM64, QAM256 };
|
||||
// enum dvb_version { DVB_S, DVB_S2 };
|
||||
// enum dvb_sampler { SAMP_NEAREST, SAMP_LINEAR, SAMP_RRC };
|
||||
|
||||
inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return std::max(d, 1); }
|
||||
|
||||
struct config
|
||||
{
|
||||
DATVDemodSettings::dvb_version standard;
|
||||
DATVDemodSettings::dvb_sampler sampler;
|
||||
|
||||
int buf_factor; // Buffer sizing
|
||||
float Fs; // Sampling frequency (Hz)
|
||||
float Fderot; // Shift the signal (Hz). Note: Ftune is faster
|
||||
int anf; // Number of auto notch filters
|
||||
bool cnr; // Measure CNR
|
||||
unsigned int decim; // Decimation, 0=auto
|
||||
float Fm; // QPSK symbol rate (Hz)
|
||||
leansdr::cstln_lut<leansdr::eucl_ss, 256>::predef constellation;
|
||||
leansdr::code_rate fec;
|
||||
float Ftune; // Bias frequency for the QPSK demodulator (Hz)
|
||||
bool allow_drift;
|
||||
bool fastlock;
|
||||
bool viterbi;
|
||||
bool hard_metric;
|
||||
bool resample;
|
||||
float resample_rej; // Approx. filter rejection in dB
|
||||
int rrc_steps; // Discrete steps between symbols, 0=auto
|
||||
float rrc_rej; // Approx. RRC filter rejection in dB
|
||||
float rolloff; // Roll-off 0..1
|
||||
bool hdlc; // Expect HDLC frames instead of MPEG packets
|
||||
bool packetized; // Output frames with 16-bit BE length
|
||||
float Finfo; // Desired refresh rate on fd_info (Hz)
|
||||
|
||||
config() :
|
||||
standard(DATVDemodSettings::DVB_S),
|
||||
sampler(DATVDemodSettings::SAMP_LINEAR),
|
||||
buf_factor(4),
|
||||
Fs(2.4e6),
|
||||
Fderot(0),
|
||||
anf(0),
|
||||
cnr(false),
|
||||
decim(0),
|
||||
Fm(2e6),
|
||||
constellation(leansdr::cstln_lut<leansdr::eucl_ss, 256>::QPSK),
|
||||
fec(leansdr::FEC12),
|
||||
Ftune(0),
|
||||
allow_drift(false),
|
||||
fastlock(true),
|
||||
viterbi(false),
|
||||
hard_metric(false),
|
||||
resample(false),
|
||||
resample_rej(10),
|
||||
rrc_steps(0),
|
||||
rrc_rej(10),
|
||||
rolloff(0.35),
|
||||
hdlc(false),
|
||||
packetized(false),
|
||||
Finfo(5)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class DATVDemod : public BasebandSampleSink, public ChannelAPI
|
||||
{
|
||||
@ -154,69 +87,23 @@ public:
|
||||
return m_settings.m_centerFrequency;
|
||||
}
|
||||
|
||||
bool SetTVScreen(TVScreen *objScreen);
|
||||
DATVideostream * SetVideoRender(DATVideoRender *objScreen);
|
||||
bool audioActive();
|
||||
bool audioDecodeOK();
|
||||
bool videoActive();
|
||||
bool videoDecodeOK();
|
||||
bool SetTVScreen(TVScreen *objScreen) { m_basebandSink->setTVScreen(objScreen); }
|
||||
DATVideostream *SetVideoRender(DATVideoRender *objScreen) { return m_basebandSink->SetVideoRender(objScreen); }
|
||||
bool audioActive() { return m_basebandSink->audioActive(); }
|
||||
bool audioDecodeOK() { return m_basebandSink->audioDecodeOK(); }
|
||||
bool videoActive() { return m_basebandSink->videoActive(); }
|
||||
bool videoDecodeOK() { return m_basebandSink->videoDecodeOK(); }
|
||||
|
||||
bool PlayVideo(bool blnStartStop);
|
||||
bool PlayVideo(bool blnStartStop) { m_basebandSink->PlayVideo(blnStartStop); }
|
||||
|
||||
void InitDATVParameters(
|
||||
int intMsps,
|
||||
int intRFBandwidth,
|
||||
int intCenterFrequency,
|
||||
DATVDemodSettings::dvb_version enmStandard,
|
||||
DATVDemodSettings::DATVModulation enmModulation,
|
||||
leansdr::code_rate enmFEC,
|
||||
int intSampleRate,
|
||||
int intSymbolRate,
|
||||
int intNotchFilters,
|
||||
bool blnAllowDrift,
|
||||
bool blnFastLock,
|
||||
DATVDemodSettings::dvb_sampler enmFilter,
|
||||
bool blnHardMetric,
|
||||
float fltRollOff,
|
||||
bool blnViterbi,
|
||||
int intEExcursion);
|
||||
|
||||
void CleanUpDATVFramework(bool blnRelease);
|
||||
int GetSampleRate();
|
||||
void InitDATVFramework();
|
||||
void InitDATVS2Framework();
|
||||
double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30
|
||||
int getModcodModulation() const { return m_modcodModulation; }
|
||||
int getModcodCodeRate() const { return m_modcodCodeRate; }
|
||||
bool isCstlnSetByModcod() const { return m_cstlnSetByModcod; }
|
||||
static DATVDemodSettings::DATVCodeRate getCodeRateFromLeanDVBCode(int leanDVBCodeRate);
|
||||
static DATVDemodSettings::DATVModulation getModulationFromLeanDVBCode(int leanDVBModulation);
|
||||
static int getLeanDVBCodeRateFromDATV(DATVDemodSettings::DATVCodeRate datvCodeRate);
|
||||
static int getLeanDVBModulationFromDATV(DATVDemodSettings::DATVModulation datvModulation);
|
||||
double getMagSq() const { return m_basebandSink->getMagSq(); } //!< Beware this is scaled to 2^30
|
||||
int getModcodModulation() const { return m_basebandSink->getModcodModulation(); }
|
||||
int getModcodCodeRate() const { return m_basebandSink->getModcodCodeRate(); }
|
||||
bool isCstlnSetByModcod() const { return m_basebandSink->isCstlnSetByModcod(); }
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
class MsgConfigureChannelizer : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int centerFrequency) {
|
||||
return new MsgConfigureChannelizer(centerFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_centerFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int centerFrequency) :
|
||||
Message(),
|
||||
m_centerFrequency(centerFrequency)
|
||||
{}
|
||||
};
|
||||
|
||||
class MsgConfigureDATVDemod : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
@ -240,198 +127,13 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportModcodCstlnChange : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
DATVDemodSettings::DATVModulation getModulation() const { return m_modulation; }
|
||||
DATVDemodSettings::DATVCodeRate getCodeRate() const { return m_codeRate; }
|
||||
|
||||
static MsgReportModcodCstlnChange* create(const DATVDemodSettings::DATVModulation& modulation,
|
||||
const DATVDemodSettings::DATVCodeRate& codeRate)
|
||||
{
|
||||
return new MsgReportModcodCstlnChange(modulation, codeRate);
|
||||
}
|
||||
|
||||
private:
|
||||
DATVDemodSettings::DATVModulation m_modulation;
|
||||
DATVDemodSettings::DATVCodeRate m_codeRate;
|
||||
|
||||
MsgReportModcodCstlnChange(
|
||||
const DATVDemodSettings::DATVModulation& modulation,
|
||||
const DATVDemodSettings::DATVCodeRate& codeRate
|
||||
) :
|
||||
Message(),
|
||||
m_modulation(modulation),
|
||||
m_codeRate(codeRate)
|
||||
{ }
|
||||
};
|
||||
|
||||
private:
|
||||
unsigned long m_lngExpectedReadIQ;
|
||||
long m_lngReadIQ;
|
||||
|
||||
//************** LEANDBV Parameters **************
|
||||
|
||||
unsigned long BUF_BASEBAND;
|
||||
unsigned long BUF_SYMBOLS;
|
||||
unsigned long BUF_BYTES;
|
||||
unsigned long BUF_MPEGBYTES;
|
||||
unsigned long BUF_PACKETS;
|
||||
unsigned long BUF_SLOW;
|
||||
|
||||
|
||||
//dvbs2
|
||||
unsigned long BUF_SLOTS;
|
||||
unsigned long BUF_FRAMES;
|
||||
unsigned long BUF_S2PACKETS;
|
||||
unsigned long S2_MAX_SYMBOLS;
|
||||
|
||||
//************** LEANDBV Scheduler ***************
|
||||
|
||||
leansdr::scheduler * m_objScheduler;
|
||||
struct config m_objCfg;
|
||||
|
||||
bool m_blnDVBInitialized;
|
||||
bool m_blnNeedConfigUpdate;
|
||||
|
||||
//LeanSDR Pipe Buffer
|
||||
// INPUT
|
||||
|
||||
leansdr::pipebuf<leansdr::cf32> *p_rawiq;
|
||||
leansdr::pipewriter<leansdr::cf32> *p_rawiq_writer;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_preprocessed;
|
||||
|
||||
// NOTCH FILTER
|
||||
leansdr::auto_notch<leansdr::f32> *r_auto_notch;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_autonotched;
|
||||
|
||||
// FREQUENCY CORRECTION : DEROTATOR
|
||||
leansdr::pipebuf<leansdr::cf32> *p_derot;
|
||||
leansdr::rotator<leansdr::f32> *r_derot;
|
||||
|
||||
// CNR ESTIMATION
|
||||
leansdr::pipebuf<leansdr::f32> *p_cnr;
|
||||
leansdr::cnr_fft<leansdr::f32> *r_cnr;
|
||||
|
||||
//FILTERING
|
||||
leansdr::fir_filter<leansdr::cf32,float> *r_resample;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_resampled;
|
||||
float *coeffs;
|
||||
int ncoeffs;
|
||||
|
||||
// OUTPUT PREPROCESSED DATA
|
||||
leansdr::sampler_interface<leansdr::f32> *sampler;
|
||||
float *coeffs_sampler;
|
||||
int ncoeffs_sampler;
|
||||
|
||||
leansdr::pipebuf<leansdr::eucl_ss> *p_symbols;
|
||||
leansdr::pipebuf<leansdr::f32> *p_freq;
|
||||
leansdr::pipebuf<leansdr::f32> *p_ss;
|
||||
leansdr::pipebuf<leansdr::f32> *p_mer;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_sampled;
|
||||
|
||||
//dvb-s2
|
||||
void *p_slots_dvbs2;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_cstln;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_cstln_pls;
|
||||
leansdr::pipebuf<int> *p_framelock;
|
||||
void *m_objDemodulatorDVBS2;
|
||||
void *p_fecframes;
|
||||
void *p_bbframes;
|
||||
void *p_s2_deinterleaver;
|
||||
void *r_fecdec;
|
||||
void *p_deframer;
|
||||
|
||||
//DECIMATION
|
||||
leansdr::pipebuf<leansdr::cf32> *p_decimated;
|
||||
leansdr::decimator<leansdr::cf32> *p_decim;
|
||||
|
||||
//PROCESSED DATA MONITORING
|
||||
leansdr::file_writer<leansdr::cf32> *r_ppout;
|
||||
|
||||
//GENERIC CONSTELLATION RECEIVER
|
||||
leansdr::cstln_receiver<leansdr::f32, leansdr::eucl_ss> *m_objDemodulator;
|
||||
|
||||
// DECONVOLUTION AND SYNCHRONIZATION
|
||||
leansdr::pipebuf<leansdr::u8> *p_bytes;
|
||||
leansdr::deconvol_sync_simple *r_deconv;
|
||||
leansdr::viterbi_sync *r;
|
||||
leansdr::pipebuf<leansdr::u8> *p_descrambled;
|
||||
leansdr::pipebuf<leansdr::u8> *p_frames;
|
||||
|
||||
leansdr::etr192_descrambler * r_etr192_descrambler;
|
||||
leansdr::hdlc_sync *r_sync;
|
||||
|
||||
leansdr::pipebuf<leansdr::u8> *p_mpegbytes;
|
||||
leansdr::pipebuf<int> *p_lock;
|
||||
leansdr::pipebuf<leansdr::u32> *p_locktime;
|
||||
leansdr::mpeg_sync<leansdr::u8, 0> *r_sync_mpeg;
|
||||
|
||||
|
||||
// DEINTERLEAVING
|
||||
leansdr::pipebuf<leansdr::rspacket<leansdr::u8> > *p_rspackets;
|
||||
leansdr::deinterleaver<leansdr::u8> *r_deinter;
|
||||
|
||||
// REED-SOLOMON
|
||||
leansdr::pipebuf<int> *p_vbitcount;
|
||||
leansdr::pipebuf<int> *p_verrcount;
|
||||
leansdr::pipebuf<leansdr::tspacket> *p_rtspackets;
|
||||
leansdr::rs_decoder<leansdr::u8, 0> *r_rsdec;
|
||||
|
||||
// BER ESTIMATION
|
||||
leansdr::pipebuf<float> *p_vber;
|
||||
leansdr::rate_estimator<float> *r_vber;
|
||||
|
||||
// DERANDOMIZATION
|
||||
leansdr::pipebuf<leansdr::tspacket> *p_tspackets;
|
||||
leansdr::derandomizer *r_derand;
|
||||
|
||||
|
||||
//OUTPUT
|
||||
leansdr::file_writer<leansdr::tspacket> *r_stdout;
|
||||
leansdr::datvvideoplayer<leansdr::tspacket> *r_videoplayer;
|
||||
|
||||
//CONSTELLATION
|
||||
leansdr::datvconstellation<leansdr::f32> *r_scope_symbols;
|
||||
leansdr::datvdvbs2constellation<leansdr::f32> *r_scope_symbols_dvbs2;
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
|
||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
||||
DownChannelizer* m_channelizer;
|
||||
|
||||
//*************** DATV PARAMETERS ***************
|
||||
TVScreen *m_objRegisteredTVScreen;
|
||||
DATVideoRender *m_objRegisteredVideoRender;
|
||||
DATVideostream *m_objVideoStream;
|
||||
DATVUDPStream m_udpStream;
|
||||
DATVideoRenderThread *m_objRenderThread;
|
||||
|
||||
// Audio
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
fftfilt * m_objRFFilter;
|
||||
NCO m_objNCO;
|
||||
|
||||
bool m_blnInitialized;
|
||||
bool m_blnRenderingVideo;
|
||||
bool m_blnStartStopVideo;
|
||||
bool m_cstlnSetByModcod;
|
||||
int m_modcodModulation;
|
||||
int m_modcodCodeRate;
|
||||
|
||||
DATVDemodSettings::DATVModulation m_enmModulation;
|
||||
|
||||
//DATVConfig m_objRunning;
|
||||
QThread *m_thread;
|
||||
DATVDemodBaseband* m_basebandSink;
|
||||
DATVDemodSettings m_settings;
|
||||
int m_sampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
MovingAverageUtil<double, double, 32> m_objMagSqAverage;
|
||||
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||
|
||||
QMutex m_objSettingsMutex;
|
||||
|
||||
//void ApplySettings();
|
||||
void applySettings(const DATVDemodSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
};
|
||||
|
169
plugins/channelrx/demoddatv/datvdemodbaseband.cpp
Normal file
169
plugins/channelrx/demoddatv/datvdemodbaseband.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "datvdemodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DATVDemodBaseband::MsgConfigureDATVDemodBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DATVDemodBaseband::MsgConfigureChannelizer, Message)
|
||||
|
||||
DATVDemodBaseband::DATVDemodBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
qDebug("DATVDemodBaseband::DATVDemodBaseband");
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownSampleChannelizer(&m_sink);
|
||||
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&DATVDemodBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
DATVDemodBaseband::~DATVDemodBaseband()
|
||||
{
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void DATVDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void DATVDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void DATVDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
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 DATVDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DATVDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureDATVDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureDATVDemodBaseband& cfg = (MsgConfigureDATVDemodBaseband&) cmd;
|
||||
qDebug() << "DATVDemodBaseband::handleMessage: MsgConfigureDATVDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "DATVDemodBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sinkSampleRate: " << cfg.getSinkSampleRate()
|
||||
<< "(requested) sinkCenterFrequency: " << cfg.getSinkCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSinkSampleRate(), cfg.getSinkCenterFrequency());
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "DATVDemodBaseband::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 DATVDemodBaseband::applySettings(const DATVDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug("DATVDemodBaseband::applySettings");
|
||||
|
||||
if ((settings.m_centerFrequency != m_settings.m_centerFrequency)|| force)
|
||||
{
|
||||
unsigned int desiredSampleRate = m_channelizer->getBasebandSampleRate();
|
||||
m_channelizer->setChannelization(desiredSampleRate, settings.m_centerFrequency);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
||||
|
||||
m_sink.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int DATVDemodBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
|
||||
void DATVDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("DATVDemodBaseband::setBasebandSampleRate: %d", sampleRate);
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
118
plugins/channelrx/demoddatv/datvdemodbaseband.h
Normal file
118
plugins/channelrx/demoddatv/datvdemodbaseband.h
Normal file
@ -0,0 +1,118 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_DATVDEMODBASEBAND_H
|
||||
#define INCLUDE_DATVDEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "datvdemodsink.h"
|
||||
|
||||
class DownSampleChannelizer;
|
||||
|
||||
class DATVDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureDATVDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const DATVDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureDATVDemodBaseband* create(const DATVDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureDATVDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
DATVDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureDATVDemodBaseband(const DATVDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSinkSampleRate() const { return m_sinkSampleRate; }
|
||||
int getSinkCenterFrequency() const { return m_sinkCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sinkSampleRate, int sinkCenterFrequency) {
|
||||
return new MsgConfigureChannelizer(sinkSampleRate, sinkCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sinkSampleRate;
|
||||
int m_sinkCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sinkSampleRate, int sinkCenterFrequency) :
|
||||
Message(),
|
||||
m_sinkSampleRate(sinkSampleRate),
|
||||
m_sinkCenterFrequency(sinkCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
DATVDemodBaseband();
|
||||
~DATVDemodBaseband();
|
||||
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;
|
||||
double getMagSq() const { return m_sink.getMagSq(); }
|
||||
void setTVScreen(TVScreen *tvScreen) { m_sink.setTVScreen(tvScreen); }
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); }
|
||||
void setBasebandSampleRate(int sampleRate); //!< To be used when supporting thread is stopped
|
||||
DATVideostream *SetVideoRender(DATVideoRender *objScreen) { return m_sink.SetVideoRender(objScreen); }
|
||||
bool audioActive() { return m_sink.audioActive(); }
|
||||
bool audioDecodeOK() { return m_sink.audioDecodeOK(); }
|
||||
bool videoActive() { return m_sink.videoActive(); }
|
||||
bool videoDecodeOK() { return m_sink.videoDecodeOK(); }
|
||||
bool PlayVideo(bool blnStartStop) { return m_sink.PlayVideo(blnStartStop); }
|
||||
|
||||
int getModcodModulation() const { return m_sink.getModcodModulation(); }
|
||||
int getModcodCodeRate() const { return m_sink.getModcodCodeRate(); }
|
||||
bool isCstlnSetByModcod() const { return m_sink.isCstlnSetByModcod(); }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownSampleChannelizer *m_channelizer;
|
||||
DATVDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
DATVDemodSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const DATVDemodSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_CHANNELANALYZERBASEBAND_H
|
26
plugins/channelrx/demoddatv/datvdemodreport.cpp
Normal file
26
plugins/channelrx/demoddatv/datvdemodreport.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 "datvdemodreport.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DATVDemodReport::MsgReportModcodCstlnChange, Message)
|
||||
|
||||
DATVDemodReport::DATVDemodReport()
|
||||
{}
|
||||
|
||||
DATVDemodReport::~DATVDemodReport()
|
||||
{}
|
58
plugins/channelrx/demoddatv/datvdemodreport.h
Normal file
58
plugins/channelrx/demoddatv/datvdemodreport.h
Normal file
@ -0,0 +1,58 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_DATVDEMODREPORT_H
|
||||
#define INCLUDE_DATVDEMODREPORT_H
|
||||
|
||||
#include "util/message.h"
|
||||
|
||||
#include "datvdemodsettings.h"
|
||||
|
||||
class DATVDemodReport
|
||||
{
|
||||
public:
|
||||
DATVDemodReport();
|
||||
~DATVDemodReport();
|
||||
class MsgReportModcodCstlnChange : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
DATVDemodSettings::DATVModulation getModulation() const { return m_modulation; }
|
||||
DATVDemodSettings::DATVCodeRate getCodeRate() const { return m_codeRate; }
|
||||
|
||||
static MsgReportModcodCstlnChange* create(const DATVDemodSettings::DATVModulation& modulation,
|
||||
const DATVDemodSettings::DATVCodeRate& codeRate)
|
||||
{
|
||||
return new MsgReportModcodCstlnChange(modulation, codeRate);
|
||||
}
|
||||
|
||||
private:
|
||||
DATVDemodSettings::DATVModulation m_modulation;
|
||||
DATVDemodSettings::DATVCodeRate m_codeRate;
|
||||
|
||||
MsgReportModcodCstlnChange(
|
||||
const DATVDemodSettings::DATVModulation& modulation,
|
||||
const DATVDemodSettings::DATVCodeRate& codeRate
|
||||
) :
|
||||
Message(),
|
||||
m_modulation(modulation),
|
||||
m_codeRate(codeRate)
|
||||
{ }
|
||||
};
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DATVDEMODREPORT_H
|
1480
plugins/channelrx/demoddatv/datvdemodsink.cpp
Normal file
1480
plugins/channelrx/demoddatv/datvdemodsink.cpp
Normal file
File diff suppressed because it is too large
Load Diff
316
plugins/channelrx/demoddatv/datvdemodsink.h
Normal file
316
plugins/channelrx/demoddatv/datvdemodsink.h
Normal file
@ -0,0 +1,316 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_DATVDEMODSINK_H
|
||||
#define INCLUDE_DATVDEMODSINK_H
|
||||
|
||||
//LeanSDR
|
||||
#include "leansdr/framework.h"
|
||||
#include "leansdr/generic.h"
|
||||
#include "leansdr/dvb.h"
|
||||
#include "leansdr/filtergen.h"
|
||||
|
||||
#include "leansdr/hdlc.h"
|
||||
#include "leansdr/iess.h"
|
||||
|
||||
#include "datvconstellation.h"
|
||||
#include "datvdvbs2constellation.h"
|
||||
#include "datvvideoplayer.h"
|
||||
#include "datvideostream.h"
|
||||
#include "datvudpstream.h"
|
||||
#include "datvideorender.h"
|
||||
#include "datvdemodsettings.h"
|
||||
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/downchannelizer.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/movingaverage.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
class TVScreen;
|
||||
class DATVideoRender;
|
||||
|
||||
class DATVDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
struct config
|
||||
{
|
||||
DATVDemodSettings::dvb_version standard;
|
||||
DATVDemodSettings::dvb_sampler sampler;
|
||||
|
||||
int buf_factor; // Buffer sizing
|
||||
float Fs; // Sampling frequency (Hz)
|
||||
float Fderot; // Shift the signal (Hz). Note: Ftune is faster
|
||||
int anf; // Number of auto notch filters
|
||||
bool cnr; // Measure CNR
|
||||
unsigned int decim; // Decimation, 0=auto
|
||||
float Fm; // QPSK symbol rate (Hz)
|
||||
leansdr::cstln_lut<leansdr::eucl_ss, 256>::predef constellation;
|
||||
leansdr::code_rate fec;
|
||||
float Ftune; // Bias frequency for the QPSK demodulator (Hz)
|
||||
bool allow_drift;
|
||||
bool fastlock;
|
||||
bool viterbi;
|
||||
bool hard_metric;
|
||||
bool resample;
|
||||
float resample_rej; // Approx. filter rejection in dB
|
||||
int rrc_steps; // Discrete steps between symbols, 0=auto
|
||||
float rrc_rej; // Approx. RRC filter rejection in dB
|
||||
float rolloff; // Roll-off 0..1
|
||||
bool hdlc; // Expect HDLC frames instead of MPEG packets
|
||||
bool packetized; // Output frames with 16-bit BE length
|
||||
float Finfo; // Desired refresh rate on fd_info (Hz)
|
||||
|
||||
config() :
|
||||
standard(DATVDemodSettings::DVB_S),
|
||||
sampler(DATVDemodSettings::SAMP_LINEAR),
|
||||
buf_factor(4),
|
||||
Fs(2.4e6),
|
||||
Fderot(0),
|
||||
anf(0),
|
||||
cnr(false),
|
||||
decim(0),
|
||||
Fm(2e6),
|
||||
constellation(leansdr::cstln_lut<leansdr::eucl_ss, 256>::QPSK),
|
||||
fec(leansdr::FEC12),
|
||||
Ftune(0),
|
||||
allow_drift(false),
|
||||
fastlock(true),
|
||||
viterbi(false),
|
||||
hard_metric(false),
|
||||
resample(false),
|
||||
resample_rej(10),
|
||||
rrc_steps(0),
|
||||
rrc_rej(10),
|
||||
rolloff(0.35),
|
||||
hdlc(false),
|
||||
packetized(false),
|
||||
Finfo(5)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
DATVDemodSink();
|
||||
~DATVDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
bool setTVScreen(TVScreen *objScreen);
|
||||
DATVideostream * SetVideoRender(DATVideoRender *objScreen);
|
||||
bool audioActive();
|
||||
bool audioDecodeOK();
|
||||
bool videoActive();
|
||||
bool videoDecodeOK();
|
||||
|
||||
bool PlayVideo(bool blnStartStop);
|
||||
|
||||
int GetSampleRate();
|
||||
double getMagSq() const { return m_objMagSqAverage; } //!< Beware this is scaled to 2^30
|
||||
int getModcodModulation() const { return m_modcodModulation; }
|
||||
int getModcodCodeRate() const { return m_modcodCodeRate; }
|
||||
bool isCstlnSetByModcod() const { return m_cstlnSetByModcod; }
|
||||
static DATVDemodSettings::DATVCodeRate getCodeRateFromLeanDVBCode(int leanDVBCodeRate);
|
||||
static DATVDemodSettings::DATVModulation getModulationFromLeanDVBCode(int leanDVBModulation);
|
||||
static int getLeanDVBCodeRateFromDATV(DATVDemodSettings::DATVCodeRate datvCodeRate);
|
||||
static int getLeanDVBModulationFromDATV(DATVDemodSettings::DATVModulation datvModulation);
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
|
||||
|
||||
void applySettings(const DATVDemodSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
|
||||
private:
|
||||
inline int decimation(float Fin, float Fout) { int d = Fin / Fout; return std::max(d, 1); }
|
||||
|
||||
void CleanUpDATVFramework(bool blnRelease);
|
||||
void InitDATVFramework();
|
||||
void InitDATVS2Framework();
|
||||
|
||||
unsigned long m_lngExpectedReadIQ;
|
||||
long m_lngReadIQ;
|
||||
|
||||
//************** LEANDBV Parameters **************
|
||||
|
||||
unsigned long BUF_BASEBAND;
|
||||
unsigned long BUF_SYMBOLS;
|
||||
unsigned long BUF_BYTES;
|
||||
unsigned long BUF_MPEGBYTES;
|
||||
unsigned long BUF_PACKETS;
|
||||
unsigned long BUF_SLOW;
|
||||
|
||||
|
||||
//dvbs2
|
||||
unsigned long BUF_SLOTS;
|
||||
unsigned long BUF_FRAMES;
|
||||
unsigned long BUF_S2PACKETS;
|
||||
unsigned long S2_MAX_SYMBOLS;
|
||||
|
||||
//************** LEANDBV Scheduler ***************
|
||||
|
||||
leansdr::scheduler * m_objScheduler;
|
||||
struct config m_objCfg;
|
||||
|
||||
bool m_blnDVBInitialized;
|
||||
bool m_blnNeedConfigUpdate;
|
||||
|
||||
//LeanSDR Pipe Buffer
|
||||
// INPUT
|
||||
|
||||
leansdr::pipebuf<leansdr::cf32> *p_rawiq;
|
||||
leansdr::pipewriter<leansdr::cf32> *p_rawiq_writer;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_preprocessed;
|
||||
|
||||
// NOTCH FILTER
|
||||
leansdr::auto_notch<leansdr::f32> *r_auto_notch;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_autonotched;
|
||||
|
||||
// FREQUENCY CORRECTION : DEROTATOR
|
||||
leansdr::pipebuf<leansdr::cf32> *p_derot;
|
||||
leansdr::rotator<leansdr::f32> *r_derot;
|
||||
|
||||
// CNR ESTIMATION
|
||||
leansdr::pipebuf<leansdr::f32> *p_cnr;
|
||||
leansdr::cnr_fft<leansdr::f32> *r_cnr;
|
||||
|
||||
//FILTERING
|
||||
leansdr::fir_filter<leansdr::cf32,float> *r_resample;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_resampled;
|
||||
float *coeffs;
|
||||
int ncoeffs;
|
||||
|
||||
// OUTPUT PREPROCESSED DATA
|
||||
leansdr::sampler_interface<leansdr::f32> *sampler;
|
||||
float *coeffs_sampler;
|
||||
int ncoeffs_sampler;
|
||||
|
||||
leansdr::pipebuf<leansdr::eucl_ss> *p_symbols;
|
||||
leansdr::pipebuf<leansdr::f32> *p_freq;
|
||||
leansdr::pipebuf<leansdr::f32> *p_ss;
|
||||
leansdr::pipebuf<leansdr::f32> *p_mer;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_sampled;
|
||||
|
||||
//dvb-s2
|
||||
void *p_slots_dvbs2;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_cstln;
|
||||
leansdr::pipebuf<leansdr::cf32> *p_cstln_pls;
|
||||
leansdr::pipebuf<int> *p_framelock;
|
||||
void *m_objDemodulatorDVBS2;
|
||||
void *p_fecframes;
|
||||
void *p_bbframes;
|
||||
void *p_s2_deinterleaver;
|
||||
void *r_fecdec;
|
||||
void *p_deframer;
|
||||
|
||||
//DECIMATION
|
||||
leansdr::pipebuf<leansdr::cf32> *p_decimated;
|
||||
leansdr::decimator<leansdr::cf32> *p_decim;
|
||||
|
||||
//PROCESSED DATA MONITORING
|
||||
leansdr::file_writer<leansdr::cf32> *r_ppout;
|
||||
|
||||
//GENERIC CONSTELLATION RECEIVER
|
||||
leansdr::cstln_receiver<leansdr::f32, leansdr::eucl_ss> *m_objDemodulator;
|
||||
|
||||
// DECONVOLUTION AND SYNCHRONIZATION
|
||||
leansdr::pipebuf<leansdr::u8> *p_bytes;
|
||||
leansdr::deconvol_sync_simple *r_deconv;
|
||||
leansdr::viterbi_sync *r;
|
||||
leansdr::pipebuf<leansdr::u8> *p_descrambled;
|
||||
leansdr::pipebuf<leansdr::u8> *p_frames;
|
||||
|
||||
leansdr::etr192_descrambler * r_etr192_descrambler;
|
||||
leansdr::hdlc_sync *r_sync;
|
||||
|
||||
leansdr::pipebuf<leansdr::u8> *p_mpegbytes;
|
||||
leansdr::pipebuf<int> *p_lock;
|
||||
leansdr::pipebuf<leansdr::u32> *p_locktime;
|
||||
leansdr::mpeg_sync<leansdr::u8, 0> *r_sync_mpeg;
|
||||
|
||||
|
||||
// DEINTERLEAVING
|
||||
leansdr::pipebuf<leansdr::rspacket<leansdr::u8> > *p_rspackets;
|
||||
leansdr::deinterleaver<leansdr::u8> *r_deinter;
|
||||
|
||||
// REED-SOLOMON
|
||||
leansdr::pipebuf<int> *p_vbitcount;
|
||||
leansdr::pipebuf<int> *p_verrcount;
|
||||
leansdr::pipebuf<leansdr::tspacket> *p_rtspackets;
|
||||
leansdr::rs_decoder<leansdr::u8, 0> *r_rsdec;
|
||||
|
||||
// BER ESTIMATION
|
||||
leansdr::pipebuf<float> *p_vber;
|
||||
leansdr::rate_estimator<float> *r_vber;
|
||||
|
||||
// DERANDOMIZATION
|
||||
leansdr::pipebuf<leansdr::tspacket> *p_tspackets;
|
||||
leansdr::derandomizer *r_derand;
|
||||
|
||||
|
||||
//OUTPUT
|
||||
leansdr::file_writer<leansdr::tspacket> *r_stdout;
|
||||
leansdr::datvvideoplayer<leansdr::tspacket> *r_videoplayer;
|
||||
|
||||
//CONSTELLATION
|
||||
leansdr::datvconstellation<leansdr::f32> *r_scope_symbols;
|
||||
leansdr::datvdvbs2constellation<leansdr::f32> *r_scope_symbols_dvbs2;
|
||||
|
||||
//*************** DATV PARAMETERS ***************
|
||||
TVScreen *m_objRegisteredTVScreen;
|
||||
DATVideoRender *m_objRegisteredVideoRender;
|
||||
DATVideostream *m_objVideoStream;
|
||||
DATVUDPStream m_udpStream;
|
||||
DATVideoRenderThread *m_objRenderThread;
|
||||
|
||||
// Audio
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
fftfilt * m_objRFFilter;
|
||||
NCO m_objNCO;
|
||||
|
||||
bool m_blnInitialized;
|
||||
bool m_blnRenderingVideo;
|
||||
bool m_blnStartStopVideo;
|
||||
bool m_cstlnSetByModcod;
|
||||
int m_modcodModulation;
|
||||
int m_modcodCodeRate;
|
||||
|
||||
DATVDemodSettings::DATVModulation m_enmModulation;
|
||||
|
||||
//DATVConfig m_objRunning;
|
||||
DATVDemodSettings m_settings;
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
MovingAverageUtil<double, double, 32> m_objMagSqAverage;
|
||||
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
|
||||
static const unsigned int m_rfFilterFftLength;
|
||||
|
||||
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_DATVDEMODSINK_H
|
@ -4,8 +4,9 @@ set(lora_SOURCES
|
||||
lorademod.cpp
|
||||
lorademodgui.cpp
|
||||
lorademodsettings.cpp
|
||||
lorademodsink.cpp
|
||||
lorademodbaseband.cpp
|
||||
loraplugin.cpp
|
||||
|
||||
lorademodgui.ui
|
||||
)
|
||||
|
||||
@ -13,6 +14,8 @@ set(lora_HEADERS
|
||||
lorademod.h
|
||||
lorademodgui.h
|
||||
lorademodsettings.h
|
||||
lorademodsink.h
|
||||
lorademodbaseband.h
|
||||
loraplugin.h
|
||||
)
|
||||
|
||||
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
Interleaving is "easiest" if the same number of bits is used per symbol as for FEC
|
||||
Chosen mode "spreading 8, low rate" has 6 bits per symbol, so use 4:6 FEC
|
||||
|
||||
More spreading needs higher frequency resolution and longer time on air, increasing drift errors.
|
||||
Want higher bandwidth when using more spreading, which needs more CPU and a better FFT.
|
||||
|
||||
Six bit Hamming can only correct long runs of drift errors when not using interleaving. Interleaving defeats the point of using Gray code and puts multiple bit errors into single FEC blocks. Hardware decoding uses RSSI to detect the symbols most likely to be damaged, so that individual bits can be repaired after de-interleaving.
|
||||
|
||||
Using Implicit Mode: explicit starts with a 4:8 block and seems to have a different whitening sequence.
|
||||
*/
|
||||
|
||||
// Six bits per symbol, six chars per block
|
||||
void LoRaDemod::interleave6(char* inout, int size)
|
||||
{
|
||||
int i, j;
|
||||
char in[6 * 2];
|
||||
short s;
|
||||
|
||||
for (j = 0; j < size; j+=6) {
|
||||
for (i = 0; i < 6; i++)
|
||||
in[i] = in[i + 6] = inout[i + j];
|
||||
// top bits are swapped
|
||||
for (i = 0; i < 6; i++) {
|
||||
s = (32 & in[2 + i]) | (16 & in[1 + i]) | (8 & in[3 + i])
|
||||
| (4 & in[4 + i]) | (2 & in[5 + i]) | (1 & in[6 + i]);
|
||||
// bits are also rotated
|
||||
s = (s << 3) | (s >> 3);
|
||||
s &= 63;
|
||||
s = (s >> i) | (s << (6 - i));
|
||||
inout[i + j] = s & 63;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
short LoRaDemod::toGray(short num)
|
||||
{
|
||||
return (num >> 1) ^ num;
|
||||
}
|
||||
|
||||
// Ignore the FEC bits, just extract the data bits
|
||||
void LoRaDemod::hamming6(char* c, int size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<0) | ((c[i] & 4)>>0) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<2) | ((c[i] & 2)<<2) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] &32)>>2) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] &16)>>4);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>2) | ((c[i] & 8)>>2);
|
||||
}
|
||||
c[i] = 0;
|
||||
}
|
||||
|
||||
// data whitening (6 bit)
|
||||
void LoRaDemod::prng6(char* inout, int size)
|
||||
{
|
||||
const char otp[] = {
|
||||
//explicit mode
|
||||
"cOGGg7CM2=b5a?<`i;T2of5jDAB=2DoQ9ko?h_RLQR4@Z\\`9jY\\PX89lHX8h_R]c_^@OB<0`W08ik?Mg>dQZf3kn5Je5R=R4h[<Ph90HHh9j;h:mS^?f:lQ:GG;nU:b?WFU20Lf4@A?`hYJMnW\\QZ\\AMIZ<h:jQk[PP<`6[Z"
|
||||
#if 0
|
||||
// implicit mode (offset 2 symbols)
|
||||
"5^ZSm0=cOGMgUB=bNcb<@a^T;_f=6DEB]2ImPIKg:j]RlYT4YZ<`9hZ\\PPb;@8X8i]Zmc_6B52\\8oUPHIcBOc>dY?d9[n5Lg]b]R8hR<0`T008h9c9QJm[c?a:lQEGa;nU=b_WfUV2?V4@c=8h9B9njlQZDC@9Z<Q8\\iiX\\Rb6k:iY"
|
||||
#endif
|
||||
};
|
||||
int i, maxchars;
|
||||
|
||||
maxchars = sizeof( otp );
|
||||
if (size < maxchars)
|
||||
maxchars = size;
|
||||
for (i = 0; i < maxchars; i++)
|
||||
inout[i] ^= (otp[i] - 48);
|
||||
}
|
||||
|
@ -17,10 +17,11 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <stdio.h>
|
||||
#include <QThread>
|
||||
|
||||
#include "dsp/downchannelizer.h"
|
||||
#include "dsp/threadedbasebandsamplesink.h"
|
||||
@ -28,343 +29,88 @@
|
||||
#include "device/deviceapi.h"
|
||||
|
||||
#include "lorademod.h"
|
||||
#include "lorabits.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureLoRaDemod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LoRaDemod::MsgConfigureChannelizer, Message)
|
||||
|
||||
const QString LoRaDemod::m_channelIdURI = "sdrangel.channel.lorademod";
|
||||
const QString LoRaDemod::m_channelId = "LoRaDemod";
|
||||
|
||||
LoRaDemod::LoRaDemod(DeviceAPI* deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_sampleSink(0),
|
||||
m_settingsMutex(QMutex::Recursive)
|
||||
m_deviceAPI(deviceAPI)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_Bandwidth = LoRaDemodSettings::bandwidths[0];
|
||||
m_sampleRate = 96000;
|
||||
m_frequency = 0;
|
||||
m_nco.setFreq(m_frequency, m_sampleRate);
|
||||
m_interpolator.create(16, m_sampleRate, m_Bandwidth/1.9);
|
||||
m_sampleDistanceRemain = (Real)m_sampleRate / m_Bandwidth;
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSink = new LoRaDemodBaseband();
|
||||
m_basebandSink->moveToThread(m_thread);
|
||||
|
||||
m_chirp = 0;
|
||||
m_angle = 0;
|
||||
m_bin = 0;
|
||||
m_result = 0;
|
||||
m_count = 0;
|
||||
m_header = 0;
|
||||
m_time = 0;
|
||||
m_tune = 0;
|
||||
applySettings(m_settings, true);
|
||||
|
||||
loraFilter = new sfft(LORA_SFFT_LEN);
|
||||
negaFilter = new sfft(LORA_SFFT_LEN);
|
||||
|
||||
mov = new float[4*LORA_SFFT_LEN];
|
||||
history = new short[1024];
|
||||
finetune = new short[16];
|
||||
|
||||
m_channelizer = new DownChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer);
|
||||
m_deviceAPI->addChannelSink(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSink(this);
|
||||
m_deviceAPI->addChannelSinkAPI(this);
|
||||
}
|
||||
|
||||
LoRaDemod::~LoRaDemod()
|
||||
{
|
||||
if (loraFilter)
|
||||
delete loraFilter;
|
||||
if (negaFilter)
|
||||
delete negaFilter;
|
||||
if (mov)
|
||||
delete [] mov;
|
||||
if (history)
|
||||
delete [] history;
|
||||
if (finetune)
|
||||
delete [] finetune;
|
||||
|
||||
m_deviceAPI->removeChannelSinkAPI(this);
|
||||
m_deviceAPI->removeChannelSink(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
delete m_basebandSink;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void LoRaDemod::dumpRaw()
|
||||
{
|
||||
short bin, j, max;
|
||||
char text[256];
|
||||
|
||||
max = m_time / 4 - 3;
|
||||
|
||||
if (max > 140)
|
||||
{
|
||||
max = 140; // about 2 symbols to each char
|
||||
}
|
||||
|
||||
for ( j=0; j < max; j++)
|
||||
{
|
||||
bin = (history[(j + 1) * 4] + m_tune ) & (LORA_SFFT_LEN - 1);
|
||||
text[j] = toGray(bin >> 1);
|
||||
}
|
||||
|
||||
prng6(text, max);
|
||||
// First block is always 8 symbols
|
||||
interleave6(text, 6);
|
||||
interleave6(&text[8], max);
|
||||
hamming6(text, 6);
|
||||
hamming6(&text[8], max);
|
||||
|
||||
for ( j=0; j < max / 2; j++)
|
||||
{
|
||||
text[j] = (text[j * 2 + 1] << 4) | (0xf & text[j * 2 + 0]);
|
||||
|
||||
if ((text[j] < 32 )||( text[j] > 126))
|
||||
{
|
||||
text[j] = 0x5f;
|
||||
}
|
||||
}
|
||||
|
||||
text[3] = text[2];
|
||||
text[2] = text[1];
|
||||
text[1] = text[0];
|
||||
text[j] = 0;
|
||||
|
||||
printf("%s\n", &text[1]);
|
||||
}
|
||||
|
||||
short LoRaDemod::synch(short bin)
|
||||
{
|
||||
short i, j;
|
||||
|
||||
if (bin < 0)
|
||||
{
|
||||
if (m_time > 70)
|
||||
{
|
||||
dumpRaw();
|
||||
}
|
||||
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
history[m_time] = bin;
|
||||
|
||||
if (m_time > 12)
|
||||
{
|
||||
if (bin == history[m_time - 6])
|
||||
{
|
||||
if (bin == history[m_time - 12])
|
||||
{
|
||||
m_tune = LORA_SFFT_LEN - bin;
|
||||
j = 0;
|
||||
|
||||
for (i=0; i<12; i++)
|
||||
{
|
||||
j += finetune[15 & (m_time - i)];
|
||||
}
|
||||
|
||||
if (j < 0)
|
||||
{
|
||||
m_tune += 1;
|
||||
}
|
||||
|
||||
m_tune &= (LORA_SFFT_LEN - 1);
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_time++;
|
||||
m_time &= 1023;
|
||||
|
||||
if (m_time & 3)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (bin + m_tune) & (LORA_SFFT_LEN - 1);
|
||||
}
|
||||
|
||||
int LoRaDemod::detect(Complex c, Complex a)
|
||||
{
|
||||
int p, q;
|
||||
short i, result, negresult, movpoint;
|
||||
float peak, negpeak, tfloat;
|
||||
float mag[LORA_SFFT_LEN];
|
||||
float rev[LORA_SFFT_LEN];
|
||||
|
||||
loraFilter->run(c * a);
|
||||
negaFilter->run(c * conj(a));
|
||||
|
||||
// process spectrum twice in FFTLEN
|
||||
if (++m_count & ((1 << DATA_BITS) - 1))
|
||||
{
|
||||
return m_result;
|
||||
}
|
||||
|
||||
movpoint = 3 & (m_count >> DATA_BITS);
|
||||
|
||||
loraFilter->fetch(mag);
|
||||
negaFilter->fetch(rev);
|
||||
peak = negpeak = 0.0f;
|
||||
result = negresult = 0;
|
||||
|
||||
for (i = 0; i < LORA_SFFT_LEN; i++)
|
||||
{
|
||||
if (rev[i] > negpeak)
|
||||
{
|
||||
negpeak = rev[i];
|
||||
negresult = i;
|
||||
}
|
||||
|
||||
tfloat = mov[i] + mov[LORA_SFFT_LEN + i] +mov[2 * LORA_SFFT_LEN + i]
|
||||
+ mov[3 * LORA_SFFT_LEN + i] + mag[i];
|
||||
|
||||
if (tfloat > peak)
|
||||
{
|
||||
peak = tfloat;
|
||||
result = i;
|
||||
}
|
||||
|
||||
mov[movpoint * LORA_SFFT_LEN + i] = mag[i];
|
||||
}
|
||||
|
||||
p = (result - 1 + LORA_SFFT_LEN) & (LORA_SFFT_LEN -1);
|
||||
q = (result + 1) & (LORA_SFFT_LEN -1);
|
||||
finetune[15 & m_time] = (mag[p] > mag[q]) ? -1 : 1;
|
||||
|
||||
if (peak < negpeak * LORA_SQUELCH)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
result = synch(result);
|
||||
|
||||
if (result >= 0)
|
||||
{
|
||||
m_result = result;
|
||||
}
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void LoRaDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool pO)
|
||||
{
|
||||
(void) pO;
|
||||
int newangle;
|
||||
Complex ci;
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
for(SampleVector::const_iterator it = begin; it < end; ++it)
|
||||
{
|
||||
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
if(m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci))
|
||||
{
|
||||
m_chirp = (m_chirp + 1) & (SPREADFACTOR - 1);
|
||||
m_angle = (m_angle + m_chirp) & (SPREADFACTOR - 1);
|
||||
Complex cangle(cos(M_PI*2*m_angle/SPREADFACTOR),-sin(M_PI*2*m_angle/SPREADFACTOR));
|
||||
newangle = detect(ci, cangle);
|
||||
|
||||
m_bin = (m_bin + newangle) & (LORA_SFFT_LEN - 1);
|
||||
Complex nangle(cos(M_PI*2*m_bin/LORA_SFFT_LEN),sin(M_PI*2*m_bin/LORA_SFFT_LEN));
|
||||
m_sampleBuffer.push_back(Sample(nangle.real() * 100, nangle.imag() * 100));
|
||||
m_sampleDistanceRemain += (Real)m_sampleRate / m_Bandwidth;
|
||||
}
|
||||
}
|
||||
|
||||
if(m_sampleSink != 0)
|
||||
{
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
|
||||
}
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
|
||||
void LoRaDemod::start()
|
||||
{
|
||||
qDebug() << "LoRaDemod::start";
|
||||
|
||||
if (m_basebandSampleRate != 0) {
|
||||
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||
}
|
||||
|
||||
m_basebandSink->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void LoRaDemod::stop()
|
||||
{
|
||||
qDebug() << "LoRaDemod::stop";
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
bool LoRaDemod::handleMessage(const Message& cmd)
|
||||
{
|
||||
qDebug() << "LoRaDemod::handleMessage";
|
||||
|
||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_sampleRate = notif.getSampleRate();
|
||||
m_nco.setFreq(-notif.getFrequencyOffset(), m_sampleRate);
|
||||
m_interpolator.create(16, m_sampleRate, m_Bandwidth/1.9);
|
||||
m_sampleDistanceRemain = m_sampleRate / m_Bandwidth;
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
qDebug() << "LoRaDemod::handleMessage: MsgChannelizerNotification: m_sampleRate: " << m_sampleRate
|
||||
<< " frequencyOffset: " << notif.getFrequencyOffset();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
|
||||
qDebug() << "LoRaDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureLoRaDemod::match(cmd))
|
||||
if (MsgConfigureLoRaDemod::match(cmd))
|
||||
{
|
||||
qDebug() << "LoRaDemod::handleMessage: MsgConfigureLoRaDemod";
|
||||
MsgConfigureLoRaDemod& cfg = (MsgConfigureLoRaDemod&) cmd;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
LoRaDemodSettings settings = cfg.getSettings();
|
||||
|
||||
m_Bandwidth = LoRaDemodSettings::bandwidths[settings.m_bandwidthIndex];
|
||||
m_interpolator.create(16, m_sampleRate, m_Bandwidth/1.9);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_settings = settings;
|
||||
qDebug() << "LoRaDemod::handleMessage: MsgConfigureLoRaDemod: m_Bandwidth: " << m_Bandwidth;
|
||||
applySettings(settings, cfg.getForce());
|
||||
|
||||
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() << "LoRaDemod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_sampleSink != 0)
|
||||
{
|
||||
return m_sampleSink->handleMessage(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray LoRaDemod::serialize() const
|
||||
@ -389,3 +135,18 @@ bool LoRaDemod::deserialize(const QByteArray& data)
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemod::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LoRaDemod::applySettings:"
|
||||
<< " m_centerFrequency: " << settings.m_centerFrequency
|
||||
<< " m_bandwidthIndex: " << settings.m_bandwidthIndex
|
||||
<< " m_spread: " << settings.m_spread
|
||||
<< " m_rgbColor: " << settings.m_rgbColor
|
||||
<< " m_title: " << settings.m_title
|
||||
<< " force: " << force;
|
||||
|
||||
LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband *msg = LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband::create(settings, force);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
@ -16,30 +16,19 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_LoRaDEMOD_H
|
||||
#define INCLUDE_LoRaDEMOD_H
|
||||
#ifndef INCLUDE_LORADEMOD_H
|
||||
#define INCLUDE_LORADEMOD_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/message.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
|
||||
#include "lorademodsettings.h"
|
||||
|
||||
#define DATA_BITS (6)
|
||||
#define SAMPLEBITS (DATA_BITS + 2)
|
||||
#define SPREADFACTOR (1 << SAMPLEBITS)
|
||||
#define LORA_SFFT_LEN (SPREADFACTOR / 2)
|
||||
#define LORA_SQUELCH (3)
|
||||
#include "lorademodbaseband.h"
|
||||
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSink;
|
||||
class DownChannelizer;
|
||||
class QThread;
|
||||
|
||||
class LoRaDemod : public BasebandSampleSink, public ChannelAPI {
|
||||
public:
|
||||
@ -66,33 +55,10 @@ 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)
|
||||
{ }
|
||||
};
|
||||
|
||||
LoRaDemod(DeviceAPI* deviceAPI);
|
||||
virtual ~LoRaDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
void setSpectrumSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
||||
void setSpectrumSink(BasebandSampleSink* sampleSink) { m_basebandSink->setSpectrumSink(sampleSink); }
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool pO);
|
||||
virtual void start();
|
||||
@ -120,44 +86,13 @@ public:
|
||||
static const QString m_channelId;
|
||||
|
||||
private:
|
||||
int detect(Complex sample, Complex angle);
|
||||
void dumpRaw(void);
|
||||
short synch (short bin);
|
||||
short toGray(short bin);
|
||||
void interleave6(char* inout, int size);
|
||||
void hamming6(char* inout, int size);
|
||||
void prng6(char* inout, int size);
|
||||
|
||||
DeviceAPI *m_deviceAPI;
|
||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
||||
DownChannelizer* m_channelizer;
|
||||
QThread *m_thread;
|
||||
LoRaDemodBaseband* m_basebandSink;
|
||||
LoRaDemodSettings m_settings;
|
||||
int m_basebandSampleRate;
|
||||
|
||||
Real m_Bandwidth;
|
||||
int m_sampleRate;
|
||||
int m_frequency;
|
||||
int m_chirp;
|
||||
int m_angle;
|
||||
int m_bin;
|
||||
int m_result;
|
||||
int m_count;
|
||||
int m_header;
|
||||
int m_time;
|
||||
short m_tune;
|
||||
|
||||
sfft* loraFilter;
|
||||
sfft* negaFilter;
|
||||
float* mov;
|
||||
short* history;
|
||||
short* finetune;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_sampleDistanceRemain;
|
||||
|
||||
BasebandSampleSink* m_sampleSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
void applySettings(const LoRaDemodSettings& settings, bool force = false);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_LoRaDEMOD_H
|
||||
#endif // INCLUDE_LORADEMOD_H
|
||||
|
169
plugins/channelrx/demodlora/lorademodbaseband.cpp
Normal file
169
plugins/channelrx/demodlora/lorademodbaseband.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "lorademodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(LoRaDemodBaseband::MsgConfigureLoRaDemodBaseband, Message)
|
||||
|
||||
LoRaDemodBaseband::LoRaDemodBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownSampleChannelizer(&m_sink);
|
||||
|
||||
qDebug("LoRaDemodBaseband::LoRaDemodBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&LoRaDemodBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
LoRaDemodBaseband::~LoRaDemodBaseband()
|
||||
{
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void LoRaDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void LoRaDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void LoRaDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
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 LoRaDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LoRaDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureLoRaDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureLoRaDemodBaseband& cfg = (MsgConfigureLoRaDemodBaseband&) cmd;
|
||||
qDebug() << "LoRaDemodBaseband::handleMessage: MsgConfigureLoRaDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "LoRaDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
|
||||
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
|
||||
m_sink.applyChannelSettings(
|
||||
m_channelizer->getChannelSampleRate(),
|
||||
LoRaDemodSettings::bandwidths[m_settings.m_bandwidthIndex],
|
||||
m_channelizer->getChannelFrequencyOffset()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodBaseband::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_bandwidthIndex != m_settings.m_bandwidthIndex)
|
||||
|| (settings.m_centerFrequency != m_settings.m_centerFrequency) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(
|
||||
LoRaDemodSettings::bandwidths[settings.m_bandwidthIndex],
|
||||
settings.m_centerFrequency
|
||||
);
|
||||
m_sink.applyChannelSettings(
|
||||
m_channelizer->getChannelSampleRate(),
|
||||
LoRaDemodSettings::bandwidths[settings.m_bandwidthIndex],
|
||||
m_channelizer->getChannelFrequencyOffset()
|
||||
);
|
||||
}
|
||||
|
||||
m_sink.applySettings(settings, force);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int LoRaDemodBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
|
||||
void LoRaDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(
|
||||
m_channelizer->getChannelSampleRate(),
|
||||
LoRaDemodSettings::bandwidths[m_settings.m_bandwidthIndex],
|
||||
m_channelizer->getChannelFrequencyOffset()
|
||||
);
|
||||
}
|
84
plugins/channelrx/demodlora/lorademodbaseband.h
Normal file
84
plugins/channelrx/demodlora/lorademodbaseband.h
Normal file
@ -0,0 +1,84 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_LORADEMODBASEBAND_H
|
||||
#define INCLUDE_LORADEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "lorademodsink.h"
|
||||
|
||||
class DownSampleChannelizer;
|
||||
|
||||
class LoRaDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureLoRaDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const LoRaDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureLoRaDemodBaseband* create(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureLoRaDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
LoRaDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureLoRaDemodBaseband(const LoRaDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
LoRaDemodBaseband();
|
||||
~LoRaDemodBaseband();
|
||||
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 setBasebandSampleRate(int sampleRate);
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_sink.setSpectrumSink(spectrumSink); }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownSampleChannelizer *m_channelizer;
|
||||
LoRaDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
LoRaDemodSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const LoRaDemodSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_LORADEMODBASEBAND_H
|
@ -165,12 +165,6 @@ void LoRaDemodGUI::applySettings(bool force)
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
setTitleColor(m_channelMarker.getColor());
|
||||
|
||||
LoRaDemod::MsgConfigureChannelizer* channelConfigMsg = LoRaDemod::MsgConfigureChannelizer::create(
|
||||
LoRaDemodSettings::bandwidths[m_settings.m_bandwidthIndex],
|
||||
m_channelMarker.getCenterFrequency());
|
||||
m_LoRaDemod->getInputMessageQueue()->push(channelConfigMsg);
|
||||
|
||||
LoRaDemod::MsgConfigureLoRaDemod* message = LoRaDemod::MsgConfigureLoRaDemod::create( m_settings, force);
|
||||
m_LoRaDemod->getInputMessageQueue()->push(message);
|
||||
}
|
||||
|
286
plugins/channelrx/demodlora/lorademodsink.cpp
Normal file
286
plugins/channelrx/demodlora/lorademodsink.cpp
Normal file
@ -0,0 +1,286 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 <QTime>
|
||||
#include <QDebug>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "dsp/dsptypes.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
|
||||
#include "lorademodsink.h"
|
||||
|
||||
const int LoRaDemodSink::DATA_BITS = 6;
|
||||
const int LoRaDemodSink::SAMPLEBITS = LoRaDemodSink::DATA_BITS + 2;
|
||||
const int LoRaDemodSink::SPREADFACTOR = (1 << LoRaDemodSink::SAMPLEBITS);
|
||||
const int LoRaDemodSink::LORA_SFFT_LEN = (LoRaDemodSink::SPREADFACTOR / 2);
|
||||
const int LoRaDemodSink::LORA_SQUELCH = 3;
|
||||
|
||||
LoRaDemodSink::LoRaDemodSink() :
|
||||
m_spectrumSink(nullptr)
|
||||
{
|
||||
m_Bandwidth = LoRaDemodSettings::bandwidths[0];
|
||||
m_channelSampleRate = 96000;
|
||||
m_channelFrequencyOffset = 0;
|
||||
m_nco.setFreq(m_channelFrequencyOffset, m_channelSampleRate);
|
||||
m_interpolator.create(16, m_channelSampleRate, m_Bandwidth/1.9);
|
||||
m_sampleDistanceRemain = (Real) m_channelSampleRate / m_Bandwidth;
|
||||
|
||||
m_chirp = 0;
|
||||
m_angle = 0;
|
||||
m_bin = 0;
|
||||
m_result = 0;
|
||||
m_count = 0;
|
||||
m_header = 0;
|
||||
m_time = 0;
|
||||
m_tune = 0;
|
||||
|
||||
loraFilter = new sfft(LORA_SFFT_LEN);
|
||||
negaFilter = new sfft(LORA_SFFT_LEN);
|
||||
mov = new float[4*LORA_SFFT_LEN];
|
||||
history = new short[1024];
|
||||
finetune = new short[16];
|
||||
}
|
||||
|
||||
LoRaDemodSink::~LoRaDemodSink()
|
||||
{
|
||||
delete loraFilter;
|
||||
delete negaFilter;
|
||||
delete [] mov;
|
||||
delete [] history;
|
||||
delete [] finetune;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::dumpRaw()
|
||||
{
|
||||
short bin, j, max;
|
||||
char text[256];
|
||||
|
||||
max = m_time / 4 - 3;
|
||||
|
||||
if (max > 140) {
|
||||
max = 140; // about 2 symbols to each char
|
||||
}
|
||||
|
||||
for ( j=0; j < max; j++)
|
||||
{
|
||||
bin = (history[(j + 1) * 4] + m_tune ) & (LORA_SFFT_LEN - 1);
|
||||
text[j] = toGray(bin >> 1);
|
||||
}
|
||||
|
||||
prng6(text, max);
|
||||
// First block is always 8 symbols
|
||||
interleave6(text, 6);
|
||||
interleave6(&text[8], max);
|
||||
hamming6(text, 6);
|
||||
hamming6(&text[8], max);
|
||||
|
||||
for ( j=0; j < max / 2; j++)
|
||||
{
|
||||
text[j] = (text[j * 2 + 1] << 4) | (0xf & text[j * 2 + 0]);
|
||||
|
||||
if ((text[j] < 32 )||( text[j] > 126)) {
|
||||
text[j] = 0x5f;
|
||||
}
|
||||
}
|
||||
|
||||
text[3] = text[2];
|
||||
text[2] = text[1];
|
||||
text[1] = text[0];
|
||||
text[j] = 0;
|
||||
|
||||
qDebug("LoRaDemodSink::dumpRaw: %s", &text[1]);
|
||||
}
|
||||
|
||||
short LoRaDemodSink::synch(short bin)
|
||||
{
|
||||
short i, j;
|
||||
|
||||
if (bin < 0)
|
||||
{
|
||||
if (m_time > 70) {
|
||||
dumpRaw();
|
||||
}
|
||||
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
history[m_time] = bin;
|
||||
|
||||
if (m_time > 12)
|
||||
{
|
||||
if (bin == history[m_time - 6])
|
||||
{
|
||||
if (bin == history[m_time - 12])
|
||||
{
|
||||
m_tune = LORA_SFFT_LEN - bin;
|
||||
j = 0;
|
||||
|
||||
for (i=0; i<12; i++) {
|
||||
j += finetune[15 & (m_time - i)];
|
||||
}
|
||||
|
||||
if (j < 0) {
|
||||
m_tune += 1;
|
||||
}
|
||||
|
||||
m_tune &= (LORA_SFFT_LEN - 1);
|
||||
m_time = 0;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_time++;
|
||||
m_time &= 1023;
|
||||
|
||||
if (m_time & 3) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (bin + m_tune) & (LORA_SFFT_LEN - 1);
|
||||
}
|
||||
|
||||
int LoRaDemodSink::detect(Complex c, Complex a)
|
||||
{
|
||||
int p, q;
|
||||
short i, result, negresult, movpoint;
|
||||
float peak, negpeak, tfloat;
|
||||
float mag[LORA_SFFT_LEN];
|
||||
float rev[LORA_SFFT_LEN];
|
||||
|
||||
loraFilter->run(c * a);
|
||||
negaFilter->run(c * conj(a));
|
||||
|
||||
// process spectrum twice in FFTLEN
|
||||
if (++m_count & ((1 << DATA_BITS) - 1)) {
|
||||
return m_result;
|
||||
}
|
||||
|
||||
movpoint = 3 & (m_count >> DATA_BITS);
|
||||
|
||||
loraFilter->fetch(mag);
|
||||
negaFilter->fetch(rev);
|
||||
peak = negpeak = 0.0f;
|
||||
result = negresult = 0;
|
||||
|
||||
for (i = 0; i < LORA_SFFT_LEN; i++)
|
||||
{
|
||||
if (rev[i] > negpeak)
|
||||
{
|
||||
negpeak = rev[i];
|
||||
negresult = i;
|
||||
}
|
||||
|
||||
tfloat = mov[i] + mov[LORA_SFFT_LEN + i] +mov[2 * LORA_SFFT_LEN + i]
|
||||
+ mov[3 * LORA_SFFT_LEN + i] + mag[i];
|
||||
|
||||
if (tfloat > peak)
|
||||
{
|
||||
peak = tfloat;
|
||||
result = i;
|
||||
}
|
||||
|
||||
mov[movpoint * LORA_SFFT_LEN + i] = mag[i];
|
||||
}
|
||||
|
||||
p = (result - 1 + LORA_SFFT_LEN) & (LORA_SFFT_LEN -1);
|
||||
q = (result + 1) & (LORA_SFFT_LEN -1);
|
||||
finetune[15 & m_time] = (mag[p] > mag[q]) ? -1 : 1;
|
||||
|
||||
if (peak < negpeak * LORA_SQUELCH)
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
result = synch(result);
|
||||
|
||||
if (result >= 0) {
|
||||
m_result = result;
|
||||
}
|
||||
|
||||
return m_result;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
int newangle;
|
||||
Complex ci;
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it < end; ++it)
|
||||
{
|
||||
Complex c(it->real() / SDR_RX_SCALEF, it->imag() / SDR_RX_SCALEF);
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
if (m_interpolator.decimate(&m_sampleDistanceRemain, c, &ci))
|
||||
{
|
||||
m_chirp = (m_chirp + 1) & (SPREADFACTOR - 1);
|
||||
m_angle = (m_angle + m_chirp) & (SPREADFACTOR - 1);
|
||||
Complex cangle(cos(M_PI*2*m_angle/SPREADFACTOR),-sin(M_PI*2*m_angle/SPREADFACTOR));
|
||||
newangle = detect(ci, cangle);
|
||||
|
||||
m_bin = (m_bin + newangle) & (LORA_SFFT_LEN - 1);
|
||||
Complex nangle(cos(M_PI*2*m_bin/LORA_SFFT_LEN),sin(M_PI*2*m_bin/LORA_SFFT_LEN));
|
||||
m_sampleBuffer.push_back(Sample(nangle.real() * 100, nangle.imag() * 100));
|
||||
m_sampleDistanceRemain += (Real) m_channelSampleRate / m_Bandwidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaDemodSink::applyChannelSettings(int channelSampleRate, int bandwidth, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "LoRaDemodSink::applyChannelSettings:"
|
||||
<< " channelSampleRate: " << channelSampleRate
|
||||
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
||||
|
||||
if((channelFrequencyOffset != m_channelFrequencyOffset) ||
|
||||
(channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
qDebug() << "LoRaDemodSink::applyChannelSettings: m_interpolator.create";
|
||||
m_interpolator.create(16, channelSampleRate, bandwidth / 1.9f);
|
||||
m_sampleDistanceRemain = (Real) channelSampleRate / bandwidth;
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_Bandwidth = bandwidth;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void LoRaDemodSink::applySettings(const LoRaDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LoRaDemodSink::applySettings:"
|
||||
<< " m_centerFrequency: " << settings.m_centerFrequency
|
||||
<< " m_bandwidthIndex: " << settings.m_bandwidthIndex
|
||||
<< " m_spread: " << settings.m_spread
|
||||
<< " m_rgbColor: " << settings.m_rgbColor
|
||||
<< " m_title: " << settings.m_title
|
||||
<< " force: " << force;
|
||||
|
||||
m_settings = settings;
|
||||
}
|
163
plugins/channelrx/demodlora/lorademodsink.h
Normal file
163
plugins/channelrx/demodlora/lorademodsink.h
Normal file
@ -0,0 +1,163 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_LORADEMODSINK_H
|
||||
#define INCLUDE_LORADEMODSINK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/message.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
|
||||
#include "lorademodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
|
||||
class LoRaDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
LoRaDemodSink();
|
||||
~LoRaDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; }
|
||||
void applyChannelSettings(int channelSampleRate, int bandwidth, int channelFrequencyOffset, bool force = false);
|
||||
void applySettings(const LoRaDemodSettings& settings, bool force = false);
|
||||
|
||||
private:
|
||||
LoRaDemodSettings m_settings;
|
||||
Real m_Bandwidth;
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
int m_chirp;
|
||||
int m_angle;
|
||||
int m_bin;
|
||||
int m_result;
|
||||
int m_count;
|
||||
int m_header;
|
||||
int m_time;
|
||||
short m_tune;
|
||||
|
||||
sfft* loraFilter;
|
||||
sfft* negaFilter;
|
||||
float* mov;
|
||||
short* history;
|
||||
short* finetune;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_sampleDistanceRemain;
|
||||
|
||||
BasebandSampleSink* m_spectrumSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
static const int DATA_BITS;
|
||||
static const int SAMPLEBITS;
|
||||
static const int SPREADFACTOR;
|
||||
static const int LORA_SFFT_LEN;
|
||||
static const int LORA_SQUELCH;
|
||||
|
||||
int detect(Complex sample, Complex angle);
|
||||
void dumpRaw(void);
|
||||
short synch (short bin);
|
||||
|
||||
/*
|
||||
Interleaving is "easiest" if the same number of bits is used per symbol as for FEC
|
||||
Chosen mode "spreading 8, low rate" has 6 bits per symbol, so use 4:6 FEC
|
||||
|
||||
More spreading needs higher frequency resolution and longer time on air, increasing drift errors.
|
||||
Want higher bandwidth when using more spreading, which needs more CPU and a better FFT.
|
||||
|
||||
Six bit Hamming can only correct long runs of drift errors when not using interleaving. Interleaving defeats the point of using Gray code and puts multiple bit errors into single FEC blocks. Hardware decoding uses RSSI to detect the symbols most likely to be damaged, so that individual bits can be repaired after de-interleaving.
|
||||
|
||||
Using Implicit Mode: explicit starts with a 4:8 block and seems to have a different whitening sequence.
|
||||
*/
|
||||
|
||||
// Six bits per symbol, six chars per block
|
||||
inline void interleave6(char* inout, int size)
|
||||
{
|
||||
int i, j;
|
||||
char in[6 * 2];
|
||||
short s;
|
||||
|
||||
for (j = 0; j < size; j+=6) {
|
||||
for (i = 0; i < 6; i++)
|
||||
in[i] = in[i + 6] = inout[i + j];
|
||||
// top bits are swapped
|
||||
for (i = 0; i < 6; i++) {
|
||||
s = (32 & in[2 + i]) | (16 & in[1 + i]) | (8 & in[3 + i])
|
||||
| (4 & in[4 + i]) | (2 & in[5 + i]) | (1 & in[6 + i]);
|
||||
// bits are also rotated
|
||||
s = (s << 3) | (s >> 3);
|
||||
s &= 63;
|
||||
s = (s >> i) | (s << (6 - i));
|
||||
inout[i + j] = s & 63;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline short toGray(short num)
|
||||
{
|
||||
return (num >> 1) ^ num;
|
||||
}
|
||||
|
||||
// Ignore the FEC bits, just extract the data bits
|
||||
inline void hamming6(char* c, int size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<0) | ((c[i] & 4)>>0) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<2) | ((c[i] & 2)<<2) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] &32)>>2) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] & 8)>>3);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>1) | ((c[i] &16)>>4);
|
||||
i++;
|
||||
c[i] = ((c[i] & 1)<<3) | ((c[i] & 2)<<1) | ((c[i] & 4)>>2) | ((c[i] & 8)>>2);
|
||||
}
|
||||
c[i] = 0;
|
||||
}
|
||||
|
||||
// data whitening (6 bit)
|
||||
inline void prng6(char* inout, int size)
|
||||
{
|
||||
const char otp[] = {
|
||||
//explicit mode
|
||||
"cOGGg7CM2=b5a?<`i;T2of5jDAB=2DoQ9ko?h_RLQR4@Z\\`9jY\\PX89lHX8h_R]c_^@OB<0`W08ik?Mg>dQZf3kn5Je5R=R4h[<Ph90HHh9j;h:mS^?f:lQ:GG;nU:b?WFU20Lf4@A?`hYJMnW\\QZ\\AMIZ<h:jQk[PP<`6[Z"
|
||||
#if 0
|
||||
// implicit mode (offset 2 symbols)
|
||||
"5^ZSm0=cOGMgUB=bNcb<@a^T;_f=6DEB]2ImPIKg:j]RlYT4YZ<`9hZ\\PPb;@8X8i]Zmc_6B52\\8oUPHIcBOc>dY?d9[n5Lg]b]R8hR<0`T008h9c9QJm[c?a:lQEGa;nU=b_WfUV2?V4@c=8h9B9njlQZDC@9Z<Q8\\iiX\\Rb6k:iY"
|
||||
#endif
|
||||
};
|
||||
int i, maxchars;
|
||||
|
||||
maxchars = sizeof( otp );
|
||||
if (size < maxchars)
|
||||
maxchars = size;
|
||||
for (i = 0; i < maxchars; i++)
|
||||
inout[i] ^= (otp[i] - 48);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDE_LORADEMODSINK_H
|
@ -7,7 +7,7 @@
|
||||
|
||||
const PluginDescriptor LoRaPlugin::m_pluginDescriptor = {
|
||||
QString("LoRa Demodulator"),
|
||||
QString("3.14.5"),
|
||||
QString("4.12.2"),
|
||||
QString("(c) 2015 John Greb"),
|
||||
QString("http://www.maintech.de"),
|
||||
true,
|
||||
|
@ -220,7 +220,6 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
|
||||
reverseAPIKeys.append("streamIndex");
|
||||
}
|
||||
|
||||
|
||||
NFMDemodBaseband::MsgConfigureNFMDemodBaseband *msg = NFMDemodBaseband::MsgConfigureNFMDemodBaseband::create(settings, force);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
|
||||
|
@ -16,8 +16,8 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_NFMTESTDEMOD_H
|
||||
#define INCLUDE_NFMTESTDEMOD_H
|
||||
#ifndef INCLUDE_NFMDEMOD_H
|
||||
#define INCLUDE_NFMDEMOD_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -122,11 +122,6 @@ public:
|
||||
static const QString m_channelId;
|
||||
|
||||
private:
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
RSRunning
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
QThread *m_thread;
|
||||
NFMDemodBaseband* m_basebandSink;
|
||||
|
@ -3,6 +3,8 @@ project(ssb)
|
||||
set(ssb_SOURCES
|
||||
ssbdemod.cpp
|
||||
ssbdemodsettings.cpp
|
||||
ssbdemodsink.cpp
|
||||
ssbdemodbaseband.cpp
|
||||
ssbdemodwebapiadapter.cpp
|
||||
ssbplugin.cpp
|
||||
)
|
||||
@ -10,6 +12,8 @@ set(ssb_SOURCES
|
||||
set(ssb_HEADERS
|
||||
ssbdemod.h
|
||||
ssbdemodsettings.h
|
||||
ssbdemodsink.h
|
||||
ssbdemodbaseband.h
|
||||
ssbdemodwebapiadapter.h
|
||||
ssbplugin.h
|
||||
)
|
||||
@ -22,14 +26,12 @@ if(NOT SERVER_MODE)
|
||||
set(ssb_SOURCES
|
||||
${ssb_SOURCES}
|
||||
ssbdemodgui.cpp
|
||||
|
||||
ssbdemodgui.ui
|
||||
)
|
||||
set(ssb_HEADERS
|
||||
${ssb_HEADERS}
|
||||
ssbdemodgui.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME demodssb)
|
||||
set(TARGET_LIB "Qt5::Widgets")
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
|
@ -25,16 +25,15 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGSSBDemodSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGSSBDemodReport.h"
|
||||
|
||||
#include "audio/audiooutput.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/downchannelizer.h"
|
||||
#include "dsp/threadedbasebandsamplesink.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "device/deviceapi.h"
|
||||
@ -43,8 +42,6 @@
|
||||
#include "ssbdemod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureSSBDemodPrivate, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SSBDemod::MsgConfigureChannelizer, Message)
|
||||
|
||||
const QString SSBDemod::m_channelIdURI = "sdrangel.channel.ssbdemod";
|
||||
const QString SSBDemod::m_channelId = "SSBDemod";
|
||||
@ -52,57 +49,17 @@ const QString SSBDemod::m_channelId = "SSBDemod";
|
||||
SSBDemod::SSBDemod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_audioBinaual(false),
|
||||
m_audioFlipChannels(false),
|
||||
m_dsb(false),
|
||||
m_audioMute(false),
|
||||
m_agc(12000, agcTarget, 1e-2),
|
||||
m_agcActive(false),
|
||||
m_agcClamping(false),
|
||||
m_agcNbSamples(12000),
|
||||
m_agcPowerThreshold(1e-2),
|
||||
m_agcThresholdGate(0),
|
||||
m_squelchDelayLine(2*48000),
|
||||
m_audioActive(false),
|
||||
m_sampleSink(0),
|
||||
m_audioFifo(24000),
|
||||
m_settingsMutex(QMutex::Recursive)
|
||||
m_basebandSampleRate(0)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_Bandwidth = 5000;
|
||||
m_LowCutoff = 300;
|
||||
m_volume = 2.0;
|
||||
m_spanLog2 = 3;
|
||||
m_inputSampleRate = 48000;
|
||||
m_inputFrequencyOffset = 0;
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSink = new SSBDemodBaseband();
|
||||
m_basebandSink->moveToThread(m_thread);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
m_undersampleCount = 0;
|
||||
m_sum = 0;
|
||||
|
||||
m_usb = true;
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
|
||||
m_agc.setClampMax(SDR_RX_SCALED/100.0);
|
||||
m_agc.setClamping(m_agcClamping);
|
||||
|
||||
SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, ssbFftLen);
|
||||
DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * ssbFftLen);
|
||||
|
||||
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();
|
||||
@ -113,46 +70,10 @@ SSBDemod::~SSBDemod()
|
||||
{
|
||||
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 SSBFilter;
|
||||
delete DSBFilter;
|
||||
}
|
||||
|
||||
void SSBDemod::configure(MessageQueue* messageQueue,
|
||||
Real Bandwidth,
|
||||
Real LowCutoff,
|
||||
Real volume,
|
||||
int spanLog2,
|
||||
bool audioBinaural,
|
||||
bool audioFlipChannel,
|
||||
bool dsb,
|
||||
bool audioMute,
|
||||
bool agc,
|
||||
bool agcClamping,
|
||||
int agcTimeLog2,
|
||||
int agcPowerThreshold,
|
||||
int agcThresholdGate)
|
||||
{
|
||||
Message* cmd = MsgConfigureSSBDemodPrivate::create(
|
||||
Bandwidth,
|
||||
LowCutoff,
|
||||
volume,
|
||||
spanLog2,
|
||||
audioBinaural,
|
||||
audioFlipChannel,
|
||||
dsb,
|
||||
audioMute,
|
||||
agc,
|
||||
agcClamping,
|
||||
agcTimeLog2,
|
||||
agcPowerThreshold,
|
||||
agcThresholdGate);
|
||||
messageQueue->push(cmd);
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
delete m_basebandSink;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
uint32_t SSBDemod::getNumberOfDeviceStreams() const
|
||||
@ -163,185 +84,31 @@ uint32_t SSBDemod::getNumberOfDeviceStreams() const
|
||||
void SSBDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
|
||||
{
|
||||
(void) positiveOnly;
|
||||
Complex ci;
|
||||
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
|
||||
{
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
|
||||
{
|
||||
processOneSample(ci);
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
void SSBDemod::processOneSample(Complex &ci)
|
||||
{
|
||||
fftfilt::cmplx *sideband;
|
||||
int n_out = 0;
|
||||
int decim = 1<<(m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
if (m_dsb) {
|
||||
n_out = DSBFilter->runDSB(ci, &sideband);
|
||||
} else {
|
||||
n_out = SSBFilter->runSSB(ci, &sideband, m_usb);
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += sideband[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = m_sum.real() / decim;
|
||||
Real avgi = m_sum.imag() / decim;
|
||||
m_magsq = (avgr * avgr + avgi * avgi) / (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
|
||||
m_magsqSum += m_magsq;
|
||||
|
||||
if (m_magsq > m_magsqPeak)
|
||||
{
|
||||
m_magsqPeak = m_magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (!m_dsb & !m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
|
||||
float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 0.1;
|
||||
fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay());
|
||||
m_audioActive = delayedSample.real() != 0.0;
|
||||
m_squelchDelayLine.write(sideband[i]*agcVal);
|
||||
|
||||
if (m_audioMute)
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = 0;
|
||||
m_audioBuffer[m_audioBufferFill].l = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fftfilt::cmplx z = m_agcActive ? delayedSample * m_agc.getStepValue() : delayedSample;
|
||||
|
||||
if (m_audioBinaual)
|
||||
{
|
||||
if (m_audioFlipChannels)
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Real demod = (z.real() + z.imag()) * 0.7;
|
||||
qint16 sample = (qint16)(demod * m_volume);
|
||||
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("SSBDemod::feed: %u/%u samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill)
|
||||
{
|
||||
qDebug("SSBDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
if (m_sampleSink != 0)
|
||||
{
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_dsb);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
|
||||
void SSBDemod::start()
|
||||
{
|
||||
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug() << "SSBDemod::start";
|
||||
|
||||
if (m_basebandSampleRate != 0) {
|
||||
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||
}
|
||||
|
||||
m_basebandSink->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void SSBDemod::stop()
|
||||
{
|
||||
qDebug() << "SSBDemod::stop";
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
bool SSBDemod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug("SSBDemod::handleMessage: MsgChannelizerNotification: m_sampleRate");
|
||||
|
||||
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "SSBDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureSSBDemod::match(cmd))
|
||||
if (MsgConfigureSSBDemod::match(cmd))
|
||||
{
|
||||
MsgConfigureSSBDemod& cfg = (MsgConfigureSSBDemod&) cmd;
|
||||
qDebug("SSBDemod::handleMessage: MsgConfigureSSBDemod");
|
||||
@ -350,115 +117,21 @@ bool SSBDemod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (BasebandSampleSink::MsgThreadedSink::match(cmd))
|
||||
{
|
||||
BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd;
|
||||
const QThread *thread = cfg.getThread();
|
||||
qDebug("SSBDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread);
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
|
||||
qDebug() << "SSBDemod::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() << "SSBDemod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_sampleSink != 0)
|
||||
{
|
||||
return m_sampleSink->handleMessage(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSBDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "SSBDemod::applyChannelSettings:"
|
||||
<< " inputSampleRate: " << inputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((m_inputFrequencyOffset != inputFrequencyOffset) ||
|
||||
(m_inputSampleRate != inputSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
|
||||
}
|
||||
|
||||
if ((m_inputSampleRate != inputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > inputSampleRate ? inputSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, inputSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_inputSampleRate = inputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void SSBDemod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("SSBDemod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
sampleRate, m_settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_inputSampleRate ? m_inputSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, m_inputSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
|
||||
|
||||
SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate);
|
||||
DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate);
|
||||
|
||||
int agcNbSamples = (sampleRate / 1000) * (1<<m_settings.m_agcTimeLog2);
|
||||
int agcThresholdGate = (sampleRate / 1000) * m_settings.m_agcThresholdGate; // ms
|
||||
|
||||
if (m_agcNbSamples != agcNbSamples)
|
||||
{
|
||||
m_agc.resize(agcNbSamples, agcNbSamples/2, agcTarget);
|
||||
m_agc.setStepDownDelay(agcNbSamples);
|
||||
m_agcNbSamples = agcNbSamples;
|
||||
}
|
||||
|
||||
if (m_agcThresholdGate != agcThresholdGate)
|
||||
{
|
||||
m_agc.setGate(agcThresholdGate);
|
||||
m_agcThresholdGate = agcThresholdGate;
|
||||
}
|
||||
|
||||
m_audioFifo.setSize(sampleRate);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate, DSPConfigureAudio::AudioOutput);
|
||||
m_guiMessageQueue->push(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
@ -498,49 +171,9 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
if((m_settings.m_lowCutoff != settings.m_lowCutoff) || force) {
|
||||
reverseAPIKeys.append("lowCutoff");
|
||||
}
|
||||
|
||||
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
|
||||
(m_settings.m_lowCutoff != settings.m_lowCutoff) || force)
|
||||
{
|
||||
float band, lowCutoff;
|
||||
|
||||
band = settings.m_rfBandwidth;
|
||||
lowCutoff = settings.m_lowCutoff;
|
||||
|
||||
if (band < 0) {
|
||||
band = -band;
|
||||
lowCutoff = -lowCutoff;
|
||||
m_usb = false;
|
||||
} else {
|
||||
m_usb = true;
|
||||
}
|
||||
|
||||
if (band < 100.0f)
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
m_Bandwidth = band;
|
||||
m_LowCutoff = lowCutoff;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_inputSampleRate ? m_inputSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, m_inputSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||
SSBFilter->create_filter(m_LowCutoff / (float) m_audioSampleRate, m_Bandwidth / (float) m_audioSampleRate);
|
||||
DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((m_settings.m_volume != settings.m_volume) || force)
|
||||
{
|
||||
if ((m_settings.m_volume != settings.m_volume) || force) {
|
||||
reverseAPIKeys.append("volume");
|
||||
m_volume = settings.m_volume;
|
||||
m_volume /= 4.0; // for 3276.8
|
||||
}
|
||||
|
||||
if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || force) {
|
||||
reverseAPIKeys.append("agcTimeLog2");
|
||||
}
|
||||
@ -553,65 +186,9 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
if ((m_settings.m_agcClamping != settings.m_agcClamping) || force) {
|
||||
reverseAPIKeys.append("agcClamping");
|
||||
}
|
||||
|
||||
if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) ||
|
||||
(m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) ||
|
||||
(m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) ||
|
||||
(m_settings.m_agcClamping != settings.m_agcClamping) || force)
|
||||
{
|
||||
int agcNbSamples = (m_audioSampleRate / 1000) * (1<<settings.m_agcTimeLog2);
|
||||
m_agc.setThresholdEnable(settings.m_agcPowerThreshold != -SSBDemodSettings::m_minPowerThresholdDB);
|
||||
double agcPowerThreshold = CalcDb::powerFromdB(settings.m_agcPowerThreshold) * (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
int agcThresholdGate = (m_audioSampleRate / 1000) * settings.m_agcThresholdGate; // ms
|
||||
bool agcClamping = settings.m_agcClamping;
|
||||
|
||||
if (m_agcNbSamples != agcNbSamples)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_agc.resize(agcNbSamples, agcNbSamples/2, agcTarget);
|
||||
m_agc.setStepDownDelay(agcNbSamples);
|
||||
m_agcNbSamples = agcNbSamples;
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if (m_agcPowerThreshold != agcPowerThreshold)
|
||||
{
|
||||
m_agc.setThreshold(agcPowerThreshold);
|
||||
m_agcPowerThreshold = agcPowerThreshold;
|
||||
}
|
||||
|
||||
if (m_agcThresholdGate != agcThresholdGate)
|
||||
{
|
||||
m_agc.setGate(agcThresholdGate);
|
||||
m_agcThresholdGate = agcThresholdGate;
|
||||
}
|
||||
|
||||
if (m_agcClamping != agcClamping)
|
||||
{
|
||||
m_agc.setClamping(agcClamping);
|
||||
m_agcClamping = agcClamping;
|
||||
}
|
||||
|
||||
qDebug() << "SBDemod::applySettings: AGC:"
|
||||
<< " agcNbSamples: " << agcNbSamples
|
||||
<< " agcPowerThreshold: " << agcPowerThreshold
|
||||
<< " agcThresholdGate: " << agcThresholdGate
|
||||
<< " agcClamping: " << agcClamping;
|
||||
}
|
||||
|
||||
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);
|
||||
audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) {
|
||||
reverseAPIKeys.append("spanLog2");
|
||||
}
|
||||
@ -631,28 +208,22 @@ void SSBDemod::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
reverseAPIKeys.append("agc");
|
||||
}
|
||||
|
||||
m_spanLog2 = settings.m_spanLog2;
|
||||
m_audioBinaual = settings.m_audioBinaural;
|
||||
m_audioFlipChannels = settings.m_audioFlipChannels;
|
||||
m_dsb = settings.m_dsb;
|
||||
m_audioMute = settings.m_audioMute;
|
||||
m_agcActive = settings.m_agc;
|
||||
|
||||
if (m_settings.m_streamIndex != settings.m_streamIndex)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
SSBDemodBaseband::MsgConfigureSSBDemodBaseband *msg = SSBDemodBaseband::MsgConfigureSSBDemodBaseband::create(settings, force);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -709,13 +280,6 @@ int SSBDemod::webapiSettingsPutPatch(
|
||||
SSBDemodSettings 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);
|
||||
}
|
||||
|
||||
MsgConfigureSSBDemod *msg = MsgConfigureSSBDemod::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
@ -870,9 +434,9 @@ void SSBDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
|
||||
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||
|
||||
response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
|
||||
response.getSsbDemodReport()->setSquelch(m_audioActive ? 1 : 0);
|
||||
response.getSsbDemodReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate);
|
||||
response.getSsbDemodReport()->setSquelch(m_basebandSink->getAudioActive() ? 1 : 0);
|
||||
response.getSsbDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
|
||||
response.getSsbDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void SSBDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SSBDemodSettings& settings, bool force)
|
||||
|
@ -26,24 +26,15 @@
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/doublebufferfifo.h"
|
||||
|
||||
#include "ssbdemodsettings.h"
|
||||
|
||||
#define ssbFftLen 1024
|
||||
#define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal
|
||||
#include "ssbdemodbaseband.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSink;
|
||||
class DownChannelizer;
|
||||
|
||||
class SSBDemod : public BasebandSampleSink, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -71,48 +62,10 @@ 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)
|
||||
{ }
|
||||
};
|
||||
|
||||
SSBDemod(DeviceAPI *deviceAPI);
|
||||
virtual ~SSBDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
||||
|
||||
void configure(MessageQueue* messageQueue,
|
||||
Real Bandwidth,
|
||||
Real LowCutoff,
|
||||
Real volume,
|
||||
int spanLog2,
|
||||
bool audioBinaural,
|
||||
bool audioFlipChannels,
|
||||
bool dsb,
|
||||
bool audioMute,
|
||||
bool agc,
|
||||
bool agcClamping,
|
||||
int agcTimeLog2,
|
||||
int agcPowerThreshold,
|
||||
int agcThresholdGate);
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_basebandSink->setSpectrumSink(spectrumSink); }
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
|
||||
virtual void start();
|
||||
@ -136,28 +89,13 @@ public:
|
||||
return m_settings.m_inputFrequencyOffset;
|
||||
}
|
||||
|
||||
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
uint32_t getInputSampleRate() const { return m_inputSampleRate; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
bool getAudioActive() const { return m_audioActive; }
|
||||
void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); }
|
||||
uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); }
|
||||
uint32_t getChannelSampleRate() const { return m_basebandSink->getChannelSampleRate(); }
|
||||
double getMagSq() const { return m_basebandSink->getMagSq(); }
|
||||
bool getAudioActive() const { return m_basebandSink->getAudioActive(); }
|
||||
|
||||
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); }
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
@ -188,169 +126,19 @@ public:
|
||||
static const QString m_channelId;
|
||||
|
||||
private:
|
||||
struct MagSqLevelsStore
|
||||
{
|
||||
MagSqLevelsStore() :
|
||||
m_magsq(1e-12),
|
||||
m_magsqPeak(1e-12)
|
||||
{}
|
||||
double m_magsq;
|
||||
double m_magsqPeak;
|
||||
};
|
||||
|
||||
class MsgConfigureSSBDemodPrivate : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
Real getBandwidth() const { return m_Bandwidth; }
|
||||
Real getLoCutoff() const { return m_LowCutoff; }
|
||||
Real getVolume() const { return m_volume; }
|
||||
int getSpanLog2() const { return m_spanLog2; }
|
||||
bool getAudioBinaural() const { return m_audioBinaural; }
|
||||
bool getAudioFlipChannels() const { return m_audioFlipChannels; }
|
||||
bool getDSB() const { return m_dsb; }
|
||||
bool getAudioMute() const { return m_audioMute; }
|
||||
bool getAGC() const { return m_agc; }
|
||||
bool getAGCClamping() const { return m_agcClamping; }
|
||||
int getAGCTimeLog2() const { return m_agcTimeLog2; }
|
||||
int getAGCPowerThershold() const { return m_agcPowerThreshold; }
|
||||
int getAGCThersholdGate() const { return m_agcThresholdGate; }
|
||||
|
||||
static MsgConfigureSSBDemodPrivate* create(Real Bandwidth,
|
||||
Real LowCutoff,
|
||||
Real volume,
|
||||
int spanLog2,
|
||||
bool audioBinaural,
|
||||
bool audioFlipChannels,
|
||||
bool dsb,
|
||||
bool audioMute,
|
||||
bool agc,
|
||||
bool agcClamping,
|
||||
int agcTimeLog2,
|
||||
int agcPowerThreshold,
|
||||
int agcThresholdGate)
|
||||
{
|
||||
return new MsgConfigureSSBDemodPrivate(
|
||||
Bandwidth,
|
||||
LowCutoff,
|
||||
volume,
|
||||
spanLog2,
|
||||
audioBinaural,
|
||||
audioFlipChannels,
|
||||
dsb,
|
||||
audioMute,
|
||||
agc,
|
||||
agcClamping,
|
||||
agcTimeLog2,
|
||||
agcPowerThreshold,
|
||||
agcThresholdGate);
|
||||
}
|
||||
|
||||
private:
|
||||
Real m_Bandwidth;
|
||||
Real m_LowCutoff;
|
||||
Real m_volume;
|
||||
int m_spanLog2;
|
||||
bool m_audioBinaural;
|
||||
bool m_audioFlipChannels;
|
||||
bool m_dsb;
|
||||
bool m_audioMute;
|
||||
bool m_agc;
|
||||
bool m_agcClamping;
|
||||
int m_agcTimeLog2;
|
||||
int m_agcPowerThreshold;
|
||||
int m_agcThresholdGate;
|
||||
|
||||
MsgConfigureSSBDemodPrivate(Real Bandwidth,
|
||||
Real LowCutoff,
|
||||
Real volume,
|
||||
int spanLog2,
|
||||
bool audioBinaural,
|
||||
bool audioFlipChannels,
|
||||
bool dsb,
|
||||
bool audioMute,
|
||||
bool agc,
|
||||
bool agcClamping,
|
||||
int agcTimeLog2,
|
||||
int agcPowerThreshold,
|
||||
int agcThresholdGate) :
|
||||
Message(),
|
||||
m_Bandwidth(Bandwidth),
|
||||
m_LowCutoff(LowCutoff),
|
||||
m_volume(volume),
|
||||
m_spanLog2(spanLog2),
|
||||
m_audioBinaural(audioBinaural),
|
||||
m_audioFlipChannels(audioFlipChannels),
|
||||
m_dsb(dsb),
|
||||
m_audioMute(audioMute),
|
||||
m_agc(agc),
|
||||
m_agcClamping(agcClamping),
|
||||
m_agcTimeLog2(agcTimeLog2),
|
||||
m_agcPowerThreshold(agcPowerThreshold),
|
||||
m_agcThresholdGate(agcThresholdGate)
|
||||
{ }
|
||||
};
|
||||
|
||||
DeviceAPI *m_deviceAPI;
|
||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
||||
DownChannelizer* m_channelizer;
|
||||
QThread *m_thread;
|
||||
SSBDemodBaseband* m_basebandSink;
|
||||
SSBDemodSettings m_settings;
|
||||
|
||||
Real m_Bandwidth;
|
||||
Real m_LowCutoff;
|
||||
Real m_volume;
|
||||
int m_spanLog2;
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_inputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
bool m_audioBinaual;
|
||||
bool m_audioFlipChannels;
|
||||
bool m_usb;
|
||||
bool m_dsb;
|
||||
bool m_audioMute;
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MagAGC m_agc;
|
||||
bool m_agcActive;
|
||||
bool m_agcClamping;
|
||||
int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging
|
||||
double m_agcPowerThreshold; //!< AGC power threshold (linear)
|
||||
int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers
|
||||
DoubleBufferFIFO<fftfilt::cmplx> m_squelchDelayLine;
|
||||
bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold)
|
||||
|
||||
NCOF m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
fftfilt* SSBFilter;
|
||||
fftfilt* DSBFilter;
|
||||
|
||||
BasebandSampleSink* m_sampleSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
quint32 m_audioSampleRate;
|
||||
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const SSBDemodSettings& settings, bool force = false);
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SSBDemodSettings& settings, bool force);
|
||||
|
||||
void processOneSample(Complex &ci);
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
};
|
||||
|
182
plugins/channelrx/demodssb/ssbdemodbaseband.cpp
Normal file
182
plugins/channelrx/demodssb/ssbdemodbaseband.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "ssbdemodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(SSBDemodBaseband::MsgConfigureSSBDemodBaseband, Message)
|
||||
|
||||
SSBDemodBaseband::SSBDemodBaseband() :
|
||||
m_messageQueueToGUI(nullptr),
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownSampleChannelizer(&m_sink);
|
||||
|
||||
qDebug("SSBDemodBaseband::SSBDemodBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&SSBDemodBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
m_sink.applyAudioSampleRate(m_audioSampleRate);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
SSBDemodBaseband::~SSBDemodBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void SSBDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void SSBDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void SSBDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
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 SSBDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SSBDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureSSBDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureSSBDemodBaseband& cfg = (MsgConfigureSSBDemodBaseband&) cmd;
|
||||
qDebug() << "SSBDemodBaseband::handleMessage: MsgConfigureSSBDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "SSBDemodBaseband::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 SSBDemodBaseband::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(m_audioSampleRate, 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);
|
||||
audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
unsigned int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate)
|
||||
{
|
||||
m_sink.applyAudioSampleRate(audioSampleRate);
|
||||
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
m_audioSampleRate = audioSampleRate;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio((int) audioSampleRate, DSPConfigureAudio::AudioOutput);
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_sink.applySettings(settings, force);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int SSBDemodBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
|
||||
void SSBDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
92
plugins/channelrx/demodssb/ssbdemodbaseband.h
Normal file
92
plugins/channelrx/demodssb/ssbdemodbaseband.h
Normal file
@ -0,0 +1,92 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_SSBDEMODBASEBAND_H
|
||||
#define INCLUDE_SSBDEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "ssbdemodsink.h"
|
||||
|
||||
class DownSampleChannelizer;
|
||||
|
||||
class SSBDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureSSBDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const SSBDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureSSBDemodBaseband* create(const SSBDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureSSBDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
SSBDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureSSBDemodBaseband(const SSBDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
SSBDemodBaseband();
|
||||
~SSBDemodBaseband();
|
||||
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 setSpectrumSink(BasebandSampleSink* spectrumSink) { m_sink.setSpectrumSink(spectrumSink); }
|
||||
double getMagSq() const { return m_sink.getMagSq(); }
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
|
||||
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
bool getAudioActive() const { return m_sink.getAudioActive(); }
|
||||
void setBasebandSampleRate(int sampleRate);
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownSampleChannelizer *m_channelizer;
|
||||
SSBDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
SSBDemodSettings m_settings;
|
||||
unsigned int m_audioSampleRate;
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const SSBDemodSettings& settings, bool force = false);
|
||||
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SSBDEMODBASEBAND_H
|
@ -219,11 +219,13 @@ void SSBDemodGUI::on_audioMute_toggled(bool checked)
|
||||
|
||||
void SSBDemodGUI::on_spanLog2_valueChanged(int value)
|
||||
{
|
||||
if ((value < 0) || (value > 4)) {
|
||||
unsigned int s2max = spanLog2Max();
|
||||
|
||||
if ((value < 0) || (value > s2max-1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyBandwidths(5 - ui->spanLog2->value());
|
||||
applyBandwidths(s2max - ui->spanLog2->value());
|
||||
}
|
||||
|
||||
void SSBDemodGUI::on_flipSidebands_clicked(bool checked)
|
||||
@ -308,7 +310,8 @@ SSBDemodGUI::SSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
|
||||
m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum);
|
||||
m_ssbDemod = (SSBDemod*) rxChannel; //new SSBDemod(m_deviceUISet->m_deviceSourceAPI);
|
||||
m_ssbDemod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
m_ssbDemod->setSampleSink(m_spectrumVis);
|
||||
m_ssbDemod->propagateMessageQueueToGUI();
|
||||
m_ssbDemod->setSpectrumSink(m_spectrumVis);
|
||||
|
||||
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
|
||||
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
|
||||
@ -378,34 +381,30 @@ void SSBDemodGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
SSBDemod::MsgConfigureChannelizer* channelConfigMsg = SSBDemod::MsgConfigureChannelizer::create(
|
||||
m_ssbDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency());
|
||||
m_ssbDemod->getInputMessageQueue()->push(channelConfigMsg);
|
||||
|
||||
SSBDemod::MsgConfigureSSBDemod* message = SSBDemod::MsgConfigureSSBDemod::create( m_settings, force);
|
||||
m_ssbDemod->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
|
||||
int SSBDemodGUI::spanLog2Limit(int spanLog2)
|
||||
unsigned int SSBDemodGUI::spanLog2Max()
|
||||
{
|
||||
while (((m_ssbDemod->getAudioSampleRate() / (1<<spanLog2)) > m_ssbDemod->getInputSampleRate()) && (spanLog2 < 4)) {
|
||||
spanLog2++;
|
||||
}
|
||||
|
||||
return spanLog2;
|
||||
unsigned int spanLog2 = 0;
|
||||
for (; m_ssbDemod->getAudioSampleRate() / (1<<spanLog2) >= 1000; spanLog2++);
|
||||
return spanLog2 == 0 ? 0 : spanLog2-1;
|
||||
}
|
||||
|
||||
void SSBDemodGUI::applyBandwidths(int spanLog2, bool force)
|
||||
void SSBDemodGUI::applyBandwidths(unsigned int spanLog2, bool force)
|
||||
{
|
||||
spanLog2 = spanLog2Limit(spanLog2);
|
||||
ui->spanLog2->setMaximum(5 - spanLog2Limit(1));
|
||||
unsigned int s2max = spanLog2Max();
|
||||
spanLog2 = spanLog2 > s2max ? s2max : spanLog2;
|
||||
unsigned int limit = s2max < 1 ? 0 : s2max - 1;
|
||||
ui->spanLog2->setMaximum(limit);
|
||||
bool dsb = ui->dsb->isChecked();
|
||||
//int spanLog2 = ui->spanLog2->value();
|
||||
m_spectrumRate = m_ssbDemod->getAudioSampleRate() / (1<<spanLog2);
|
||||
int bw = ui->BW->value();
|
||||
int lw = ui->lowCut->value();
|
||||
int bwMax = std::min(m_ssbDemod->getAudioSampleRate() / (100*(1<<spanLog2)), m_ssbDemod->getInputSampleRate()/100);
|
||||
int bwMax = m_ssbDemod->getAudioSampleRate() / (100*(1<<spanLog2));
|
||||
int tickInterval = m_spectrumRate / 1200;
|
||||
tickInterval = tickInterval == 0 ? 1 : tickInterval;
|
||||
|
||||
|
@ -70,8 +70,8 @@ private:
|
||||
|
||||
bool blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void applyBandwidths(int spanLog2, bool force = false);
|
||||
int spanLog2Limit(int spanLog2);
|
||||
void applyBandwidths(unsigned int spanLog2, bool force = false);
|
||||
unsigned int spanLog2Max();
|
||||
void displaySettings();
|
||||
void displayStreamIndex();
|
||||
void displayAGCPowerThreshold(int value);
|
||||
|
399
plugins/channelrx/demodssb/ssbdemodsink.cpp
Normal file
399
plugins/channelrx/demodssb/ssbdemodsink.cpp
Normal file
@ -0,0 +1,399 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 <QTime>
|
||||
#include <QDebug>
|
||||
|
||||
#include "audio/audiooutput.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "ssbdemodsink.h"
|
||||
|
||||
const int SSBDemodSink::m_ssbFftLen = 1024;
|
||||
const int SSBDemodSink::m_agcTarget = 3276.8; // -10 dB amplitude => -20 dB power: center of normal signal
|
||||
|
||||
SSBDemodSink::SSBDemodSink() :
|
||||
m_audioBinaual(false),
|
||||
m_audioFlipChannels(false),
|
||||
m_dsb(false),
|
||||
m_audioMute(false),
|
||||
m_agc(12000, m_agcTarget, 1e-2),
|
||||
m_agcActive(false),
|
||||
m_agcClamping(false),
|
||||
m_agcNbSamples(12000),
|
||||
m_agcPowerThreshold(1e-2),
|
||||
m_agcThresholdGate(0),
|
||||
m_squelchDelayLine(2*48000),
|
||||
m_audioActive(false),
|
||||
m_spectrumSink(nullptr),
|
||||
m_audioFifo(24000)
|
||||
{
|
||||
m_Bandwidth = 5000;
|
||||
m_LowCutoff = 300;
|
||||
m_volume = 2.0;
|
||||
m_spanLog2 = 3;
|
||||
m_channelSampleRate = 48000;
|
||||
m_channelFrequencyOffset = 0;
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
m_undersampleCount = 0;
|
||||
m_sum = 0;
|
||||
|
||||
m_usb = true;
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
|
||||
m_agc.setClampMax(SDR_RX_SCALED/100.0);
|
||||
m_agc.setClamping(m_agcClamping);
|
||||
|
||||
SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, m_ssbFftLen);
|
||||
DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen);
|
||||
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
applySettings(m_settings, true);
|
||||
}
|
||||
|
||||
SSBDemodSink::~SSBDemodSink()
|
||||
{
|
||||
delete SSBFilter;
|
||||
delete DSBFilter;
|
||||
}
|
||||
|
||||
void SSBDemodSink::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
|
||||
{
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
|
||||
{
|
||||
processOneSample(ci);
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SSBDemodSink::processOneSample(Complex &ci)
|
||||
{
|
||||
fftfilt::cmplx *sideband;
|
||||
int n_out = 0;
|
||||
int decim = 1<<(m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
if (m_dsb) {
|
||||
n_out = DSBFilter->runDSB(ci, &sideband);
|
||||
} else {
|
||||
n_out = SSBFilter->runSSB(ci, &sideband, m_usb);
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += sideband[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = m_sum.real() / decim;
|
||||
Real avgi = m_sum.imag() / decim;
|
||||
m_magsq = (avgr * avgr + avgi * avgi) / (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
|
||||
m_magsqSum += m_magsq;
|
||||
|
||||
if (m_magsq > m_magsqPeak)
|
||||
{
|
||||
m_magsqPeak = m_magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (!m_dsb & !m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
|
||||
float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 0.1;
|
||||
fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay());
|
||||
m_audioActive = delayedSample.real() != 0.0;
|
||||
m_squelchDelayLine.write(sideband[i]*agcVal);
|
||||
|
||||
if (m_audioMute)
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = 0;
|
||||
m_audioBuffer[m_audioBufferFill].l = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fftfilt::cmplx z = m_agcActive ? delayedSample * m_agc.getStepValue() : delayedSample;
|
||||
|
||||
if (m_audioBinaual)
|
||||
{
|
||||
if (m_audioFlipChannels)
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume);
|
||||
m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Real demod = (z.real() + z.imag()) * 0.7;
|
||||
qint16 sample = (qint16)(demod * m_volume);
|
||||
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("SSBDemodSink::feed: %u/%u samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill) {
|
||||
qDebug("SSBDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
if (m_spectrumSink != 0) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_dsb);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
void SSBDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "SSBDemodSink::applyChannelSettings:"
|
||||
<< " channelSampleRate: " << channelSampleRate
|
||||
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
||||
|
||||
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
|
||||
(m_channelSampleRate != channelSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((m_channelSampleRate != channelSampleRate) || force)
|
||||
{
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > channelSampleRate ? channelSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, channelSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void SSBDemodSink::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("SSBDemodSink::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_channelSampleRate ? m_channelSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, m_channelSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate;
|
||||
|
||||
SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate);
|
||||
DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate);
|
||||
|
||||
int agcNbSamples = (sampleRate / 1000) * (1<<m_settings.m_agcTimeLog2);
|
||||
int agcThresholdGate = (sampleRate / 1000) * m_settings.m_agcThresholdGate; // ms
|
||||
|
||||
if (m_agcNbSamples != agcNbSamples)
|
||||
{
|
||||
m_agc.resize(agcNbSamples, agcNbSamples/2, m_agcTarget);
|
||||
m_agc.setStepDownDelay(agcNbSamples);
|
||||
m_agcNbSamples = agcNbSamples;
|
||||
}
|
||||
|
||||
if (m_agcThresholdGate != agcThresholdGate)
|
||||
{
|
||||
m_agc.setGate(agcThresholdGate);
|
||||
m_agcThresholdGate = agcThresholdGate;
|
||||
}
|
||||
|
||||
m_audioFifo.setSize(sampleRate);
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void SSBDemodSink::applySettings(const SSBDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "SSBDemodSink::applySettings:"
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_rfBandwidth: " << settings.m_rfBandwidth
|
||||
<< " m_lowCutoff: " << settings.m_lowCutoff
|
||||
<< " m_volume: " << settings.m_volume
|
||||
<< " m_spanLog2: " << settings.m_spanLog2
|
||||
<< " m_audioBinaual: " << settings.m_audioBinaural
|
||||
<< " m_audioFlipChannels: " << settings.m_audioFlipChannels
|
||||
<< " m_dsb: " << settings.m_dsb
|
||||
<< " m_audioMute: " << settings.m_audioMute
|
||||
<< " m_agcActive: " << settings.m_agc
|
||||
<< " m_agcClamping: " << settings.m_agcClamping
|
||||
<< " m_agcTimeLog2: " << settings.m_agcTimeLog2
|
||||
<< " agcPowerThreshold: " << settings.m_agcPowerThreshold
|
||||
<< " agcThresholdGate: " << settings.m_agcThresholdGate
|
||||
<< " m_audioDeviceName: " << settings.m_audioDeviceName
|
||||
<< " m_streamIndex: " << settings.m_streamIndex
|
||||
<< " m_useReverseAPI: " << settings.m_useReverseAPI
|
||||
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
|
||||
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
|
||||
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
|
||||
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
|
||||
<< " force: " << force;
|
||||
|
||||
if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
|
||||
(m_settings.m_lowCutoff != settings.m_lowCutoff) || force)
|
||||
{
|
||||
float band, lowCutoff;
|
||||
|
||||
band = settings.m_rfBandwidth;
|
||||
lowCutoff = settings.m_lowCutoff;
|
||||
|
||||
if (band < 0) {
|
||||
band = -band;
|
||||
lowCutoff = -lowCutoff;
|
||||
m_usb = false;
|
||||
} else {
|
||||
m_usb = true;
|
||||
}
|
||||
|
||||
if (band < 100.0f)
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
m_Bandwidth = band;
|
||||
m_LowCutoff = lowCutoff;
|
||||
|
||||
Real interpolatorBandwidth = (m_Bandwidth * 1.5f) > m_channelSampleRate ? m_channelSampleRate : (m_Bandwidth * 1.5f);
|
||||
m_interpolator.create(16, m_channelSampleRate, interpolatorBandwidth, 2.0f);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
|
||||
SSBFilter->create_filter(m_LowCutoff / (float) m_audioSampleRate, m_Bandwidth / (float) m_audioSampleRate);
|
||||
DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) m_audioSampleRate);
|
||||
}
|
||||
|
||||
if ((m_settings.m_volume != settings.m_volume) || force)
|
||||
{
|
||||
m_volume = settings.m_volume;
|
||||
m_volume /= 4.0; // for 3276.8
|
||||
}
|
||||
|
||||
if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) ||
|
||||
(m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) ||
|
||||
(m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) ||
|
||||
(m_settings.m_agcClamping != settings.m_agcClamping) || force)
|
||||
{
|
||||
int agcNbSamples = (m_audioSampleRate / 1000) * (1<<settings.m_agcTimeLog2);
|
||||
m_agc.setThresholdEnable(settings.m_agcPowerThreshold != -SSBDemodSettings::m_minPowerThresholdDB);
|
||||
double agcPowerThreshold = CalcDb::powerFromdB(settings.m_agcPowerThreshold) * (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
int agcThresholdGate = (m_audioSampleRate / 1000) * settings.m_agcThresholdGate; // ms
|
||||
bool agcClamping = settings.m_agcClamping;
|
||||
|
||||
if (m_agcNbSamples != agcNbSamples)
|
||||
{
|
||||
m_agc.resize(agcNbSamples, agcNbSamples/2, m_agcTarget);
|
||||
m_agc.setStepDownDelay(agcNbSamples);
|
||||
m_agcNbSamples = agcNbSamples;
|
||||
}
|
||||
|
||||
if (m_agcPowerThreshold != agcPowerThreshold)
|
||||
{
|
||||
m_agc.setThreshold(agcPowerThreshold);
|
||||
m_agcPowerThreshold = agcPowerThreshold;
|
||||
}
|
||||
|
||||
if (m_agcThresholdGate != agcThresholdGate)
|
||||
{
|
||||
m_agc.setGate(agcThresholdGate);
|
||||
m_agcThresholdGate = agcThresholdGate;
|
||||
}
|
||||
|
||||
if (m_agcClamping != agcClamping)
|
||||
{
|
||||
m_agc.setClamping(agcClamping);
|
||||
m_agcClamping = agcClamping;
|
||||
}
|
||||
|
||||
qDebug() << "SBDemodSink::applySettings: AGC:"
|
||||
<< " agcNbSamples: " << agcNbSamples
|
||||
<< " agcPowerThreshold: " << agcPowerThreshold
|
||||
<< " agcThresholdGate: " << agcThresholdGate
|
||||
<< " agcClamping: " << agcClamping;
|
||||
}
|
||||
|
||||
m_spanLog2 = settings.m_spanLog2;
|
||||
m_audioBinaual = settings.m_audioBinaural;
|
||||
m_audioFlipChannels = settings.m_audioFlipChannels;
|
||||
m_dsb = settings.m_dsb;
|
||||
m_audioMute = settings.m_audioMute;
|
||||
m_agcActive = settings.m_agc;
|
||||
m_settings = settings;
|
||||
}
|
||||
|
130
plugins/channelrx/demodssb/ssbdemodsink.h
Normal file
130
plugins/channelrx/demodssb/ssbdemodsink.h
Normal file
@ -0,0 +1,130 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_SSBDEMODSINK_H
|
||||
#define INCLUDE_SSBDEMODSINK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/doublebufferfifo.h"
|
||||
|
||||
#include "ssbdemodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
|
||||
class SSBDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
SSBDemodSink();
|
||||
~SSBDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
void setSpectrumSink(BasebandSampleSink* spectrumSink) { m_spectrumSink = spectrumSink; }
|
||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const SSBDemodSettings& settings, bool force = false);
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
|
||||
AudioFifo *getAudioFifo() { return &m_audioFifo; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
bool getAudioActive() const { return m_audioActive; }
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
SSBDemodSettings m_settings;
|
||||
|
||||
Real m_Bandwidth;
|
||||
Real m_LowCutoff;
|
||||
Real m_volume;
|
||||
int m_spanLog2;
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
bool m_audioBinaual;
|
||||
bool m_audioFlipChannels;
|
||||
bool m_usb;
|
||||
bool m_dsb;
|
||||
bool m_audioMute;
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MagAGC m_agc;
|
||||
bool m_agcActive;
|
||||
bool m_agcClamping;
|
||||
int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging
|
||||
double m_agcPowerThreshold; //!< AGC power threshold (linear)
|
||||
int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers
|
||||
DoubleBufferFIFO<fftfilt::cmplx> m_squelchDelayLine;
|
||||
bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold)
|
||||
|
||||
NCOF m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
fftfilt* SSBFilter;
|
||||
fftfilt* DSBFilter;
|
||||
|
||||
BasebandSampleSink* m_spectrumSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
quint32 m_audioSampleRate;
|
||||
|
||||
static const int m_ssbFftLen;
|
||||
static const int m_agcTarget;
|
||||
|
||||
void processOneSample(Complex &ci);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SSBDEMODSINK_H
|
@ -11,7 +11,7 @@
|
||||
|
||||
const PluginDescriptor SSBPlugin::m_pluginDescriptor = {
|
||||
QString("SSB Demodulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.2"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
@ -3,6 +3,8 @@ project(wfm)
|
||||
set(wfm_SOURCES
|
||||
wfmdemod.cpp
|
||||
wfmdemodsettings.cpp
|
||||
wfmdemodsink.cpp
|
||||
wfmdemodbaseband.cpp
|
||||
wfmdemodwebapiadapter.cpp
|
||||
wfmplugin.cpp
|
||||
)
|
||||
@ -10,6 +12,8 @@ set(wfm_SOURCES
|
||||
set(wfm_HEADERS
|
||||
wfmdemod.h
|
||||
wfmdemodsettings.h
|
||||
wfmdemodsink.h
|
||||
wfmdemodbaseband.h
|
||||
wfmdemodwebapiadapter.h
|
||||
wfmplugin.h
|
||||
)
|
||||
@ -22,7 +26,6 @@ if(NOT SERVER_MODE)
|
||||
set(wfm_SOURCES
|
||||
${wfm_SOURCES}
|
||||
wfmdemodgui.cpp
|
||||
|
||||
wfmdemodgui.ui
|
||||
)
|
||||
set(wfm_HEADERS
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGWFMDemodSettings.h"
|
||||
@ -44,7 +45,6 @@
|
||||
#include "wfmdemod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureWFMDemod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(WFMDemod::MsgConfigureChannelizer, Message)
|
||||
|
||||
const QString WFMDemod::m_channelIdURI = "sdrangel.channel.wfmdemod";
|
||||
const QString WFMDemod::m_channelId = "WFMDemod";
|
||||
@ -53,33 +53,17 @@ const int WFMDemod::m_udpBlockSize = 512;
|
||||
WFMDemod::WFMDemod(DeviceAPI* deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_inputSampleRate(384000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_squelchOpen(false),
|
||||
m_magsq(0.0f),
|
||||
m_magsqSum(0.0f),
|
||||
m_magsqPeak(0.0f),
|
||||
m_magsqCount(0),
|
||||
m_audioFifo(250000),
|
||||
m_settingsMutex(QMutex::Recursive)
|
||||
m_basebandSampleRate(0)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, rfFilterFftLength);
|
||||
m_phaseDiscri.setFMScaling(384000/75000);
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSink = new WFMDemodBaseband();
|
||||
m_basebandSink->moveToThread(m_thread);
|
||||
|
||||
m_audioBuffer.resize(16384);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, 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->addChannelSink(this);
|
||||
m_deviceAPI->addChannelSinkAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -90,13 +74,11 @@ WFMDemod::~WFMDemod()
|
||||
{
|
||||
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 m_rfFilter;
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
delete m_basebandSink;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
uint32_t WFMDemod::getNumberOfDeviceStreams() const
|
||||
@ -107,137 +89,31 @@ uint32_t WFMDemod::getNumberOfDeviceStreams() const
|
||||
void WFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
||||
{
|
||||
(void) firstOfBurst;
|
||||
Complex ci;
|
||||
fftfilt::cmplx *rf;
|
||||
int rf_out;
|
||||
Real demod;
|
||||
double msq;
|
||||
float fmDev;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
||||
{
|
||||
Complex c(it->real(), it->imag());
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
rf_out = m_rfFilter->runFilt(c, &rf); // filter RF before demod
|
||||
|
||||
for (int i = 0 ; i < rf_out; i++)
|
||||
{
|
||||
msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag();
|
||||
Real magsq = msq / (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
m_magsqSum += magsq;
|
||||
m_movingAverage(magsq);
|
||||
|
||||
if (magsq > m_magsqPeak) {
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (magsq >= m_squelchLevel)
|
||||
{
|
||||
if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate
|
||||
m_squelchState++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchState > 0) {
|
||||
m_squelchState--;
|
||||
}
|
||||
}
|
||||
|
||||
m_squelchOpen = (m_squelchState > (m_settings.m_rfBandwidth / 20));
|
||||
|
||||
if (m_squelchOpen && !m_settings.m_audioMute) { // squelch open and not mute
|
||||
demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev);
|
||||
} else {
|
||||
demod = 0;
|
||||
}
|
||||
|
||||
Complex e(demod, 0);
|
||||
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci))
|
||||
{
|
||||
qint16 sample = (qint16)(ci.real() * 3276.8f * m_settings.m_volume);
|
||||
m_sampleBuffer.push_back(Sample(sample, sample));
|
||||
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("WFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_audioBufferFill > 0)
|
||||
{
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill) {
|
||||
qDebug("WFMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
|
||||
void WFMDemod::start()
|
||||
{
|
||||
m_squelchState = 0;
|
||||
m_audioFifo.clear();
|
||||
m_phaseDiscri.reset();
|
||||
applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug() << "WFMDemod::start";
|
||||
|
||||
if (m_basebandSampleRate != 0) {
|
||||
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
|
||||
}
|
||||
|
||||
m_basebandSink->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void WFMDemod::stop()
|
||||
{
|
||||
qDebug() << "WFMDemod::stop";
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
bool WFMDemod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DownChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "WFMDemod::handleMessage: MsgChannelizerNotification: m_inputSampleRate: " << notif.getSampleRate()
|
||||
<< " m_inputFrequencyOffset: " << notif.getFrequencyOffset();
|
||||
|
||||
applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "WFMDemod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " sampleRate: " << cfg.getSampleRate()
|
||||
<< " inputFrequencyOffset: " << cfg.getCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureWFMDemod::match(cmd))
|
||||
if (MsgConfigureWFMDemod::match(cmd))
|
||||
{
|
||||
MsgConfigureWFMDemod& cfg = (MsgConfigureWFMDemod&) cmd;
|
||||
qDebug("WFMDemod::handleMessage: MsgConfigureWFMDemod");
|
||||
@ -246,29 +122,15 @@ bool WFMDemod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (BasebandSampleSink::MsgThreadedSink::match(cmd))
|
||||
{
|
||||
BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd;
|
||||
const QThread *thread = cfg.getThread();
|
||||
qDebug("WFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread);
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
|
||||
qDebug() << "WFMDemod::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() << "WFMDemod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -277,54 +139,6 @@ bool WFMDemod::handleMessage(const Message& cmd)
|
||||
}
|
||||
}
|
||||
|
||||
void WFMDemod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("WFMDemod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolator.create(16, m_inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_inputSampleRate / sampleRate;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void WFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "WFMDemod::applyChannelSettings:"
|
||||
<< " inputSampleRate: " << inputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(inputSampleRate != m_inputSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-inputFrequencyOffset, inputSampleRate);
|
||||
}
|
||||
|
||||
if ((inputSampleRate != m_inputSampleRate) || force)
|
||||
{
|
||||
qDebug() << "WFMDemod::applyChannelSettings: m_interpolator.create";
|
||||
m_settingsMutex.lock();
|
||||
m_interpolator.create(16, inputSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) inputSampleRate / (Real) m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
|
||||
m_settingsMutex.unlock();
|
||||
qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter";
|
||||
Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / inputSampleRate;
|
||||
Real hiCut = (m_settings.m_rfBandwidth / 2.0) / inputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_fmExcursion = m_settings.m_rfBandwidth / (Real) inputSampleRate;
|
||||
m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
|
||||
qDebug("WFMDemod::applySettings: m_fmExcursion: %f", m_fmExcursion);
|
||||
}
|
||||
|
||||
m_inputSampleRate = inputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "WFMDemod::applySettings:"
|
||||
@ -373,58 +187,22 @@ void WFMDemod::applySettings(const WFMDemodSettings& settings, bool force)
|
||||
reverseAPIKeys.append("rgbColor");
|
||||
}
|
||||
|
||||
if((settings.m_afBandwidth != m_settings.m_afBandwidth) ||
|
||||
(settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
qDebug() << "WFMDemod::applySettings: m_interpolator.create";
|
||||
m_interpolator.create(16, m_inputSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
|
||||
qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter";
|
||||
Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_inputSampleRate;
|
||||
Real hiCut = (settings.m_rfBandwidth / 2.0) / m_inputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_fmExcursion = settings.m_rfBandwidth / (Real) m_inputSampleRate;
|
||||
m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
|
||||
qDebug("WFMDemod::applySettings: m_fmExcursion: %f", m_fmExcursion);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_squelch != m_settings.m_squelch) || force)
|
||||
{
|
||||
qDebug() << "WFMDemod::applySettings: set m_squelchLevel";
|
||||
m_squelchLevel = pow(10.0, settings.m_squelch / 10.0);
|
||||
}
|
||||
|
||||
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 (m_settings.m_streamIndex != settings.m_streamIndex)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
WFMDemodBaseband::MsgConfigureWFMDemodBaseband *msg = WFMDemodBaseband::MsgConfigureWFMDemodBaseband::create(settings, force);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -481,13 +259,6 @@ int WFMDemod::webapiSettingsPutPatch(
|
||||
WFMDemodSettings settings = m_settings;
|
||||
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
||||
|
||||
if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
|
||||
{
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
requiredBW(settings.m_rfBandwidth), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
}
|
||||
|
||||
MsgConfigureWFMDemod *msg = MsgConfigureWFMDemod::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
@ -609,9 +380,9 @@ void WFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response
|
||||
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||
|
||||
response.getWfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
|
||||
response.getWfmDemodReport()->setSquelch(m_squelchState > 0 ? 1 : 0);
|
||||
response.getWfmDemodReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getWfmDemodReport()->setChannelSampleRate(m_inputSampleRate);
|
||||
response.getWfmDemodReport()->setSquelch(m_basebandSink->getSquelchState() > 0 ? 1 : 0);
|
||||
response.getWfmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
|
||||
response.getWfmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void WFMDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WFMDemodSettings& settings, bool force)
|
||||
|
@ -26,23 +26,12 @@
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/phasediscri.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "wfmdemodsettings.h"
|
||||
|
||||
#define rfFilterFftLength 1024
|
||||
#include "wfmdemodbaseband.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class ThreadedBasebandSampleSink;
|
||||
class DownChannelizer;
|
||||
class DeviceAPI;
|
||||
|
||||
class WFMDemod : public BasebandSampleSink, public ChannelAPI {
|
||||
@ -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)
|
||||
{ }
|
||||
};
|
||||
|
||||
WFMDemod(DeviceAPI *deviceAPI);
|
||||
virtual ~WFMDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
@ -120,26 +86,10 @@ public:
|
||||
return m_settings.m_inputFrequencyOffset;
|
||||
}
|
||||
|
||||
double getMagSq() const { return m_movingAverage.asDouble(); }
|
||||
bool getSquelchOpen() const { return m_squelchOpen; }
|
||||
double getMagSq() const { return m_basebandSink->getMagSq(); }
|
||||
bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); }
|
||||
|
||||
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); }
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
@ -164,79 +114,23 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
static int requiredBW(int rfBW)
|
||||
{
|
||||
if (rfBW <= 48000) {
|
||||
return 48000;
|
||||
} else {
|
||||
return (3*rfBW)/2;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
RSRunning
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSink* m_threadedChannelizer;
|
||||
DownChannelizer* m_channelizer;
|
||||
|
||||
int m_inputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
WFMDemodBaseband* m_basebandSink;
|
||||
WFMDemodSettings m_settings;
|
||||
quint32 m_audioSampleRate;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator; //!< Interpolator between sample rate sent from DSP engine and requested RF bandwidth (rational)
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
fftfilt* m_rfFilter;
|
||||
|
||||
Real m_squelchLevel;
|
||||
int m_squelchState;
|
||||
bool m_squelchOpen;
|
||||
double m_magsq; //!< displayed averaged value
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
Real m_fmExcursion;
|
||||
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
|
||||
AudioFifo m_audioFifo;
|
||||
SampleVector m_sampleBuffer;
|
||||
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 applyAudioSampleRate(int sampleRate);
|
||||
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const WFMDemodSettings& settings, bool force = false);
|
||||
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
171
plugins/channelrx/demodwfm/wfmdemodbaseband.cpp
Normal file
171
plugins/channelrx/demodwfm/wfmdemodbaseband.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "wfmdemodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(WFMDemodBaseband::MsgConfigureWFMDemodBaseband, Message)
|
||||
|
||||
WFMDemodBaseband::WFMDemodBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownSampleChannelizer(&m_sink);
|
||||
|
||||
qDebug("WFMDemodBaseband::WFMDemodBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&WFMDemodBaseband::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()));
|
||||
}
|
||||
|
||||
WFMDemodBaseband::~WFMDemodBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void WFMDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void WFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void WFMDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
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 WFMDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WFMDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureWFMDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureWFMDemodBaseband& cfg = (MsgConfigureWFMDemodBaseband&) cmd;
|
||||
qDebug() << "WFMDemodBaseband::handleMessage: MsgConfigureWFMDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "WFMDemodBaseband::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 WFMDemodBaseband::applySettings(const WFMDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|
||||
|| (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(WFMDemodSettings::requiredBW(settings.m_rfBandwidth), 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_sink.applySettings(settings, force);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int WFMDemodBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
|
||||
void WFMDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
89
plugins/channelrx/demodwfm/wfmdemodbaseband.h
Normal file
89
plugins/channelrx/demodwfm/wfmdemodbaseband.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_WFMDEMODBASEBAND_H
|
||||
#define INCLUDE_WFMDEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "wfmdemodsink.h"
|
||||
|
||||
class DownSampleChannelizer;
|
||||
|
||||
class WFMDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureWFMDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const WFMDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureWFMDemodBaseband* create(const WFMDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureWFMDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
WFMDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureWFMDemodBaseband(const WFMDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
WFMDemodBaseband();
|
||||
~WFMDemodBaseband();
|
||||
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 setBasebandSampleRate(int sampleRate);
|
||||
|
||||
unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); }
|
||||
double getMagSq() const { return m_sink.getMagSq(); }
|
||||
bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
|
||||
int getSquelchState() const { return m_sink.getSquelchState(); }
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownSampleChannelizer *m_channelizer;
|
||||
WFMDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
WFMDemodSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const WFMDemodSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_WFMDEMODBASEBAND_H
|
@ -282,11 +282,6 @@ void WFMDemodGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
WFMDemod::MsgConfigureChannelizer *msgChan = WFMDemod::MsgConfigureChannelizer::create(
|
||||
WFMDemod::requiredBW(m_settings.m_rfBandwidth),
|
||||
m_channelMarker.getCenterFrequency());
|
||||
m_wfmDemod->getInputMessageQueue()->push(msgChan);
|
||||
|
||||
WFMDemod::MsgConfigureWFMDemod* msgConfig = WFMDemod::MsgConfigureWFMDemod::create( m_settings, force);
|
||||
m_wfmDemod->getInputMessageQueue()->push(msgConfig);
|
||||
}
|
||||
|
@ -52,6 +52,15 @@ struct WFMDemodSettings
|
||||
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
|
||||
static int requiredBW(int rfBW)
|
||||
{
|
||||
if (rfBW <= 48000) {
|
||||
return 48000;
|
||||
} else {
|
||||
return (3*rfBW)/2;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* PLUGINS_CHANNELRX_DEMODWFM_WFMDEMODSETTINGS_H_ */
|
||||
|
232
plugins/channelrx/demodwfm/wfmdemodsink.cpp
Normal file
232
plugins/channelrx/demodwfm/wfmdemodsink.cpp
Normal file
@ -0,0 +1,232 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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 "audio/audiooutput.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "wfmdemodsink.h"
|
||||
|
||||
const unsigned int WFMDemodSink::m_rfFilterFftLength = 1024;
|
||||
|
||||
WFMDemodSink::WFMDemodSink() :
|
||||
m_channelSampleRate(384000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_squelchOpen(false),
|
||||
m_magsq(0.0f),
|
||||
m_magsqSum(0.0f),
|
||||
m_magsqPeak(0.0f),
|
||||
m_magsqCount(0),
|
||||
m_audioFifo(250000)
|
||||
{
|
||||
m_rfFilter = new fftfilt(-50000.0 / 384000.0, 50000.0 / 384000.0, m_rfFilterFftLength);
|
||||
m_phaseDiscri.setFMScaling(384000/75000);
|
||||
|
||||
m_audioBuffer.resize(16384);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
WFMDemodSink::~WFMDemodSink()
|
||||
{
|
||||
delete m_rfFilter;
|
||||
}
|
||||
|
||||
void WFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
Complex ci;
|
||||
fftfilt::cmplx *rf;
|
||||
int rf_out;
|
||||
Real demod;
|
||||
double msq;
|
||||
float fmDev;
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
||||
{
|
||||
Complex c(it->real(), it->imag());
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
rf_out = m_rfFilter->runFilt(c, &rf); // filter RF before demod
|
||||
|
||||
for (int i = 0 ; i < rf_out; i++)
|
||||
{
|
||||
msq = rf[i].real()*rf[i].real() + rf[i].imag()*rf[i].imag();
|
||||
Real magsq = msq / (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
m_magsqSum += magsq;
|
||||
m_movingAverage(magsq);
|
||||
|
||||
if (magsq > m_magsqPeak) {
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
|
||||
if (magsq >= m_squelchLevel)
|
||||
{
|
||||
if (m_squelchState < m_settings.m_rfBandwidth / 10) { // twice attack and decay rate
|
||||
m_squelchState++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_squelchState > 0) {
|
||||
m_squelchState--;
|
||||
}
|
||||
}
|
||||
|
||||
m_squelchOpen = (m_squelchState > (m_settings.m_rfBandwidth / 20));
|
||||
|
||||
if (m_squelchOpen && !m_settings.m_audioMute) { // squelch open and not mute
|
||||
demod = m_phaseDiscri.phaseDiscriminatorDelta(rf[i], msq, fmDev);
|
||||
} else {
|
||||
demod = 0;
|
||||
}
|
||||
|
||||
Complex e(demod, 0);
|
||||
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, e, &ci))
|
||||
{
|
||||
qint16 sample = (qint16)(ci.real() * 3276.8f * m_settings.m_volume);
|
||||
m_sampleBuffer.push_back(Sample(sample, sample));
|
||||
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("WFMDemodSink::feed: %u/%u audio samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_audioBufferFill > 0)
|
||||
{
|
||||
uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
|
||||
|
||||
if (res != m_audioBufferFill) {
|
||||
qDebug("WFMDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill);
|
||||
}
|
||||
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
void WFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("WFMDemodSink::applyAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_interpolator.create(16, m_channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_channelSampleRate / sampleRate;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate;
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void WFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "WFMDemodSink::applyChannelSettings:"
|
||||
<< " channelSampleRate: " << channelSampleRate
|
||||
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
||||
|
||||
if((channelFrequencyOffset != m_channelFrequencyOffset) ||
|
||||
(channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((channelSampleRate != m_channelSampleRate) || force)
|
||||
{
|
||||
qDebug() << "WFMDemod::applyChannelSettings: m_interpolator.create";
|
||||
m_interpolator.create(16, channelSampleRate, m_settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) channelSampleRate / (Real) m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate;
|
||||
qDebug() << "WFMDemod::applySettings: m_rfFilter->create_filter";
|
||||
Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
|
||||
Real hiCut = (m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_fmExcursion = m_settings.m_rfBandwidth / (Real) channelSampleRate;
|
||||
m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
|
||||
qDebug("WFMDemod::applySettings: m_fmExcursion: %f", m_fmExcursion);
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void WFMDemodSink::applySettings(const WFMDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "WFMDemodSink::applySettings:"
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_rfBandwidth: " << settings.m_rfBandwidth
|
||||
<< " m_afBandwidth: " << settings.m_afBandwidth
|
||||
<< " m_volume: " << settings.m_volume
|
||||
<< " m_squelch: " << settings.m_squelch
|
||||
<< " m_audioDeviceName: " << settings.m_audioDeviceName
|
||||
<< " m_audioMute: " << settings.m_audioMute
|
||||
<< " m_streamIndex: " << settings.m_streamIndex
|
||||
<< " m_useReverseAPI: " << settings.m_useReverseAPI
|
||||
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
|
||||
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
|
||||
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
|
||||
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
|
||||
<< " force: " << force;
|
||||
|
||||
if((settings.m_afBandwidth != m_settings.m_afBandwidth) ||
|
||||
(settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
qDebug() << "WFMDemodSink::applySettings: m_interpolator.create";
|
||||
m_interpolator.create(16, m_channelSampleRate, settings.m_afBandwidth);
|
||||
m_interpolatorDistanceRemain = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate;
|
||||
qDebug() << "WFMDemodSink::applySettings: m_rfFilter->create_filter";
|
||||
Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
|
||||
Real hiCut = (settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_fmExcursion = settings.m_rfBandwidth / (Real) m_channelSampleRate;
|
||||
m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
|
||||
qDebug("WFMDemodSink::applySettings: m_fmExcursion: %f", m_fmExcursion);
|
||||
}
|
||||
|
||||
if ((settings.m_squelch != m_settings.m_squelch) || force)
|
||||
{
|
||||
qDebug() << "WFMDemodSink::applySettings: set m_squelchLevel";
|
||||
m_squelchLevel = pow(10.0, settings.m_squelch / 10.0);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
121
plugins/channelrx/demodwfm/wfmdemodsink.h
Normal file
121
plugins/channelrx/demodwfm/wfmdemodsink.h
Normal file
@ -0,0 +1,121 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_WFMDEMODSINK_H
|
||||
#define INCLUDE_WFMDEMODSINK_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/phasediscri.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "wfmdemodsettings.h"
|
||||
|
||||
class WFMDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
WFMDemodSink();
|
||||
~WFMDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
double getMagSq() const { return m_movingAverage.asDouble(); }
|
||||
bool getSquelchOpen() const { return m_squelchOpen; }
|
||||
int getSquelchState() const { return m_squelchState; }
|
||||
|
||||
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 WFMDemodSettings& settings, bool force = false);
|
||||
|
||||
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;
|
||||
WFMDemodSettings m_settings;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator; //!< Interpolator between sample rate sent from DSP engine and requested RF bandwidth (rational)
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
fftfilt* m_rfFilter;
|
||||
|
||||
Real m_squelchLevel;
|
||||
int m_squelchState;
|
||||
bool m_squelchOpen;
|
||||
double m_magsq; //!< displayed averaged value
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
Real m_fmExcursion;
|
||||
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
|
||||
AudioFifo m_audioFifo;
|
||||
SampleVector m_sampleBuffer;
|
||||
PhaseDiscriminators m_phaseDiscri;
|
||||
|
||||
static const unsigned int m_rfFilterFftLength;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_WFMDEMODSINK_H
|
@ -12,7 +12,7 @@
|
||||
|
||||
const PluginDescriptor WFMPlugin::m_pluginDescriptor = {
|
||||
QString("WFM Demodulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.2"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
Loading…
Reference in New Issue
Block a user