1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-13 20:01:46 -05:00

WDSP Receiver as a copy of the SSB demodulator

This commit is contained in:
f4exb 2024-06-21 00:44:17 +02:00
parent 5b05b13c5d
commit 8ce840dd17
18 changed files with 4986 additions and 0 deletions

View File

@ -96,6 +96,7 @@ option(ENABLE_CHANNELRX_HEATMAP "Enable channelrx heatmap plugin" ON)
option(ENABLE_CHANNELRX_FREQSCANNER "Enable channelrx freqscanner plugin" ON) option(ENABLE_CHANNELRX_FREQSCANNER "Enable channelrx freqscanner plugin" ON)
option(ENABLE_CHANNELRX_ENDOFTRAIN "Enable channelrx end-of-train plugin" ON) option(ENABLE_CHANNELRX_ENDOFTRAIN "Enable channelrx end-of-train plugin" ON)
option(ENABLE_CHANNELRX_CHANNELPOWER "Enable channelrx channel power plugin" ON) option(ENABLE_CHANNELRX_CHANNELPOWER "Enable channelrx channel power plugin" ON)
option(ENABLE_CHANNELRX_WDSPRX "Enable channelrx WDSP receiver plugin" ON)
# Channel Tx enablers # Channel Tx enablers
option(ENABLE_CHANNELTX "Enable channeltx plugins" ON) option(ENABLE_CHANNELTX "Enable channeltx plugins" ON)

View File

@ -205,6 +205,13 @@ else()
message(STATUS "Not building demoddsc (ENABLE_CHANNELRX_DEMODDSC=${ENABLE_CHANNELRX_DEMODDSC})") message(STATUS "Not building demoddsc (ENABLE_CHANNELRX_DEMODDSC=${ENABLE_CHANNELRX_DEMODDSC})")
endif() endif()
if (ENABLE_CHANNELRX_WDSPRX AND WDSP_SUPPORT)
add_subdirectory(wdsprx)
else()
message(STATUS "Not building wdsprx (ENABLE_CHANNELRX_WDSPRX=${ENABLE_CHANNELRX_WDSPRX} WDSP_SUPPORT=${WDSP_SUPPORT})")
endif()
if(NOT SERVER_MODE) if(NOT SERVER_MODE)
if (ENABLE_CHANNELRX_HEATMAP) if (ENABLE_CHANNELRX_HEATMAP)
add_subdirectory(heatmap) add_subdirectory(heatmap)

View File

@ -0,0 +1,64 @@
project(wdsprx)
set(wdsprx_SOURCES
wdsprx.cpp
wdsprxsettings.cpp
wdsprxsink.cpp
wdsprxbaseband.cpp
wdsprxwebapiadapter.cpp
wdsprxplugin.cpp
)
set(wdsprx_HEADERS
wdsprx.h
wdsprxsettings.h
wdsprxsink.h
wdsprxbaseband.h
wdsprxwebapiadapter.h
wdsprxplugin.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(wdsprx_SOURCES
${wdsprx_SOURCES}
wdsprxgui.cpp
wdsprxgui.ui
)
set(wdsprx_HEADERS
${wdsprx_HEADERS}
wdsprxgui.h
)
set(TARGET_NAME wdsprx)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME wdsprxsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${wdsprx_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt::Core
${TARGET_LIB}
sdrbase
wdsp
${TARGET_LIB_GUI}
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,923 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// (c) 2014 Modified by John Greb
// //
// 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 <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <QMutexLocker>
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGSSBDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGSSBDemodReport.h"
#include "dsp/dspcommands.h"
#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "util/db.h"
#include "maincore.h"
#include "wdsprx.h"
MESSAGE_CLASS_DEFINITION(WDSPRx::MsgConfigureWDSPRx, Message)
const char* const WDSPRx::m_channelIdURI = "sdrangel.channel.wdsprx";
const char* const WDSPRx::m_channelId = "WDSPRx";
WDSPRx::WDSPRx(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_thread(nullptr),
m_basebandSink(nullptr),
m_running(false),
m_spectrumVis(SDR_RX_SCALEF),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&WDSPRx::networkManagerFinished
);
QObject::connect(
this,
&ChannelAPI::indexInDeviceSetChanged,
this,
&WDSPRx::handleIndexInDeviceSetChanged
);
}
WDSPRx::~WDSPRx()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&WDSPRx::networkManagerFinished
);
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
stop();
}
void WDSPRx::setDeviceAPI(DeviceAPI *deviceAPI)
{
if (deviceAPI != m_deviceAPI)
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
m_deviceAPI = deviceAPI;
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
}
}
uint32_t WDSPRx::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void WDSPRx::setMessageQueueToGUI(MessageQueue* queue)
{
ChannelAPI::setMessageQueueToGUI(queue);
if (m_basebandSink) {
m_basebandSink->setMessageQueueToGUI(queue);
}
}
void WDSPRx::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
{
(void) positiveOnly;
if (m_running) {
m_basebandSink->feed(begin, end);
}
}
void WDSPRx::start()
{
QMutexLocker m_lock(&m_mutex);
if (m_running) {
return;
}
qDebug() << "WDSPRx::start";
m_thread = new QThread();
m_basebandSink = new WDSPRxBaseband();
m_basebandSink->setFifoLabel(QString("%1 [%2:%3]")
.arg(m_channelId)
.arg(m_deviceAPI->getDeviceSetIndex())
.arg(getIndexInDeviceSet())
);
m_basebandSink->setSpectrumSink(&m_spectrumVis);
m_basebandSink->setChannel(this);
m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI());
m_basebandSink->moveToThread(m_thread);
QObject::connect(
m_thread,
&QThread::finished,
m_basebandSink,
&QObject::deleteLater
);
QObject::connect(
m_thread,
&QThread::finished,
m_thread,
&QThread::deleteLater
);
if (m_basebandSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_basebandSampleRate);
}
m_thread->start();
WDSPRxBaseband::MsgConfigureWDSPRxBaseband *msg = WDSPRxBaseband::MsgConfigureWDSPRxBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
m_running = true;
}
void WDSPRx::stop()
{
QMutexLocker m_lock(&m_mutex);
if (!m_running) {
return;
}
qDebug() << "WDSPRx::stop";
m_running = false;
m_thread->exit();
m_thread->wait();
}
bool WDSPRx::handleMessage(const Message& cmd)
{
if (MsgConfigureWDSPRx::match(cmd))
{
MsgConfigureWDSPRx& cfg = (MsgConfigureWDSPRx&) cmd;
qDebug("WDSPRx::handleMessage: MsgConfigureWDSPRx");
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
qDebug() << "WDSPRx::handleMessage: DSPSignalNotification";
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
// Forward to the sink
if (m_running) {
m_basebandSink->getInputMessageQueue()->push(new DSPSignalNotification(notif));
}
// Forwatd to GUI if any
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(new DSPSignalNotification(notif));
}
return true;
}
else if (MainCore::MsgChannelDemodQuery::match(cmd))
{
qDebug() << "WDSPRx::handleMessage: MsgChannelDemodQuery";
sendSampleRateToDemodAnalyzer();
return true;
}
else
{
return false;
}
}
void WDSPRx::setCenterFrequency(qint64 frequency)
{
WDSPRxSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureWDSPRx *msgToGUI = MsgConfigureWDSPRx::create(settings, false);
m_guiMessageQueue->push(msgToGUI);
}
}
void WDSPRx::applySettings(const WDSPRxSettings& settings, bool force)
{
qDebug() << "WDSPRx::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_filterIndex: " << settings.m_filterIndex
<< " [m_spanLog2: " << settings.m_filterBank[settings.m_filterIndex].m_spanLog2
<< " m_rfBandwidth: " << settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth
<< " m_lowCutoff: " << settings.m_filterBank[settings.m_filterIndex].m_lowCutoff
<< " m_fftWindow: " << settings.m_filterBank[settings.m_filterIndex].m_fftWindow << "]"
<< " m_volume: " << settings.m_volume
<< " 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_dnr: " << settings.m_dnr
<< " m_dnrScheme: " << settings.m_dnrScheme
<< " m_dnrAboveAvgFactor: " << settings.m_dnrAboveAvgFactor
<< " m_dnrSigmaFactor: " << settings.m_dnrSigmaFactor
<< " m_dnrNbPeaks: " << settings.m_dnrNbPeaks
<< " m_dnrAlpha: " << settings.m_dnrAlpha
<< " 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;
QList<QString> reverseAPIKeys;
if ((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((m_settings.m_filterIndex != settings.m_filterIndex) || force) {
reverseAPIKeys.append("filterIndex");
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2 != settings.m_filterBank[settings.m_filterIndex].m_spanLog2) || force) {
reverseAPIKeys.append("spanLog2");
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth != settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff != settings.m_filterBank[settings.m_filterIndex].m_lowCutoff) || force) {
reverseAPIKeys.append("lowCutoff");
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow != settings.m_filterBank[settings.m_filterIndex].m_fftWindow) || force) {
reverseAPIKeys.append("fftWindow");
}
if ((m_settings.m_volume != settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || force) {
reverseAPIKeys.append("agcTimeLog2");
}
if ((m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || force) {
reverseAPIKeys.append("agcPowerThreshold");
}
if ((m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || force) {
reverseAPIKeys.append("agcThresholdGate");
}
if ((m_settings.m_agcClamping != settings.m_agcClamping) || force) {
reverseAPIKeys.append("agcClamping");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((m_settings.m_audioBinaural != settings.m_audioBinaural) || force) {
reverseAPIKeys.append("audioBinaural");
}
if ((m_settings.m_audioFlipChannels != settings.m_audioFlipChannels) || force) {
reverseAPIKeys.append("audioFlipChannels");
}
if ((m_settings.m_dsb != settings.m_dsb) || force) {
reverseAPIKeys.append("dsb");
}
if ((m_settings.m_audioMute != settings.m_audioMute) || force) {
reverseAPIKeys.append("audioMute");
}
if ((m_settings.m_agc != settings.m_agc) || force) {
reverseAPIKeys.append("agc");
}
if ((m_settings.m_dnr != settings.m_dnr) || force) {
reverseAPIKeys.append("dnr");
}
if ((m_settings.m_dnrScheme != settings.m_dnrScheme) || force) {
reverseAPIKeys.append("dnrScheme");
}
if ((m_settings.m_dnrAboveAvgFactor != settings.m_dnrAboveAvgFactor) || force) {
reverseAPIKeys.append("dnrAboveAvgFactor");
}
if ((m_settings.m_dnrSigmaFactor != settings.m_dnrSigmaFactor) || force) {
reverseAPIKeys.append("dnrSigmaFactor");
}
if ((m_settings.m_dnrNbPeaks != settings.m_dnrNbPeaks) || force) {
reverseAPIKeys.append("dnrNbPeaks");
}
if ((m_settings.m_dnrAlpha != settings.m_dnrAlpha) || force) {
reverseAPIKeys.append("dnrAlpha");
}
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_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
m_settings.m_streamIndex = settings.m_streamIndex; // make sure ChannelAPI::getStreamIndex() is consistent
emit streamIndexChanged(settings.m_streamIndex);
}
reverseAPIKeys.append("streamIndex");
}
if ((settings.m_dsb != m_settings.m_dsb)
|| (settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth != m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth)
|| (settings.m_filterBank[settings.m_filterIndex].m_lowCutoff != m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff) || force)
{
SpectrumSettings spectrumSettings = m_spectrumVis.getSettings();
spectrumSettings.m_ssb = !settings.m_dsb;
spectrumSettings.m_usb = (settings.m_filterBank[settings.m_filterIndex].m_lowCutoff < settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth);
SpectrumVis::MsgConfigureSpectrumVis *msg = SpectrumVis::MsgConfigureSpectrumVis::create(spectrumSettings, false);
m_spectrumVis.getInputMessageQueue()->push(msg);
}
if (m_running)
{
WDSPRxBaseband::MsgConfigureWDSPRxBaseband *msg = WDSPRxBaseband::MsgConfigureWDSPRxBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "settings", pipes);
if (pipes.size() > 0) {
sendChannelSettings(pipes, reverseAPIKeys, settings, force);
}
m_settings = settings;
}
QByteArray WDSPRx::serialize() const
{
return m_settings.serialize();
}
bool WDSPRx::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureWDSPRx *msg = MsgConfigureWDSPRx::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureWDSPRx *msg = MsgConfigureWDSPRx::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void WDSPRx::sendSampleRateToDemodAnalyzer()
{
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe: pipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
if (messageQueue)
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
getAudioSampleRate()
);
messageQueue->push(msg);
}
}
}
}
int WDSPRx::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings());
response.getSsbDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int WDSPRx::webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage)
{
(void) errorMessage;
response.setIndex(m_settings.m_workspaceIndex);
return 200;
}
int WDSPRx::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
WDSPRxSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureWDSPRx *msg = MsgConfigureWDSPRx::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("WDSPRx::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureWDSPRx *msgToGUI = MsgConfigureWDSPRx::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void WDSPRx::webapiUpdateChannelSettings(
WDSPRxSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getSsbDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("filterIndex")) {
settings.m_filterIndex = response.getSsbDemodSettings()->getFilterIndex();
}
if (channelSettingsKeys.contains("spanLog2")) {
settings.m_filterBank[settings.m_filterIndex].m_spanLog2 = response.getSsbDemodSettings()->getSpanLog2();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth = response.getSsbDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("lowCutoff")) {
settings.m_filterBank[settings.m_filterIndex].m_lowCutoff = response.getSsbDemodSettings()->getLowCutoff();
}
if (channelSettingsKeys.contains("fftWimdow")) {
settings.m_filterBank[settings.m_filterIndex].m_fftWindow = (FFTWindow::Function) response.getSsbDemodSettings()->getFftWindow();
}
if (channelSettingsKeys.contains("volume")) {
settings.m_volume = response.getSsbDemodSettings()->getVolume();
}
if (channelSettingsKeys.contains("audioBinaural")) {
settings.m_audioBinaural = response.getSsbDemodSettings()->getAudioBinaural() != 0;
}
if (channelSettingsKeys.contains("audioFlipChannels")) {
settings.m_audioFlipChannels = response.getSsbDemodSettings()->getAudioFlipChannels() != 0;
}
if (channelSettingsKeys.contains("dsb")) {
settings.m_dsb = response.getSsbDemodSettings()->getDsb() != 0;
}
if (channelSettingsKeys.contains("audioMute")) {
settings.m_audioMute = response.getSsbDemodSettings()->getAudioMute() != 0;
}
if (channelSettingsKeys.contains("agc")) {
settings.m_agc = response.getSsbDemodSettings()->getAgc() != 0;
}
if (channelSettingsKeys.contains("agcClamping")) {
settings.m_agcClamping = response.getSsbDemodSettings()->getAgcClamping() != 0;
}
if (channelSettingsKeys.contains("agcTimeLog2")) {
settings.m_agcTimeLog2 = response.getSsbDemodSettings()->getAgcTimeLog2();
}
if (channelSettingsKeys.contains("agcPowerThreshold")) {
settings.m_agcPowerThreshold = response.getSsbDemodSettings()->getAgcPowerThreshold();
}
if (channelSettingsKeys.contains("agcThresholdGate")) {
settings.m_agcThresholdGate = response.getSsbDemodSettings()->getAgcThresholdGate();
}
if (channelSettingsKeys.contains("dnr")) {
settings.m_dnr = response.getSsbDemodSettings()->getDnr() != 0;
}
if (channelSettingsKeys.contains("dnrAboveAvgFactor")) {
settings.m_dnrAboveAvgFactor = response.getSsbDemodSettings()->getDnrAboveAvgFactor();
}
if (channelSettingsKeys.contains("dnrSigmaFactor")) {
settings.m_dnrSigmaFactor = response.getSsbDemodSettings()->getDnrSigmaFactor();
}
if (channelSettingsKeys.contains("dnrNbPeaks")) {
settings.m_dnrNbPeaks = response.getSsbDemodSettings()->getDnrNbPeaks();
}
if (channelSettingsKeys.contains("dnrAlpha")) {
settings.m_dnrAlpha = response.getSsbDemodSettings()->getDnrAlpha();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getSsbDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getSsbDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("audioDeviceName")) {
settings.m_audioDeviceName = *response.getSsbDemodSettings()->getAudioDeviceName();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getSsbDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getSsbDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getSsbDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getSsbDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getSsbDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getSsbDemodSettings()->getReverseApiChannelIndex();
}
if (settings.m_spectrumGUI && channelSettingsKeys.contains("spectrumConfig")) {
settings.m_spectrumGUI->updateFrom(channelSettingsKeys, response.getSsbDemodSettings()->getSpectrumConfig());
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getSsbDemodSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getSsbDemodSettings()->getRollupState());
}
}
int WDSPRx::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSsbDemodReport(new SWGSDRangel::SWGSSBDemodReport());
response.getSsbDemodReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void WDSPRx::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const WDSPRxSettings& settings)
{
response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getSsbDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getSsbDemodSettings()->setFilterIndex(settings.m_filterIndex);
response.getSsbDemodSettings()->setSpanLog2(settings.m_filterBank[settings.m_filterIndex].m_spanLog2);
response.getSsbDemodSettings()->setRfBandwidth(settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth);
response.getSsbDemodSettings()->setLowCutoff(settings.m_filterBank[settings.m_filterIndex].m_lowCutoff);
response.getSsbDemodSettings()->setFftWindow((int) settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
response.getSsbDemodSettings()->setVolume(settings.m_volume);
response.getSsbDemodSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0);
response.getSsbDemodSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0);
response.getSsbDemodSettings()->setDsb(settings.m_dsb ? 1 : 0);
response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getSsbDemodSettings()->setAgc(settings.m_agc ? 1 : 0);
response.getSsbDemodSettings()->setAgcClamping(settings.m_agcClamping ? 1 : 0);
response.getSsbDemodSettings()->setAgcTimeLog2(settings.m_agcTimeLog2);
response.getSsbDemodSettings()->setAgcPowerThreshold(settings.m_agcPowerThreshold);
response.getSsbDemodSettings()->setAgcThresholdGate(settings.m_agcThresholdGate);
response.getSsbDemodSettings()->setDnr(settings.m_dnr ? 1 : 0);
response.getSsbDemodSettings()->setDnrScheme(settings.m_dnrScheme);
response.getSsbDemodSettings()->setDnrAboveAvgFactor(settings.m_dnrAboveAvgFactor);
response.getSsbDemodSettings()->setDnrSigmaFactor(settings.m_dnrSigmaFactor);
response.getSsbDemodSettings()->setDnrNbPeaks(settings.m_dnrNbPeaks);
response.getSsbDemodSettings()->setDnrAlpha(settings.m_dnrAlpha);
response.getSsbDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getSsbDemodSettings()->getTitle()) {
*response.getSsbDemodSettings()->getTitle() = settings.m_title;
} else {
response.getSsbDemodSettings()->setTitle(new QString(settings.m_title));
}
if (response.getSsbDemodSettings()->getAudioDeviceName()) {
*response.getSsbDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
} else {
response.getSsbDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
response.getSsbDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getSsbDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getSsbDemodSettings()->getReverseApiAddress()) {
*response.getSsbDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getSsbDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getSsbDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getSsbDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getSsbDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_spectrumGUI)
{
if (response.getSsbDemodSettings()->getSpectrumConfig())
{
settings.m_spectrumGUI->formatTo(response.getSsbDemodSettings()->getSpectrumConfig());
}
else
{
SWGSDRangel::SWGGLSpectrum *swgGLSpectrum = new SWGSDRangel::SWGGLSpectrum();
settings.m_spectrumGUI->formatTo(swgGLSpectrum);
response.getSsbDemodSettings()->setSpectrumConfig(swgGLSpectrum);
}
}
if (settings.m_channelMarker)
{
if (response.getSsbDemodSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getSsbDemodSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getSsbDemodSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getSsbDemodSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getSsbDemodSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getSsbDemodSettings()->setRollupState(swgRollupState);
}
}
}
void WDSPRx::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
if (m_running)
{
response.getSsbDemodReport()->setSquelch(m_basebandSink->getAudioActive() ? 1 : 0);
response.getSsbDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate());
response.getSsbDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
}
void WDSPRx::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WDSPRxSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void WDSPRx::sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const WDSPRxSettings& settings,
bool force)
{
qDebug("WDSPRx::sendChannelSettings: %d pipes", pipes.size());
for (const auto& pipe : pipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
if (messageQueue)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
MainCore::MsgChannelSettings *msg = MainCore::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
messageQueue->push(msg);
}
}
}
void WDSPRx::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const WDSPRxSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString(m_channelId));
swgChannelSettings->setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings());
SWGSDRangel::SWGSSBDemodSettings *swgSSBDemodSettings = swgChannelSettings->getSsbDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgSSBDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("filteIndex") || force) {
swgSSBDemodSettings->setFilterIndex(settings.m_filterIndex);
}
if (channelSettingsKeys.contains("spanLog2") || force) {
swgSSBDemodSettings->setSpanLog2(settings.m_filterBank[settings.m_filterIndex].m_spanLog2);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgSSBDemodSettings->setRfBandwidth(settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth);
}
if (channelSettingsKeys.contains("lowCutoff") || force) {
swgSSBDemodSettings->setLowCutoff(settings.m_filterBank[settings.m_filterIndex].m_lowCutoff);
}
if (channelSettingsKeys.contains("fftWindow") || force) {
swgSSBDemodSettings->setLowCutoff(settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
}
if (channelSettingsKeys.contains("volume") || force) {
swgSSBDemodSettings->setVolume(settings.m_volume);
}
if (channelSettingsKeys.contains("audioBinaural") || force) {
swgSSBDemodSettings->setAudioBinaural(settings.m_audioBinaural ? 1 : 0);
}
if (channelSettingsKeys.contains("audioFlipChannels") || force) {
swgSSBDemodSettings->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0);
}
if (channelSettingsKeys.contains("dsb") || force) {
swgSSBDemodSettings->setDsb(settings.m_dsb ? 1 : 0);
}
if (channelSettingsKeys.contains("audioMute") || force) {
swgSSBDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0);
}
if (channelSettingsKeys.contains("agc") || force) {
swgSSBDemodSettings->setAgc(settings.m_agc ? 1 : 0);
}
if (channelSettingsKeys.contains("agcClamping") || force) {
swgSSBDemodSettings->setAgcClamping(settings.m_agcClamping ? 1 : 0);
}
if (channelSettingsKeys.contains("agcTimeLog2") || force) {
swgSSBDemodSettings->setAgcTimeLog2(settings.m_agcTimeLog2);
}
if (channelSettingsKeys.contains("agcPowerThreshold") || force) {
swgSSBDemodSettings->setAgcPowerThreshold(settings.m_agcPowerThreshold);
}
if (channelSettingsKeys.contains("agcThresholdGate") || force) {
swgSSBDemodSettings->setAgcThresholdGate(settings.m_agcThresholdGate);
}
if (channelSettingsKeys.contains("dnr")) {
swgSSBDemodSettings->setDnr(settings.m_dnr ? 1 : 0);
}
if (channelSettingsKeys.contains("dnrAboveAvgFactor")) {
swgSSBDemodSettings->setDnrAboveAvgFactor(settings.m_dnrAboveAvgFactor);
}
if (channelSettingsKeys.contains("dnrSigmaFactor")) {
swgSSBDemodSettings->setDnrSigmaFactor(settings.m_dnrSigmaFactor);
}
if (channelSettingsKeys.contains("dnrNbPeaks")) {
swgSSBDemodSettings->setDnrNbPeaks(settings.m_dnrNbPeaks);
}
if (channelSettingsKeys.contains("dnrAlpha")) {
swgSSBDemodSettings->setDnrAlpha(settings.m_dnrAlpha);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgSSBDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgSSBDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgSSBDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgSSBDemodSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_spectrumGUI && (channelSettingsKeys.contains("spectrunConfig") || force))
{
SWGSDRangel::SWGGLSpectrum *swgGLSpectrum = new SWGSDRangel::SWGGLSpectrum();
settings.m_spectrumGUI->formatTo(swgGLSpectrum);
swgSSBDemodSettings->setSpectrumConfig(swgGLSpectrum);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgSSBDemodSettings->setChannelMarker(swgChannelMarker);
}
if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force))
{
SWGSDRangel::SWGRollupState *swgRolllupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRolllupState);
swgSSBDemodSettings->setRollupState(swgRolllupState);
}
}
void WDSPRx::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "WDSPRx::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("WDSPRx::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
void WDSPRx::handleIndexInDeviceSetChanged(int index)
{
if (!m_running || (index < 0)) {
return;
}
QString fifoLabel = QString("%1 [%2:%3]")
.arg(m_channelId)
.arg(m_deviceAPI->getDeviceSetIndex())
.arg(index);
m_basebandSink->setFifoLabel(fifoLabel);
m_basebandSink->setAudioFifoLabel(fifoLabel);
}

View File

@ -0,0 +1,187 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron@spam.no> //
// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// 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_WDSPRX_H
#define INCLUDE_WDSPRX_H
#include <vector>
#include <QRecursiveMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesink.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "wdsprxsettings.h"
#include "wdsprxbaseband.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class ObjectPipe;
class WDSPRx : public BasebandSampleSink, public ChannelAPI {
public:
class MsgConfigureWDSPRx : public Message {
MESSAGE_CLASS_DECLARATION
public:
const WDSPRxSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureWDSPRx* create(const WDSPRxSettings& settings, bool force)
{
return new MsgConfigureWDSPRx(settings, force);
}
private:
WDSPRxSettings m_settings;
bool m_force;
MsgConfigureWDSPRx(const WDSPRxSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
WDSPRx(DeviceAPI *deviceAPI);
virtual ~WDSPRx();
virtual void destroy() { delete this; }
virtual void setDeviceAPI(DeviceAPI *deviceAPI);
virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; }
SpectrumVis *getSpectrumVis() { return &m_spectrumVis; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSinkName() { return objectName(); }
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_settings.m_inputFrequencyOffset;
}
void setMessageQueueToGUI(MessageQueue* queue) override;
uint32_t getAudioSampleRate() const { return m_running ? m_basebandSink->getAudioSampleRate() : 0; }
uint32_t getChannelSampleRate() const { return m_running ? m_basebandSink->getChannelSampleRate() : 0; }
double getMagSq() const { return m_running ? m_basebandSink->getMagSq() : 0.0; }
bool getAudioActive() const { return m_running && m_basebandSink->getAudioActive(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_running) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
} else {
avg = 0.0; peak = 0.0; nbSamples = 1;
}
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const WDSPRxSettings& settings);
static void webapiUpdateChannelSettings(
WDSPRxSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
uint32_t getNumberOfDeviceStreams() const;
static const char* const m_channelIdURI;
static const char* const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread *m_thread;
WDSPRxBaseband* m_basebandSink;
QRecursiveMutex m_mutex;
bool m_running;
WDSPRxSettings m_settings;
SpectrumVis m_spectrumVis;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
virtual bool handleMessage(const Message& cmd);
void applySettings(const WDSPRxSettings& settings, bool force = false);
void sendSampleRateToDemodAnalyzer();
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WDSPRxSettings& settings, bool force);
void sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const WDSPRxSettings& settings,
bool force
);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const WDSPRxSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleIndexInDeviceSetChanged(int index);
};
#endif // INCLUDE_WDSPRX_H

View File

@ -0,0 +1,245 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// 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/spectrumvis.h"
#include "wdsprxbaseband.h"
MESSAGE_CLASS_DEFINITION(WDSPRxBaseband::MsgConfigureWDSPRxBaseband, Message)
WDSPRxBaseband::WDSPRxBaseband() :
m_channelizer(&m_sink),
m_messageQueueToGUI(nullptr),
m_spectrumVis(nullptr)
{
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
qDebug("WDSPRxBaseband::WDSPRxBaseband");
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&WDSPRxBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue());
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
m_sink.applyAudioSampleRate(m_audioSampleRate);
m_channelSampleRate = 0;
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
WDSPRxBaseband::~WDSPRxBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo());
}
void WDSPRxBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
m_sampleFifo.reset();
m_channelSampleRate = 0;
}
void WDSPRxBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void WDSPRxBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void WDSPRxBaseband::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 WDSPRxBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool WDSPRxBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureWDSPRxBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureWDSPRxBaseband& cfg = (MsgConfigureWDSPRxBaseband&) cmd;
qDebug() << "WDSPRxBaseband::handleMessage: MsgConfigureWDSPRxBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "WDSPRxBaseband::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 (m_channelSampleRate != m_channelizer.getChannelSampleRate())
{
m_sink.applyAudioSampleRate(m_audioSampleRate); // reapply when channel sample rate changes
m_channelSampleRate = m_channelizer.getChannelSampleRate();
}
return true;
}
else if (DSPConfigureAudio::match(cmd))
{
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
unsigned int audioSampleRate = cfg.getSampleRate();
if (m_audioSampleRate != audioSampleRate)
{
qDebug("WDSPRxBaseband::handleMessage: DSPConfigureAudio: new sample rate %d",audioSampleRate);
m_sink.applyAudioSampleRate(audioSampleRate);
m_channelizer.setChannelization(audioSampleRate, m_settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer.getChannelSampleRate(), m_channelizer.getChannelFrequencyOffset());
m_audioSampleRate = audioSampleRate;
if (getMessageQueueToGUI())
{
qDebug("WDSPRxBaseband::handleMessage: DSPConfigureAudio: forward to GUI");
DSPConfigureAudio *msg = new DSPConfigureAudio((int) audioSampleRate, DSPConfigureAudio::AudioOutput);
getMessageQueueToGUI()->push(msg);
}
if (m_spectrumVis)
{
DSPSignalNotification *msg = new DSPSignalNotification(m_audioSampleRate/(1<<m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2), 0);
m_spectrumVis->getInputMessageQueue()->push(msg);
}
}
return true;
}
else
{
return false;
}
}
void WDSPRxBaseband::applySettings(const WDSPRxSettings& 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 (m_channelSampleRate != m_channelizer.getChannelSampleRate())
{
m_sink.applyAudioSampleRate(m_audioSampleRate); // reapply when channel sample rate changes
m_channelSampleRate = m_channelizer.getChannelSampleRate();
}
}
if ((settings.m_filterBank[settings.m_filterIndex].m_spanLog2 != m_settings.m_filterBank[settings.m_filterIndex].m_spanLog2) || force)
{
if (m_spectrumVis)
{
DSPSignalNotification *msg = new DSPSignalNotification(m_audioSampleRate/(1<<settings.m_filterBank[settings.m_filterIndex].m_spanLog2), 0);
m_spectrumVis->getInputMessageQueue()->push(msg);
}
}
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);
}
if (m_spectrumVis)
{
DSPSignalNotification *msg = new DSPSignalNotification(m_audioSampleRate/(1<<m_settings.m_filterBank[settings.m_filterIndex].m_spanLog2), 0);
m_spectrumVis->getInputMessageQueue()->push(msg);
}
}
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int WDSPRxBaseband::getChannelSampleRate() const
{
return m_channelizer.getChannelSampleRate();
}
void WDSPRxBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer.setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer.getChannelSampleRate(), m_channelizer.getChannelFrequencyOffset());
}

View File

@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// //
// 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_WDSPRXBASEBAND_H
#define INCLUDE_WDSPRXBASEBAND_H
#include <QObject>
#include <QRecursiveMutex>
#include "dsp/samplesinkfifo.h"
#include "dsp/downchannelizer.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "wdsprxsink.h"
class ChannelAPI;
class SpectrumVis;
class WDSPRxBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureWDSPRxBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const WDSPRxSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureWDSPRxBaseband* create(const WDSPRxSettings& settings, bool force)
{
return new MsgConfigureWDSPRxBaseband(settings, force);
}
private:
WDSPRxSettings m_settings;
bool m_force;
MsgConfigureWDSPRxBaseband(const WDSPRxSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
WDSPRxBaseband();
~WDSPRxBaseband();
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(SpectrumVis* spectrumSink) { m_spectrumVis = 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; }
void setChannel(ChannelAPI *channel);
void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
void setAudioFifoLabel(const QString& label) { m_sink.setAudioFifoLabel(label); }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer m_channelizer;
WDSPRxSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
WDSPRxSettings m_settings;
unsigned int m_audioSampleRate;
int m_channelSampleRate;
MessageQueue *m_messageQueueToGUI;
SpectrumVis *m_spectrumVis;
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const WDSPRxSettings& 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_WDSPRXBASEBAND_H

View File

@ -0,0 +1,897 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2021-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// 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 <QPixmap>
#include "wdsprxgui.h"
#include "device/deviceuiset.h"
#include "ui_wdsprxgui.h"
#include "dsp/spectrumvis.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "gui/glspectrum.h"
#include "gui/basicchannelsettingsdialog.h"
#include "plugin/pluginapi.h"
#include "util/db.h"
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "gui/dialpopup.h"
#include "gui/dialogpositioner.h"
#include "maincore.h"
#include "wdsprx.h"
WDSPRxGUI* WDSPRxGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
WDSPRxGUI* gui = new WDSPRxGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void WDSPRxGUI::destroy()
{
delete this;
}
void WDSPRxGUI::resetToDefaults()
{
m_settings.resetToDefaults();
}
QByteArray WDSPRxGUI::serialize() const
{
return m_settings.serialize();
}
bool WDSPRxGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data))
{
ui->BW->setMaximum(480);
ui->BW->setMinimum(-480);
ui->lowCut->setMaximum(480);
ui->lowCut->setMinimum(-480);
displaySettings();
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
return true;
}
else
{
m_settings.resetToDefaults();
ui->BW->setMaximum(480);
ui->BW->setMinimum(-480);
ui->lowCut->setMaximum(480);
ui->lowCut->setMinimum(-480);
displaySettings();
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
return false;
}
}
bool WDSPRxGUI::handleMessage(const Message& message)
{
if (WDSPRx::MsgConfigureWDSPRx::match(message))
{
qDebug("WDSPRxGUI::handleMessage: WDSPRx::MsgConfigureWDSPRx");
const WDSPRx::MsgConfigureWDSPRx& cfg = (WDSPRx::MsgConfigureWDSPRx&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
ui->spectrumGUI->updateSettings();
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPConfigureAudio::match(message))
{
qDebug("WDSPRxGUI::handleMessage: DSPConfigureAudio: %d", m_wdspRx->getAudioSampleRate());
applyBandwidths(1 + ui->spanLog2->maximum() - ui->spanLog2->value()); // will update spectrum details with new sample rate
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
qDebug("WDSPRxGUI::handleMessage: DSPSignalNotification: centerFrequency: %lld sampleRate: %d",
m_deviceCenterFrequency, m_basebandSampleRate);
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
updateAbsoluteCenterFrequency();
return true;
}
else
{
return false;
}
}
void WDSPRxGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void WDSPRxGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void WDSPRxGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void WDSPRxGUI::on_audioBinaural_toggled(bool binaural)
{
m_audioBinaural = binaural;
m_settings.m_audioBinaural = binaural;
applySettings();
}
void WDSPRxGUI::on_audioFlipChannels_toggled(bool flip)
{
m_audioFlipChannels = flip;
m_settings.m_audioFlipChannels = flip;
applySettings();
}
void WDSPRxGUI::on_dsb_toggled(bool dsb)
{
ui->flipSidebands->setEnabled(!dsb);
applyBandwidths(1 + ui->spanLog2->maximum() - ui->spanLog2->value());
}
void WDSPRxGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySettings();
}
void WDSPRxGUI::on_BW_valueChanged(int value)
{
(void) value;
qDebug("WDSPRxGUI::on_BW_valueChanged: ui->spanLog2: %d", ui->spanLog2->value());
applyBandwidths(1 + ui->spanLog2->maximum() - ui->spanLog2->value());
}
void WDSPRxGUI::on_lowCut_valueChanged(int value)
{
(void) value;
applyBandwidths(1 + ui->spanLog2->maximum() - ui->spanLog2->value());
}
void WDSPRxGUI::on_volume_valueChanged(int value)
{
ui->volumeText->setText(QString("%1").arg(value));
m_settings.m_volume = CalcDb::powerFromdB(value);
applySettings();
}
void WDSPRxGUI::on_agc_toggled(bool checked)
{
m_settings.m_agc = checked;
applySettings();
displayAGC();
}
void WDSPRxGUI::on_agcClamping_toggled(bool checked)
{
m_settings.m_agcClamping = checked;
applySettings();
}
void WDSPRxGUI::on_dnr_toggled(bool checked)
{
m_settings.m_dnr = checked;
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnr = m_settings.m_dnr;
applySettings();
}
void WDSPRxGUI::on_agcTimeLog2_valueChanged(int value)
{
QString s = QString::number((1<<value), 'f', 0);
ui->agcTimeText->setText(s);
m_settings.m_agcTimeLog2 = value;
applySettings();
}
void WDSPRxGUI::on_agcPowerThreshold_valueChanged(int value)
{
displayAGCPowerThreshold(value);
m_settings.m_agcPowerThreshold = value;
applySettings();
}
void WDSPRxGUI::on_agcThresholdGate_valueChanged(int value)
{
int agcThresholdGate = value < 20 ? value : ((value - 20) * 10) + 20;
QString s = QString::number(agcThresholdGate, 'f', 0);
ui->agcThresholdGateText->setText(s);
m_settings.m_agcThresholdGate = agcThresholdGate;
applySettings();
}
void WDSPRxGUI::on_audioMute_toggled(bool checked)
{
m_audioMute = checked;
m_settings.m_audioMute = checked;
applySettings();
}
void WDSPRxGUI::on_spanLog2_valueChanged(int value)
{
int s2max = spanLog2Max();
if ((value < 0) || (value > s2max-1)) {
return;
}
applyBandwidths(s2max - ui->spanLog2->value());
}
void WDSPRxGUI::on_flipSidebands_clicked(bool checked)
{
(void) checked;
int bwValue = ui->BW->value();
int lcValue = ui->lowCut->value();
ui->BW->setValue(-bwValue);
ui->lowCut->setValue(-lcValue);
}
void WDSPRxGUI::on_fftWindow_currentIndexChanged(int index)
{
m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow = (FFTWindow::Function) index;
applySettings();
}
void WDSPRxGUI::on_filterIndex_valueChanged(int value)
{
if ((value < 0) || (value >= 10)) {
return;
}
ui->filterIndexText->setText(tr("%1").arg(value));
m_settings.m_filterIndex = value;
ui->BW->setMaximum(480);
ui->BW->setMinimum(-480);
ui->lowCut->setMaximum(480);
ui->lowCut->setMinimum(-480);
m_settings.m_dnr = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnr;
m_settings.m_dnrScheme = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrScheme;
m_settings.m_dnrAboveAvgFactor = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrAboveAvgFactor;
m_settings.m_dnrSigmaFactor = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrSigmaFactor;
m_settings.m_dnrNbPeaks = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrNbPeaks;
m_settings.m_dnrAlpha = m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrAlpha;
displaySettings();
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
}
void WDSPRxGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.setDefaultTitle(m_displayedName);
if (m_deviceUISet->m_deviceMIMOEngine)
{
dialog.setNumberOfStreams(m_wdspRx->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
}
dialog.move(p);
new DialogPositioner(&dialog, false);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitle(m_channelMarker.getTitle());
setTitleColor(m_settings.m_rgbColor);
if (m_deviceUISet->m_deviceMIMOEngine)
{
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
updateIndexLabel();
}
applySettings();
}
resetContextMenuType();
}
void WDSPRxGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySettings();
}
WDSPRxGUI::WDSPRxGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::WDSPRxGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_spectrumRate(6000),
m_audioBinaural(false),
m_audioFlipChannels(false),
m_audioMute(false),
m_squelchOpen(false),
m_audioSampleRate(-1),
m_fftNRDialog(nullptr)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodssb/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_wdspRx = (WDSPRx*) rxChannel;
m_spectrumVis = m_wdspRx->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum);
m_wdspRx->setMessageQueueToGUI(getInputMessageQueue());
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect(const QPoint &)));
CRightClickEnabler *dnrRightClickEnabler = new CRightClickEnabler(ui->dnr);
connect(dnrRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(dnrSetupDialog(const QPoint &)));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum);
ui->glSpectrum->setCenterFrequency(m_spectrumRate/2);
ui->glSpectrum->setSampleRate(m_spectrumRate);
SpectrumSettings spectrumSettings = m_spectrumVis->getSettings();
spectrumSettings.m_displayWaterfall = true;
spectrumSettings.m_ssb = true;
SpectrumVis::MsgConfigureSpectrumVis *msg = SpectrumVis::MsgConfigureSpectrumVis::create(spectrumSettings, false);
m_spectrumVis->getInputMessageQueue()->push(msg);
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::green);
m_channelMarker.setBandwidth(6000);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("SSB Demodulator");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setSpectrumGUI(ui->spectrumGUI);
m_settings.setRollupState(&m_rollupState);
m_deviceUISet->addChannelMarker(&m_channelMarker);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On);
m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::Off);
m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On);
m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off);
ui->BW->setMaximum(480);
ui->BW->setMinimum(-480);
ui->lowCut->setMaximum(480);
ui->lowCut->setMinimum(-480);
displaySettings();
makeUIConnections();
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
DialPopup::addPopupsToChildDials(this);
m_resizer.enableChildMouseTracking();
}
WDSPRxGUI::~WDSPRxGUI()
{
delete ui;
}
bool WDSPRxGUI::blockApplySettings(bool block)
{
bool ret = !m_doApplySettings;
m_doApplySettings = !block;
return ret;
}
void WDSPRxGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
WDSPRx::MsgConfigureWDSPRx* message = WDSPRx::MsgConfigureWDSPRx::create( m_settings, force);
m_wdspRx->getInputMessageQueue()->push(message);
}
}
uint32_t WDSPRxGUI::getValidAudioSampleRate() const
{
// When not running, m_wdspRx->getAudioSampleRate() will return 0, but we
// want a valid value to initialise the GUI, to allow a user to preselect settings
int sr = m_wdspRx->getAudioSampleRate();
if (sr == 0)
{
if (m_audioSampleRate > 0) {
sr = m_audioSampleRate;
} else {
sr = 48000;
}
}
return sr;
}
unsigned int WDSPRxGUI::spanLog2Max()
{
unsigned int spanLog2 = 0;
for (; getValidAudioSampleRate() / (1<<spanLog2) >= 1000; spanLog2++);
return spanLog2 == 0 ? 0 : spanLog2-1;
}
void WDSPRxGUI::applyBandwidths(unsigned int spanLog2, bool force)
{
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 = getValidAudioSampleRate() / (1<<spanLog2);
int bw = ui->BW->value();
int lw = ui->lowCut->value();
int bwMax = getValidAudioSampleRate() / (100*(1<<spanLog2));
int tickInterval = m_spectrumRate / 1200;
tickInterval = tickInterval == 0 ? 1 : tickInterval;
qDebug() << "WDSPRxGUI::applyBandwidths:"
<< " s2max:" << s2max
<< " dsb: " << dsb
<< " spanLog2: " << spanLog2
<< " m_spectrumRate: " << m_spectrumRate
<< " bw: " << bw
<< " lw: " << lw
<< " bwMax: " << bwMax
<< " tickInterval: " << tickInterval;
ui->BW->setTickInterval(tickInterval);
ui->lowCut->setTickInterval(tickInterval);
bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw;
if (bw < 0) {
lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0;
} else if (bw > 0) {
lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw;
} else {
lw = 0;
}
if (dsb)
{
bw = bw < 0 ? -bw : bw;
lw = 0;
}
QString spanStr = QString::number(bwMax/10.0, 'f', 1);
QString bwStr = QString::number(bw/10.0, 'f', 1);
QString lwStr = QString::number(lw/10.0, 'f', 1);
SpectrumSettings spectrumSettings = m_spectrumVis->getSettings();
if (dsb)
{
ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr));
ui->spanText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(spanStr));
ui->scaleMinus->setText("0");
ui->scaleCenter->setText("");
ui->scalePlus->setText(tr("%1").arg(QChar(0xB1, 0x00)));
ui->lsbLabel->setText("");
ui->usbLabel->setText("");
ui->glSpectrum->setCenterFrequency(0);
ui->glSpectrum->setSampleRate(2*m_spectrumRate);
spectrumSettings.m_ssb = false;
ui->glSpectrum->setLsbDisplay(false);
ui->glSpectrum->setSsbSpectrum(false);
}
else
{
ui->BWText->setText(tr("%1k").arg(bwStr));
ui->spanText->setText(tr("%1k").arg(spanStr));
ui->scaleMinus->setText("-");
ui->scaleCenter->setText("0");
ui->scalePlus->setText("+");
ui->lsbLabel->setText("LSB");
ui->usbLabel->setText("USB");
ui->glSpectrum->setCenterFrequency(0);
ui->glSpectrum->setSampleRate(2*m_spectrumRate);
spectrumSettings.m_ssb = true;
ui->glSpectrum->setLsbDisplay(bw < 0);
ui->glSpectrum->setSsbSpectrum(true);
}
SpectrumVis::MsgConfigureSpectrumVis *msg = SpectrumVis::MsgConfigureSpectrumVis::create(spectrumSettings, false);
m_spectrumVis->getInputMessageQueue()->push(msg);
ui->lowCutText->setText(tr("%1k").arg(lwStr));
ui->BW->blockSignals(true);
ui->lowCut->blockSignals(true);
ui->BW->setMaximum(bwMax);
ui->BW->setMinimum(dsb ? 0 : -bwMax);
ui->BW->setValue(bw);
ui->lowCut->setMaximum(dsb ? 0 : bwMax);
ui->lowCut->setMinimum(dsb ? 0 : -bwMax);
ui->lowCut->setValue(lw);
ui->lowCut->blockSignals(false);
ui->BW->blockSignals(false);
ui->channelPowerMeter->setRange(WDSPRxSettings::m_minPowerThresholdDB, 0);
m_settings.m_dsb = dsb;
m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2 = spanLog2;
m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth = bw * 100;
m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff = lw * 100;
applySettings(force);
bool wasBlocked = blockApplySettings(true);
m_channelMarker.setBandwidth(bw * 200);
m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb);
ui->dsb->setIcon(bw < 0 ? m_iconDSBLSB: m_iconDSBUSB);
if (!dsb) { m_channelMarker.setLowCutoff(lw * 100); }
blockApplySettings(wasBlocked);
}
void WDSPRxGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setBandwidth(m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth * 2);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.setLowCutoff(m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff);
if (m_deviceUISet->m_deviceMIMOEngine)
{
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
}
ui->flipSidebands->setEnabled(!m_settings.m_dsb);
if (m_settings.m_dsb)
{
m_channelMarker.setSidebands(ChannelMarker::dsb);
}
else
{
if (m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth < 0)
{
m_channelMarker.setSidebands(ChannelMarker::lsb);
ui->dsb->setIcon(m_iconDSBLSB);
}
else
{
m_channelMarker.setSidebands(ChannelMarker::usb);
ui->dsb->setIcon(m_iconDSBUSB);
}
}
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
setTitle(m_channelMarker.getTitle());
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->agc->setChecked(m_settings.m_agc);
displayAGC();
ui->agcClamping->setChecked(m_settings.m_agcClamping);
ui->dnr->setChecked(m_settings.m_dnr);
ui->audioBinaural->setChecked(m_settings.m_audioBinaural);
ui->audioFlipChannels->setChecked(m_settings.m_audioFlipChannels);
ui->audioMute->setChecked(m_settings.m_audioMute);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->fftWindow->setCurrentIndex((int) m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow);
// Prevent uncontrolled triggering of applyBandwidths
ui->spanLog2->blockSignals(true);
ui->dsb->blockSignals(true);
ui->BW->blockSignals(true);
ui->filterIndex->blockSignals(true);
ui->filterIndex->setValue(m_settings.m_filterIndex);
ui->filterIndexText->setText(tr("%1").arg(m_settings.m_filterIndex));
ui->dsb->setChecked(m_settings.m_dsb);
ui->spanLog2->setValue(1 + ui->spanLog2->maximum() - m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2);
ui->BW->setValue(m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth / 100.0);
QString s = QString::number(m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth/1000.0, 'f', 1);
if (m_settings.m_dsb) {
ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(s));
} else {
ui->BWText->setText(tr("%1k").arg(s));
}
ui->spanLog2->blockSignals(false);
ui->dsb->blockSignals(false);
ui->BW->blockSignals(false);
ui->filterIndex->blockSignals(false);
// The only one of the four signals triggering applyBandwidths will trigger it once only with all other values
// set correctly and therefore validate the settings and apply them to dependent widgets
ui->lowCut->setValue(m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff / 100.0);
ui->lowCutText->setText(tr("%1k").arg(m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff / 1000.0));
int volume = CalcDb::dbPower(m_settings.m_volume);
ui->volume->setValue(volume);
ui->volumeText->setText(QString("%1").arg(volume));
ui->agcTimeLog2->setValue(m_settings.m_agcTimeLog2);
s = QString::number((1<<ui->agcTimeLog2->value()), 'f', 0);
ui->agcTimeText->setText(s);
ui->agcPowerThreshold->setValue(m_settings.m_agcPowerThreshold);
displayAGCPowerThreshold(ui->agcPowerThreshold->value());
displayAGCThresholdGate(m_settings.m_agcThresholdGate);
updateIndexLabel();
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
}
void WDSPRxGUI::displayAGC()
{
// Disable controls only valid when AGC is enabled
ui->agcClamping->setEnabled(m_settings.m_agc);
ui->agcTimeLog2->setEnabled(m_settings.m_agc);
ui->agcTimeText->setEnabled(m_settings.m_agc);
ui->agcPowerThreshold->setEnabled(m_settings.m_agc);
ui->agcPowerThresholdText->setEnabled(m_settings.m_agc);
ui->agcThresholdGate->setEnabled(m_settings.m_agc);
ui->agcThresholdGateText->setEnabled(m_settings.m_agc);
}
void WDSPRxGUI::displayAGCPowerThreshold(int value)
{
if (value == WDSPRxSettings::m_minPowerThresholdDB)
{
ui->agcPowerThresholdText->setText("---");
}
else
{
QString s = QString::number(value, 'f', 0);
ui->agcPowerThresholdText->setText(s);
}
}
void WDSPRxGUI::displayAGCThresholdGate(int value)
{
QString s = QString::number(value, 'f', 0);
ui->agcThresholdGateText->setText(s);
int dialValue = value;
if (value > 20) {
dialValue = ((value - 20) / 10) + 20;
}
ui->agcThresholdGate->setValue(dialValue);
}
void WDSPRxGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void WDSPRxGUI::enterEvent(EnterEventType* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void WDSPRxGUI::audioSelect(const QPoint& p)
{
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName);
audioSelect.move(p);
new DialogPositioner(&audioSelect, false);
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void WDSPRxGUI::dnrSetupDialog(const QPoint& p)
{
m_fftNRDialog = new FFTNRDialog();
m_fftNRDialog->move(p);
QObject::connect(m_fftNRDialog, &FFTNRDialog::valueChanged, this, &WDSPRxGUI::dnrSetup);
m_fftNRDialog->setScheme((FFTNoiseReduction::Scheme) m_settings.m_dnrScheme);
m_fftNRDialog->setAboveAvgFactor(m_settings.m_dnrAboveAvgFactor);
m_fftNRDialog->setSigmaFactor(m_settings.m_dnrSigmaFactor);
m_fftNRDialog->setNbPeaks(m_settings.m_dnrNbPeaks);
m_fftNRDialog->setAlpha(m_settings.m_dnrAlpha, 2048, m_audioSampleRate);
m_fftNRDialog->exec();
QObject::disconnect(m_fftNRDialog, &FFTNRDialog::valueChanged, this, &WDSPRxGUI::dnrSetup);
m_fftNRDialog->deleteLater();
m_fftNRDialog = nullptr;
}
void WDSPRxGUI::dnrSetup(int32_t iValueChanged)
{
if (!m_fftNRDialog) {
return;
}
FFTNRDialog::ValueChanged valueChanged = (FFTNRDialog::ValueChanged) iValueChanged;
switch (valueChanged)
{
case FFTNRDialog::ValueChanged::ChangedScheme:
m_settings.m_dnrScheme = m_fftNRDialog->getScheme();
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrScheme = m_settings.m_dnrScheme;
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedAboveAvgFactor:
m_settings.m_dnrAboveAvgFactor = m_fftNRDialog->getAboveAvgFactor();
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrAboveAvgFactor = m_settings.m_dnrAboveAvgFactor;
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedSigmaFactor:
m_settings.m_dnrSigmaFactor = m_fftNRDialog->getSigmaFactor();
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrSigmaFactor = m_settings.m_dnrSigmaFactor;
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedNbPeaks:
m_settings.m_dnrNbPeaks = m_fftNRDialog->getNbPeaks();
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrNbPeaks = m_settings.m_dnrNbPeaks;
applySettings();
break;
case FFTNRDialog::ValueChanged::ChangedAlpha:
m_settings.m_dnrAlpha = m_fftNRDialog->getAlpha();
m_settings.m_filterBank[m_settings.m_filterIndex].m_dnrAlpha = m_settings.m_dnrAlpha;
applySettings();
break;
default:
break;
}
}
void WDSPRxGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_wdspRx->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(WDSPRxSettings::m_mminPowerThresholdDBf + powDbAvg) / WDSPRxSettings::m_mminPowerThresholdDBf,
(WDSPRxSettings::m_mminPowerThresholdDBf + powDbPeak) / WDSPRxSettings::m_mminPowerThresholdDBf,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(tr("%1 dB").arg(powDbAvg, 0, 'f', 1));
}
int audioSampleRate = m_wdspRx->getAudioSampleRate();
bool squelchOpen = m_wdspRx->getAudioActive();
if ((audioSampleRate != m_audioSampleRate) || (squelchOpen != m_squelchOpen))
{
if (audioSampleRate < 0) {
ui->audioMute->setStyleSheet("QToolButton { background-color : red; }");
} else if (squelchOpen) {
ui->audioMute->setStyleSheet("QToolButton { background-color : green; }");
} else {
ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
m_audioSampleRate = audioSampleRate;
m_squelchOpen = squelchOpen;
}
m_tickCount++;
}
void WDSPRxGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &WDSPRxGUI::on_deltaFrequency_changed);
QObject::connect(ui->audioBinaural, &QToolButton::toggled, this, &WDSPRxGUI::on_audioBinaural_toggled);
QObject::connect(ui->audioFlipChannels, &QToolButton::toggled, this, &WDSPRxGUI::on_audioFlipChannels_toggled);
QObject::connect(ui->dsb, &QToolButton::toggled, this, &WDSPRxGUI::on_dsb_toggled);
QObject::connect(ui->BW, &TickedSlider::valueChanged, this, &WDSPRxGUI::on_BW_valueChanged);
QObject::connect(ui->lowCut, &TickedSlider::valueChanged, this, &WDSPRxGUI::on_lowCut_valueChanged);
QObject::connect(ui->volume, &QDial::valueChanged, this, &WDSPRxGUI::on_volume_valueChanged);
QObject::connect(ui->agc, &ButtonSwitch::toggled, this, &WDSPRxGUI::on_agc_toggled);
QObject::connect(ui->agcClamping, &ButtonSwitch::toggled, this, &WDSPRxGUI::on_agcClamping_toggled);
QObject::connect(ui->dnr, &ButtonSwitch::toggled, this, &WDSPRxGUI::on_dnr_toggled);
QObject::connect(ui->agcTimeLog2, &QDial::valueChanged, this, &WDSPRxGUI::on_agcTimeLog2_valueChanged);
QObject::connect(ui->agcPowerThreshold, &QDial::valueChanged, this, &WDSPRxGUI::on_agcPowerThreshold_valueChanged);
QObject::connect(ui->agcThresholdGate, &QDial::valueChanged, this, &WDSPRxGUI::on_agcThresholdGate_valueChanged);
QObject::connect(ui->audioMute, &QToolButton::toggled, this, &WDSPRxGUI::on_audioMute_toggled);
QObject::connect(ui->spanLog2, &QSlider::valueChanged, this, &WDSPRxGUI::on_spanLog2_valueChanged);
QObject::connect(ui->flipSidebands, &QPushButton::clicked, this, &WDSPRxGUI::on_flipSidebands_clicked);
QObject::connect(ui->fftWindow, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &WDSPRxGUI::on_fftWindow_currentIndexChanged);
QObject::connect(ui->filterIndex, &QDial::valueChanged, this, &WDSPRxGUI::on_filterIndex_valueChanged);
}
void WDSPRxGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}

View File

@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020, 2022-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// 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_WDSPRXGUI_H
#define INCLUDE_WDSPRXGUI_H
#include <QIcon>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "gui/fftnrdialog.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "wdsprxsettings.h"
class PluginAPI;
class DeviceUISet;
class AudioFifo;
class WDSPRx;
class SpectrumVis;
class BasebandSampleSink;
namespace Ui {
class WDSPRxGUI;
}
class WDSPRxGUI : public ChannelGUI {
Q_OBJECT
public:
static WDSPRxGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; };
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; };
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; };
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; };
virtual QString getTitle() const { return m_settings.m_title; };
virtual QColor getTitleColor() const { return m_settings.m_rgbColor; };
virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; }
virtual bool getHidden() const { return m_settings.m_hidden; }
virtual ChannelMarker& getChannelMarker() { return m_channelMarker; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual void setStreamIndex(int streamIndex) { m_settings.m_streamIndex = streamIndex; }
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
Ui::WDSPRxGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
WDSPRxSettings m_settings;
qint64 m_deviceCenterFrequency;
int m_basebandSampleRate;
bool m_doApplySettings;
int m_spectrumRate;
bool m_audioBinaural;
bool m_audioFlipChannels;
bool m_audioMute;
bool m_squelchOpen;
int m_audioSampleRate;
uint32_t m_tickCount;
WDSPRx* m_wdspRx;
SpectrumVis* m_spectrumVis;
MessageQueue m_inputMessageQueue;
FFTNRDialog* m_fftNRDialog;
QIcon m_iconDSBUSB;
QIcon m_iconDSBLSB;
explicit WDSPRxGUI(PluginAPI* pluginAPI, DeviceUISet* deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~WDSPRxGUI();
bool blockApplySettings(bool block);
void applySettings(bool force = false);
void applyBandwidths(unsigned int spanLog2, bool force = false);
unsigned int spanLog2Max();
void displaySettings();
void displayAGC();
void displayAGCPowerThreshold(int value);
void displayAGCThresholdGate(int value);
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
uint32_t getValidAudioSampleRate() const;
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_audioBinaural_toggled(bool binaural);
void on_audioFlipChannels_toggled(bool flip);
void on_dsb_toggled(bool dsb);
void on_BW_valueChanged(int value);
void on_lowCut_valueChanged(int value);
void on_volume_valueChanged(int value);
void on_agc_toggled(bool checked);
void on_agcClamping_toggled(bool checked);
void on_dnr_toggled(bool checked);
void on_agcTimeLog2_valueChanged(int value);
void on_agcPowerThreshold_valueChanged(int value);
void on_agcThresholdGate_valueChanged(int value);
void on_audioMute_toggled(bool checked);
void on_spanLog2_valueChanged(int value);
void on_flipSidebands_clicked(bool checked);
void on_fftWindow_currentIndexChanged(int index);
void on_filterIndex_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void audioSelect(const QPoint& p);
void dnrSetupDialog(const QPoint& p);
void dnrSetup(int valueChanged);
void tick();
};
#endif // INCLUDE_WDSPRXGUI_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2014 John Greb <hexameron> //
// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2019 Davide Gerhard <rainbow@irh.it> //
// Copyright (C) 2020 Kacper Michajłow <kasper93@gmail.com> //
// //
// 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 "wdsprxplugin.h"
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "wdsprxgui.h"
#endif
#include "wdsprx.h"
#include "wdsprxwebapiadapter.h"
#include "wdsprxplugin.h"
const PluginDescriptor WDSPRxPlugin::m_pluginDescriptor = {
WDSPRx::m_channelId,
QStringLiteral("WDSP Receiver"),
QStringLiteral("7.22.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
WDSPRxPlugin::WDSPRxPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& WDSPRxPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void WDSPRxPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register demodulator
m_pluginAPI->registerRxChannel(WDSPRx::m_channelIdURI, WDSPRx::m_channelId, this);
}
void WDSPRxPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
WDSPRx *instance = new WDSPRx(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* WDSPRxPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return nullptr;
}
#else
ChannelGUI* WDSPRxPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return WDSPRxGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* WDSPRxPlugin::createChannelWebAPIAdapter() const
{
return new WDSPRxWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2016-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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_WDSPRXPLUGIN_H
#define INCLUDE_WDSPRXPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class WDSPRxPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.wdsprx")
public:
explicit WDSPRxPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_WDSPRXPLUGIN_H

View File

@ -0,0 +1,232 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 <QColor>
#include "audio/audiodevicemanager.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "wdsprxsettings.h"
#ifdef SDR_RX_SAMPLE_24BIT
const int WDSPRxSettings::m_minPowerThresholdDB = -120;
const float WDSPRxSettings::m_mminPowerThresholdDBf = 120.0f;
#else
const int WDSPRxSettings::m_minPowerThresholdDB = -100;
const float WDSPRxSettings::m_mminPowerThresholdDBf = 100.0f;
#endif
WDSPRxSettings::WDSPRxSettings() :
m_channelMarker(nullptr),
m_spectrumGUI(nullptr),
m_rollupState(nullptr)
{
m_filterBank.resize(10);
resetToDefaults();
}
void WDSPRxSettings::resetToDefaults()
{
m_audioBinaural = false;
m_audioFlipChannels = false;
m_dsb = false;
m_audioMute = false;
m_agc = false;
m_agcClamping = false;
m_agcPowerThreshold = -100;
m_agcThresholdGate = 4;
m_agcTimeLog2 = 7;
m_volume = 1.0;
m_inputFrequencyOffset = 0;
m_dnr = false;
m_dnrScheme = 0;
m_dnrAboveAvgFactor = 40.0f;
m_dnrSigmaFactor = 4.0f;
m_dnrNbPeaks = 20;
m_dnrAlpha = 1.0;
m_rgbColor = QColor(0, 255, 0).rgb();
m_title = "WDSP Receiver";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
m_filterIndex = 0;
}
QByteArray WDSPRxSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(3, m_volume * 10.0);
if (m_spectrumGUI) {
s.writeBlob(4, m_spectrumGUI->serialize());
}
s.writeU32(5, m_rgbColor);
s.writeBool(8, m_audioBinaural);
s.writeBool(9, m_audioFlipChannels);
s.writeBool(10, m_dsb);
s.writeBool(11, m_agc);
s.writeS32(12, m_agcTimeLog2);
s.writeS32(13, m_agcPowerThreshold);
s.writeS32(14, m_agcThresholdGate);
s.writeBool(15, m_agcClamping);
s.writeString(16, m_title);
s.writeString(17, m_audioDeviceName);
s.writeBool(18, m_useReverseAPI);
s.writeString(19, m_reverseAPIAddress);
s.writeU32(20, m_reverseAPIPort);
s.writeU32(21, m_reverseAPIDeviceIndex);
s.writeU32(22, m_reverseAPIChannelIndex);
s.writeS32(23, m_streamIndex);
if (m_rollupState) {
s.writeBlob(24, m_rollupState->serialize());
}
s.writeS32(25, m_workspaceIndex);
s.writeBlob(26, m_geometryBytes);
s.writeBool(27, m_hidden);
s.writeU32(29, m_filterIndex);
s.writeBool(30, m_dnr);
s.writeS32(31, m_dnrScheme);
s.writeFloat(32, m_dnrAboveAvgFactor);
s.writeFloat(33, m_dnrSigmaFactor);
s.writeS32(34, m_dnrNbPeaks);
s.writeFloat(35, m_dnrAlpha);
for (unsigned int i = 0; i < 10; i++)
{
s.writeS32(100 + 10*i, m_filterBank[i].m_spanLog2);
s.writeS32(101 + 10*i, m_filterBank[i].m_rfBandwidth / 100.0);
s.writeS32(102 + 10*i, m_filterBank[i].m_lowCutoff / 100.0);
s.writeS32(103 + 10*i, (int) m_filterBank[i].m_fftWindow);
s.writeBool(104 + 10*i, m_filterBank[i].m_dnr);
s.writeS32(105 + 10*i, m_filterBank[i].m_dnrScheme);
s.writeFloat(106 + 10*i, m_filterBank[i].m_dnrAboveAvgFactor);
s.writeFloat(107 + 10*i, m_filterBank[i].m_dnrSigmaFactor);
s.writeS32(108 + 10*i, m_filterBank[i].m_dnrNbPeaks);
s.writeFloat(109 + 10*i, m_filterBank[i].m_dnrAlpha);
}
return s.final();
}
bool WDSPRxSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
QString strtmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(3, &tmp, 30);
m_volume = tmp / 10.0;
if (m_spectrumGUI)
{
d.readBlob(4, &bytetmp);
m_spectrumGUI->deserialize(bytetmp);
}
d.readU32(5, &m_rgbColor);
d.readBool(8, &m_audioBinaural, false);
d.readBool(9, &m_audioFlipChannels, false);
d.readBool(10, &m_dsb, false);
d.readBool(11, &m_agc, false);
d.readS32(12, &m_agcTimeLog2, 7);
d.readS32(13, &m_agcPowerThreshold, -40);
d.readS32(14, &m_agcThresholdGate, 4);
d.readBool(15, &m_agcClamping, false);
d.readString(16, &m_title, "SSB Demodulator");
d.readString(17, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readBool(18, &m_useReverseAPI, false);
d.readString(19, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(20, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(21, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(22, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readS32(23, &m_streamIndex, 0);
if (m_rollupState)
{
d.readBlob(24, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(25, &m_workspaceIndex, 0);
d.readBlob(26, &m_geometryBytes);
d.readBool(27, &m_hidden, false);
d.readU32(29, &utmp, 0);
m_filterIndex = utmp < 10 ? utmp : 0;
d.readBool(30, &m_dnr, false);
d.readS32(31, &m_dnrScheme, 0);
d.readFloat(32, &m_dnrAboveAvgFactor, 40.0f);
d.readFloat(33, &m_dnrSigmaFactor, 4.0f);
d.readS32(34, &m_dnrNbPeaks, 20);
d.readFloat(35, &m_dnrAlpha, 1.0);
for (unsigned int i = 0; (i < 10); i++)
{
d.readS32(100 + 10*i, &m_filterBank[i].m_spanLog2, 3);
d.readS32(101 + 10*i, &tmp, 30);
m_filterBank[i].m_rfBandwidth = tmp * 100.0;
d.readS32(102+ 10*i, &tmp, 3);
m_filterBank[i].m_lowCutoff = tmp * 100.0;
d.readS32(103 + 10*i, &tmp, (int) FFTWindow::Blackman);
m_filterBank[i].m_fftWindow =
(FFTWindow::Function) (tmp < 0 ? 0 : tmp > (int) FFTWindow::BlackmanHarris7 ? (int) FFTWindow::BlackmanHarris7 : tmp);
d.readBool(104 + 10*i, &m_filterBank[i].m_dnr, false);
d.readS32(105 + 10*i, &m_filterBank[i].m_dnrScheme, 0);
d.readFloat(106 + 10*i, &m_filterBank[i].m_dnrAboveAvgFactor, 20.0f);
d.readFloat(107 + 10*i, &m_filterBank[i].m_dnrSigmaFactor, 4.0f);
d.readS32(108 + 10*i, &m_filterBank[i].m_dnrNbPeaks, 10);
d.readFloat(109 + 10*i, &m_filterBank[i].m_dnrAlpha, 0.95f);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,104 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELRX_WDSPRX_WDSPRXSETTINGS_H_
#define PLUGINS_CHANNELRX_WDSPRX_WDSPRXSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "dsp/fftwindow.h"
class Serializable;
struct WDSPRxFilterSettings
{
int m_spanLog2;
Real m_rfBandwidth;
Real m_lowCutoff;
FFTWindow::Function m_fftWindow;
bool m_dnr;
int m_dnrScheme;
float m_dnrAboveAvgFactor;
float m_dnrSigmaFactor;
int m_dnrNbPeaks;
float m_dnrAlpha;
WDSPRxFilterSettings() :
m_spanLog2(3),
m_rfBandwidth(3000),
m_lowCutoff(300),
m_fftWindow(FFTWindow::Blackman)
{}
};
struct WDSPRxSettings
{
qint32 m_inputFrequencyOffset;
// Real m_rfBandwidth;
// 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;
bool m_dnr;
int m_dnrScheme;
float m_dnrAboveAvgFactor;
float m_dnrSigmaFactor;
int m_dnrNbPeaks;
float m_dnrAlpha;
quint32 m_rgbColor;
QString m_title;
QString m_audioDeviceName;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
// FFTWindow::Function m_fftWindow;
std::vector<WDSPRxFilterSettings> m_filterBank;
unsigned int m_filterIndex;
Serializable *m_channelMarker;
Serializable *m_spectrumGUI;
Serializable *m_rollupState;
WDSPRxSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static const int m_minPowerThresholdDB;
static const float m_mminPowerThresholdDBf;
};
#endif /* PLUGINS_CHANNELRX_WDSPRX_WDSPRXSETTINGS_H_ */

View File

@ -0,0 +1,502 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 "dsp/spectrumvis.h"
#include "dsp/datafifo.h"
#include "util/db.h"
#include "util/messagequeue.h"
#include "maincore.h"
#include "wdsprxsink.h"
const int WDSPRxSink::m_ssbFftLen = 2048;
const int WDSPRxSink::m_agcTarget = 3276; // 32768/10 -10 dB amplitude => -20 dB power: center of normal signal
WDSPRxSink::WDSPRxSink() :
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_audioSampleRate(48000)
{
m_Bandwidth = 5000;
m_LowCutoff = 300;
m_volume = 2.0;
m_spanLog2 = 3;
m_channelSampleRate = 48000;
m_channelFrequencyOffset = 0;
m_audioBuffer.resize(m_audioSampleRate / 10);
m_audioBufferFill = 0;
m_undersampleCount = 0;
m_sum = 0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
m_usb = true;
m_magsq = 0.0;
m_magsqSum = 0.0;
m_magsqPeak = 0.0;
m_magsqCount = 0;
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);
m_lowpassI.create(101, m_audioSampleRate, m_Bandwidth * 1.2);
m_lowpassQ.create(101, m_audioSampleRate, m_Bandwidth * 1.2);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
applySettings(m_settings, true);
}
WDSPRxSink::~WDSPRxSink()
{
delete SSBFilter;
delete DSBFilter;
}
void WDSPRxSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
if (m_channelSampleRate == 0) {
return;
}
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 WDSPRxSink::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]) : 1.0;
fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay());
m_audioActive = delayedSample.real() != 0.0;
// Prevent overload based on squared magnitude variation
// Only if AGC is active
if (m_agcActive && m_agcClamping && (agcVal > 100.0 || agcVal == 0.0))
{
// qDebug("WDSPRxSink::processOneSample: %f", agcVal);
m_agc.reset(m_agcTarget*m_agcTarget);
m_squelchDelayLine.write(fftfilt::cmplx{0.0, 0.0});
}
else
{
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 && m_agcClamping) ?
fftfilt::cmplx{m_lowpassI.filter(delayedSample.real()), m_lowpassQ.filter(delayedSample.imag())}
: 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);
}
m_demodBuffer[m_demodBufferFill++] = z.real();
m_demodBuffer[m_demodBufferFill++] = z.imag();
}
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_demodBuffer[m_demodBufferFill++] = (z.real() + z.imag()) * 0.7;
}
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<ObjectPipe*> dataPipes;
MainCore::instance()->getDataPipes().getDataPipes(m_channel, "demod", dataPipes);
if (dataPipes.size() > 0)
{
QList<ObjectPipe*>::iterator it = dataPipes.begin();
for (; it != dataPipes.end(); ++it)
{
DataFifo *fifo = qobject_cast<DataFifo*>((*it)->m_element);
if (fifo)
{
fifo->write(
(quint8*) &m_demodBuffer[0],
m_demodBuffer.size() * sizeof(qint16),
m_audioBinaual ? DataFifo::DataTypeCI16 : DataFifo::DataTypeI16
);
}
}
}
m_demodBufferFill = 0;
}
}
++m_audioBufferFill;
if (m_audioBufferFill >= m_audioBuffer.size())
{
std::size_t res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], std::min(m_audioBufferFill, m_audioBuffer.size()));
if (res != m_audioBufferFill) {
qDebug("WDSPRxSink::processOneSample: %lu/%lu samples written", res, m_audioBufferFill);
}
m_audioBufferFill = 0;
}
}
if (m_spectrumSink && (m_sampleBuffer.size() != 0))
{
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_dsb);
m_sampleBuffer.clear();
}
}
void WDSPRxSink::setDNR(bool dnr)
{
SSBFilter->setDNR(dnr);
}
void WDSPRxSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "WDSPRxSink::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 WDSPRxSink::applyAudioSampleRate(int sampleRate)
{
qDebug("WDSPRxSink::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, m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow);
DSBFilter->create_dsb_filter(m_Bandwidth / (float) sampleRate, m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow);
m_lowpassI.create(101, sampleRate, m_Bandwidth * 1.2);
m_lowpassQ.create(101, sampleRate, m_Bandwidth * 1.2);
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;
m_audioBuffer.resize(sampleRate / 10);
m_audioBufferFill = 0;
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_channel, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue* messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
if (messageQueue)
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
messageQueue->push(msg);
}
}
}
}
void WDSPRxSink::applySettings(const WDSPRxSettings& settings, bool force)
{
qDebug() << "WDSPRxSink::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_filterIndex: " << settings.m_filterIndex
<< " [m_spanLog2: " << settings.m_filterBank[settings.m_filterIndex].m_spanLog2
<< " m_rfBandwidth: " << settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth
<< " m_lowCutoff: " << settings.m_filterBank[settings.m_filterIndex].m_lowCutoff
<< " m_fftWindow: " << settings.m_filterBank[settings.m_filterIndex].m_fftWindow << "]"
<< " m_volume: " << settings.m_volume
<< " 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_dnr: " << settings.m_dnr
<< " m_dnrScheme: " << settings.m_dnrScheme
<< " m_dnrAboveAvgFactor: " << settings.m_dnrAboveAvgFactor
<< " m_dnrSigmaFactor: " << settings.m_dnrSigmaFactor
<< " m_dnrNbPeaks: " << settings.m_dnrNbPeaks
<< " m_dnrAlpha: " << settings.m_dnrAlpha
<< " 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_filterBank[m_settings.m_filterIndex].m_rfBandwidth != settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth) ||
(m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff != settings.m_filterBank[settings.m_filterIndex].m_lowCutoff) ||
(m_settings.m_filterBank[m_settings.m_filterIndex].m_fftWindow != settings.m_filterBank[settings.m_filterIndex].m_fftWindow) || force)
{
float band, lowCutoff;
band = settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth;
lowCutoff = settings.m_filterBank[settings.m_filterIndex].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, settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
DSBFilter->create_dsb_filter(m_Bandwidth / (float) m_audioSampleRate, settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
m_lowpassI.create(101, m_audioSampleRate, m_Bandwidth * 1.2);
m_lowpassQ.create(101, m_audioSampleRate, m_Bandwidth * 1.2);
}
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 != -WDSPRxSettings::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_agcClamping = agcClamping;
}
qDebug() << "WDSPRxSink::applySettings: AGC:"
<< " agcNbSamples: " << agcNbSamples
<< " agcPowerThreshold: " << agcPowerThreshold
<< " agcThresholdGate: " << agcThresholdGate
<< " agcClamping: " << agcClamping;
}
if ((m_settings.m_dnr != settings.m_dnr) || force) {
setDNR(settings.m_dnr);
}
if ((m_settings.m_dnrScheme != settings.m_dnrScheme) || force) {
SSBFilter->setDNRScheme((FFTNoiseReduction::Scheme) settings.m_dnrScheme);
}
if ((m_settings.m_dnrAboveAvgFactor != settings.m_dnrAboveAvgFactor) || force) {
SSBFilter->setDNRAboveAvgFactor(settings.m_dnrAboveAvgFactor);
}
if ((m_settings.m_dnrSigmaFactor != settings.m_dnrSigmaFactor) || force) {
SSBFilter->setDNRSigmaFactor(settings.m_dnrSigmaFactor);
}
if ((m_settings.m_dnrNbPeaks != settings.m_dnrNbPeaks) || force) {
SSBFilter->setDNRNbPeaks(settings.m_dnrNbPeaks);
}
if ((m_settings.m_dnrAlpha != settings.m_dnrAlpha) || force) {
SSBFilter->setDNRAlpha(settings.m_dnrAlpha);
}
m_spanLog2 = settings.m_filterBank[settings.m_filterIndex].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,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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_WDSPRXSINK_H
#define INCLUDE_WDSPRXSINK_H
#include <QVector>
#include "dsp/channelsamplesink.h"
#include "dsp/ncof.h"
#include "dsp/interpolator.h"
#include "dsp/fftfilt.h"
#include "dsp/agc.h"
#include "dsp/firfilter.h"
#include "audio/audiofifo.h"
#include "util/doublebufferfifo.h"
#include "wdsprxsettings.h"
class SpectrumVis;
class ChannelAPI;
class WDSPRxSink : public ChannelSampleSink {
public:
WDSPRxSink();
~WDSPRxSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; }
void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
void applySettings(const WDSPRxSettings& 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 setChannel(ChannelAPI *channel) { m_channel = channel; }
void setAudioFifoLabel(const QString& label) { m_audioFifo.setLabel(label); }
void setDNR(bool dnr);
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;
};
WDSPRxSettings m_settings;
ChannelAPI *m_channel;
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)
Lowpass<Real> m_lowpassI;
Lowpass<Real> m_lowpassQ;
NCOF m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
fftfilt* SSBFilter;
fftfilt* DSBFilter;
SpectrumVis* m_spectrumSink;
SampleVector m_sampleBuffer;
AudioVector m_audioBuffer;
std::size_t m_audioBufferFill;
AudioFifo m_audioFifo;
quint32 m_audioSampleRate;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
static const int m_ssbFftLen;
static const int m_agcTarget;
void processOneSample(Complex &ci);
};
#endif // INCLUDE_SSBDEMODSINK_H

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
// written by Christian Daniel //
// Copyright (C) 2015-2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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 "SWGChannelSettings.h"
#include "wdsprx.h"
#include "wdsprxwebapiadapter.h"
WDSPRxWebAPIAdapter::WDSPRxWebAPIAdapter()
{}
WDSPRxWebAPIAdapter::~WDSPRxWebAPIAdapter()
{}
int WDSPRxWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings());
response.getSsbDemodSettings()->init();
WDSPRx::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int WDSPRxWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
WDSPRx::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// 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_WDSPRX_WEBAPIADAPTER_H
#define INCLUDE_WDSPRX_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "wdsprxsettings.h"
/**
* Standalone API adapter only for the settings
*/
class WDSPRxWebAPIAdapter : public ChannelWebAPIAdapter {
public:
WDSPRxWebAPIAdapter();
virtual ~WDSPRxWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
WDSPRxSettings m_settings;
};
#endif // INCLUDE_WDSPRX_WEBAPIADAPTER_H