1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-12 11:26:11 -05:00

Rx plugins: refactoring of classes (2)

This commit is contained in:
f4exb 2019-12-03 18:49:52 +01:00
parent 0873672a74
commit 735f1cdbb4
53 changed files with 5515 additions and 2518 deletions

View File

@ -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
@ -23,16 +29,15 @@ set(bfm_HEADERS
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS}
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
set(bfm_SOURCES
${bfm_SOURCES}
bfmdemodgui.cpp
bfmdemodgui.ui
bfmdemodgui.cpp
bfmdemodgui.ui
)
set(bfm_HEADERS
${bfm_HEADERS}
@ -55,10 +60,10 @@ add_library(${TARGET_NAME} SHARED
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -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)
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
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;
applySettings(m_settings, true);
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->removeChannelSinkAPI(this);
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,107 +117,23 @@ 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))
{
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);
return true;
}
else
{
qDebug() << "BFMDemod::handleMessage: passed: " << cmd.getIdentifier();
if (m_sampleSink != 0)
{
return m_sampleSink->handleMessage(cmd);
}
else
{
return false;
}
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)
{
qDebug() << "BFMDemod::applySettings: MsgConfigureBFMDemod:"
@ -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()));

View File

@ -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;
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;
QThread *m_thread;
BFMDemodBaseband* m_basebandSink;
BFMDemodSettings m_settings;
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);

View 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);
}
}

View 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

View File

@ -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);
}

View File

@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "bfmdemodreport.h"
MESSAGE_CLASS_DEFINITION(BFMDemodReport::MsgReportChannelSampleRateChanged, Message)
BFMDemodReport::BFMDemodReport()
{}
BFMDemodReport::~BFMDemodReport()
{}

View 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

View File

@ -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;
}
}
};

View 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;
}

View 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

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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);
};

View 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());
}

View 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

View File

@ -0,0 +1,26 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "datvdemodreport.h"
MESSAGE_CLASS_DEFINITION(DATVDemodReport::MsgReportModcodCstlnChange, Message)
DATVDemodReport::DATVDemodReport()
{}
DATVDemodReport::~DATVDemodReport()
{}

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@ -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
)
@ -24,8 +27,8 @@ add_library(demodlora SHARED
)
target_link_libraries(demodlora
Qt5::Core
Qt5::Widgets
Qt5::Core
Qt5::Widgets
sdrbase
sdrgui
)

View File

@ -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);
}

View File

@ -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,342 +29,87 @@
#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->removeChannelSinkAPI(this);
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;
}
return false;
}
}
@ -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;
}

View File

@ -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

View 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()
);
}

View 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

View File

@ -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);
}

View 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;
}

View 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

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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")
@ -46,11 +48,11 @@ add_library(${TARGET_NAME} SHARED
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -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,117 +117,23 @@ 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;
}
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)
{
qDebug() << "SSBDemod::applySettings:"
@ -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)

View File

@ -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);
};

View 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());
}

View 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

View File

@ -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;

View File

@ -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);

View 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;
}

View 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

View File

@ -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,

View File

@ -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,8 +26,7 @@ if(NOT SERVER_MODE)
set(wfm_SOURCES
${wfm_SOURCES}
wfmdemodgui.cpp
wfmdemodgui.ui
wfmdemodgui.ui
)
set(wfm_HEADERS
${wfm_HEADERS}
@ -46,8 +49,8 @@ add_library(${TARGET_NAME} SHARED
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)

View File

@ -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->removeChannelSinkAPI(this);
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)

View File

@ -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);

View 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());
}

View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_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

View File

@ -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);
}

View File

@ -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_ */

View 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;
}

View 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

View File

@ -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,