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:
parent
5b05b13c5d
commit
8ce840dd17
@ -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)
|
||||||
|
@ -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)
|
||||||
|
64
plugins/channelrx/wdsprx/CMakeLists.txt
Normal file
64
plugins/channelrx/wdsprx/CMakeLists.txt
Normal 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()
|
923
plugins/channelrx/wdsprx/wdsprx.cpp
Normal file
923
plugins/channelrx/wdsprx/wdsprx.cpp
Normal 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);
|
||||||
|
}
|
187
plugins/channelrx/wdsprx/wdsprx.h
Normal file
187
plugins/channelrx/wdsprx/wdsprx.h
Normal 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
|
245
plugins/channelrx/wdsprx/wdsprxbaseband.cpp
Normal file
245
plugins/channelrx/wdsprx/wdsprxbaseband.cpp
Normal 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());
|
||||||
|
}
|
100
plugins/channelrx/wdsprx/wdsprxbaseband.h
Normal file
100
plugins/channelrx/wdsprx/wdsprxbaseband.h
Normal 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
|
897
plugins/channelrx/wdsprx/wdsprxgui.cpp
Normal file
897
plugins/channelrx/wdsprx/wdsprxgui.cpp
Normal 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);
|
||||||
|
}
|
144
plugins/channelrx/wdsprx/wdsprxgui.h
Normal file
144
plugins/channelrx/wdsprx/wdsprxgui.h
Normal 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
|
1190
plugins/channelrx/wdsprx/wdsprxgui.ui
Normal file
1190
plugins/channelrx/wdsprx/wdsprxgui.ui
Normal file
File diff suppressed because it is too large
Load Diff
97
plugins/channelrx/wdsprx/wdsprxplugin.cpp
Normal file
97
plugins/channelrx/wdsprx/wdsprxplugin.cpp
Normal 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();
|
||||||
|
}
|
49
plugins/channelrx/wdsprx/wdsprxplugin.h
Normal file
49
plugins/channelrx/wdsprx/wdsprxplugin.h
Normal 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
|
232
plugins/channelrx/wdsprx/wdsprxsettings.cpp
Normal file
232
plugins/channelrx/wdsprx/wdsprxsettings.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
104
plugins/channelrx/wdsprx/wdsprxsettings.h
Normal file
104
plugins/channelrx/wdsprx/wdsprxsettings.h
Normal 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_ */
|
502
plugins/channelrx/wdsprx/wdsprxsink.cpp
Normal file
502
plugins/channelrx/wdsprx/wdsprxsink.cpp
Normal 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;
|
||||||
|
}
|
142
plugins/channelrx/wdsprx/wdsprxsink.h
Normal file
142
plugins/channelrx/wdsprx/wdsprxsink.h
Normal 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
|
53
plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp
Normal file
53
plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp
Normal 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;
|
||||||
|
}
|
49
plugins/channelrx/wdsprx/wdsprxwebapiadapter.h
Normal file
49
plugins/channelrx/wdsprx/wdsprxwebapiadapter.h
Normal 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
|
Loading…
Reference in New Issue
Block a user