From 8ce840dd17ae0c76859cf3366d3c99f8a5394f06 Mon Sep 17 00:00:00 2001 From: f4exb Date: Fri, 21 Jun 2024 00:44:17 +0200 Subject: [PATCH] WDSP Receiver as a copy of the SSB demodulator --- CMakeLists.txt | 1 + plugins/channelrx/CMakeLists.txt | 7 + plugins/channelrx/wdsprx/CMakeLists.txt | 64 + plugins/channelrx/wdsprx/wdsprx.cpp | 923 +++++++++++++ plugins/channelrx/wdsprx/wdsprx.h | 187 +++ plugins/channelrx/wdsprx/wdsprxbaseband.cpp | 245 ++++ plugins/channelrx/wdsprx/wdsprxbaseband.h | 100 ++ plugins/channelrx/wdsprx/wdsprxgui.cpp | 897 +++++++++++++ plugins/channelrx/wdsprx/wdsprxgui.h | 144 ++ plugins/channelrx/wdsprx/wdsprxgui.ui | 1190 +++++++++++++++++ plugins/channelrx/wdsprx/wdsprxplugin.cpp | 97 ++ plugins/channelrx/wdsprx/wdsprxplugin.h | 49 + plugins/channelrx/wdsprx/wdsprxsettings.cpp | 232 ++++ plugins/channelrx/wdsprx/wdsprxsettings.h | 104 ++ plugins/channelrx/wdsprx/wdsprxsink.cpp | 502 +++++++ plugins/channelrx/wdsprx/wdsprxsink.h | 142 ++ .../channelrx/wdsprx/wdsprxwebapiadapter.cpp | 53 + .../channelrx/wdsprx/wdsprxwebapiadapter.h | 49 + 18 files changed, 4986 insertions(+) create mode 100644 plugins/channelrx/wdsprx/CMakeLists.txt create mode 100644 plugins/channelrx/wdsprx/wdsprx.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprx.h create mode 100644 plugins/channelrx/wdsprx/wdsprxbaseband.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxbaseband.h create mode 100644 plugins/channelrx/wdsprx/wdsprxgui.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxgui.h create mode 100644 plugins/channelrx/wdsprx/wdsprxgui.ui create mode 100644 plugins/channelrx/wdsprx/wdsprxplugin.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxplugin.h create mode 100644 plugins/channelrx/wdsprx/wdsprxsettings.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxsettings.h create mode 100644 plugins/channelrx/wdsprx/wdsprxsink.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxsink.h create mode 100644 plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp create mode 100644 plugins/channelrx/wdsprx/wdsprxwebapiadapter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d85bcf88f..a7e6e50b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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_ENDOFTRAIN "Enable channelrx end-of-train 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 option(ENABLE_CHANNELTX "Enable channeltx plugins" ON) diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index c1759b72f..5d8a3aa3d 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -205,6 +205,13 @@ else() message(STATUS "Not building demoddsc (ENABLE_CHANNELRX_DEMODDSC=${ENABLE_CHANNELRX_DEMODDSC})") 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 (ENABLE_CHANNELRX_HEATMAP) add_subdirectory(heatmap) diff --git a/plugins/channelrx/wdsprx/CMakeLists.txt b/plugins/channelrx/wdsprx/CMakeLists.txt new file mode 100644 index 000000000..d24c9587f --- /dev/null +++ b/plugins/channelrx/wdsprx/CMakeLists.txt @@ -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 $ CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} ) +endif() diff --git a/plugins/channelrx/wdsprx/wdsprx.cpp b/plugins/channelrx/wdsprx/wdsprx.cpp new file mode 100644 index 000000000..7c54e7569 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprx.cpp @@ -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 // +// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2022 Jiří Pinkava // +// (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 . // +/////////////////////////////////////////////////////////////////////////////////// + + +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 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 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 pipes; + MainCore::instance()->getMessagePipes().getMessagePipes(this, "reportdemod", pipes); + + if (pipes.size() > 0) + { + for (const auto& pipe: pipes) + { + MessageQueue *messageQueue = qobject_cast(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& 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& pipes, + QList& channelSettingsKeys, + const WDSPRxSettings& settings, + bool force) +{ + qDebug("WDSPRx::sendChannelSettings: %d pipes", pipes.size()); + + for (const auto& pipe : pipes) + { + MessageQueue *messageQueue = qobject_cast(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& 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); +} diff --git a/plugins/channelrx/wdsprx/wdsprx.h b/plugins/channelrx/wdsprx/wdsprx.h new file mode 100644 index 000000000..cce0f4910 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprx.h @@ -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 // +// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Kacper Michajłow // +// Copyright (C) 2022 Jiří Pinkava // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WDSPRX_H +#define INCLUDE_WDSPRX_H + +#include + +#include +#include + +#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& channelSettingsKeys, const WDSPRxSettings& settings, bool force); + void sendChannelSettings( + const QList& pipes, + QList& channelSettingsKeys, + const WDSPRxSettings& settings, + bool force + ); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const WDSPRxSettings& settings, + bool force + ); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + void handleIndexInDeviceSetChanged(int index); +}; + +#endif // INCLUDE_WDSPRX_H diff --git a/plugins/channelrx/wdsprx/wdsprxbaseband.cpp b/plugins/channelrx/wdsprx/wdsprxbaseband.cpp new file mode 100644 index 000000000..c0696d50b --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxbaseband.cpp @@ -0,0 +1,245 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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<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<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<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()); +} diff --git a/plugins/channelrx/wdsprx/wdsprxbaseband.h b/plugins/channelrx/wdsprx/wdsprxbaseband.h new file mode 100644 index 000000000..6042b51b2 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxbaseband.h @@ -0,0 +1,100 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019-2022 Edouard Griffiths, F4EXB // +// Copyright (C) 2022 Jiří Pinkava // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WDSPRXBASEBAND_H +#define INCLUDE_WDSPRXBASEBAND_H + +#include +#include + +#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 diff --git a/plugins/channelrx/wdsprx/wdsprxgui.cpp b/plugins/channelrx/wdsprx/wdsprxgui.cpp new file mode 100644 index 000000000..a133882b9 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxgui.cpp @@ -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 // +// Copyright (C) 2021-2023 Jon Beniston, M7RCE // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////////// +#include + +#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(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<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<= 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<BW->value(); + int lw = ui->lowCut->value(); + int bwMax = getValidAudioSampleRate() / (100*(1<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<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::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); +} diff --git a/plugins/channelrx/wdsprx/wdsprxgui.h b/plugins/channelrx/wdsprx/wdsprxgui.h new file mode 100644 index 000000000..137697775 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxgui.h @@ -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 // +// Copyright (C) 2022 Jon Beniston, M7RCE // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_WDSPRXGUI_H +#define INCLUDE_WDSPRXGUI_H + +#include + +#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 diff --git a/plugins/channelrx/wdsprx/wdsprxgui.ui b/plugins/channelrx/wdsprx/wdsprxgui.ui new file mode 100644 index 000000000..e67737b97 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxgui.ui @@ -0,0 +1,1190 @@ + + + WDSPRxGUI + + + + 0 + 0 + 414 + 190 + + + + + 0 + 0 + + + + + 414 + 0 + + + + + Liberation Sans + 9 + + + + WDSP Receiver + + + + + 0 + 0 + 412 + 181 + + + + + 412 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 125 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 60 + 0 + + + + Channel power + + + Qt::RightToLeft + + + -100.0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + + Toggle btw Mono and Binaural I/Q audio + + + ... + + + + :/mono.png + :/stereo.png:/mono.png + + + true + + + + + + + Flip left/right audio channels + + + ... + + + + :/flip_lr.png + :/flip_rl.png:/flip_lr.png + + + true + + + + + + + Qt::Vertical + + + + + + + Flip sidebands in SSB mode (LSB->USB or USB->LSB) + + + + + + + :/flip_sidebands.png:/flip_sidebands.png + + + + + + + DSB/SSB toggle + + + ... + + + + :/usb.png + :/dsb.png:/usb.png + + + true + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Span + + + + + + + Demod frequency span + + + 0 + + + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 70 + 0 + + + + + 60 + 16777215 + + + + FFT filter window function + + + QComboBox::AdjustToContents + + + + Bart + + + + + B-H + + + + + FT + + + + + Ham + + + + + Han + + + + + Rec + + + + + Kai + + + + + Black + + + + + B-H7 + + + + + + + + + 24 + 24 + + + + Select filter in filter bank + + + 0 + + + 9 + + + 1 + + + 0 + + + + + + + + 10 + 0 + + + + false + + + Filter index in filter bank + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 + + + 1 + + + 3 + + + Qt::Horizontal + + + false + + + QSlider::NoTicks + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 0.3k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Hi cut + + + + + + + + 16777215 + 16 + + + + Lowpass filter cutoff frequency + + + -60 + + + 60 + + + 1 + + + 30 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 3.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + Liberation Sans + 8 + + + + f: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 10 + 10 + + + + + Liberation Sans + 8 + + + + - + + + + + + + + Liberation Sans + 8 + + + + LSB + + + Qt::AlignCenter + + + + + + + + 12 + 10 + + + + + Liberation Sans + 8 + + + + 0 + + + Qt::AlignCenter + + + + + + + + Liberation Sans + 8 + + + + USB + + + Qt::AlignCenter + + + + + + + + 10 + 10 + + + + + Liberation Sans + 8 + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Vol + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + Audio volume in dB + + + -10 + + + 40 + + + 1 + + + + + + + Audio volume in dB + + + 10 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Toggle AGC + + + AGC + + + true + + + + + + + Toggle AGC clamping to maximum allowable signal + + + CL + + + true + + + + + + + Toggle Digital Noise Reduction + + + NR + + + true + + + + + + + Qt::Vertical + + + + + + + + 24 + 24 + + + + AGC time constant (ms in log2 steps) + + + 4 + + + 11 + + + 1 + + + 7 + + + + + + + + 35 + 0 + + + + AGC time constant (ms) + + + 0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + 24 + 24 + + + + Power threshold (dB) + + + -120 + + + 0 + + + 1 + + + -40 + + + + + + + + 26 + 0 + + + + Power threshold (dB) + + + -000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + + 24 + 24 + + + + Power threshold gate (ms) + + + 68 + + + 1 + + + 4 + + + + + + + + 22 + 0 + + + + Power threshold gate (ms) + + + 000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Mute/Unmute audio + + + + + + + :/sound_on.png + :/sound_off.png:/sound_on.png + + + true + + + + + + + + + + + 10 + 170 + 218 + 284 + + + + + 0 + 0 + + + + Channel Spectrum + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 200 + 250 + + + + + Liberation Mono + 8 + + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + + + + + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + RollupContents + QWidget +
gui/rollupcontents.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + GLSpectrum + QWidget +
gui/glspectrum.h
+ 1 +
+ + GLSpectrumGUI + QWidget +
gui/glspectrumgui.h
+ 1 +
+ + TickedSlider + QSlider +
gui/tickedslider.h
+
+
+ + + + +
diff --git a/plugins/channelrx/wdsprx/wdsprxplugin.cpp b/plugins/channelrx/wdsprx/wdsprxplugin.cpp new file mode 100644 index 000000000..a00c14893 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxplugin.cpp @@ -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 // +// Copyright (C) 2015-2023 Edouard Griffiths, F4EXB // +// Copyright (C) 2019 Davide Gerhard // +// Copyright (C) 2020 Kacper Michajłow // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////////// +#include "wdsprxplugin.h" + +#include +#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(); +} diff --git a/plugins/channelrx/wdsprx/wdsprxplugin.h b/plugins/channelrx/wdsprx/wdsprxplugin.h new file mode 100644 index 000000000..52ff7a97f --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxplugin.h @@ -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 // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////////// +#ifndef INCLUDE_WDSPRXPLUGIN_H +#define INCLUDE_WDSPRXPLUGIN_H + +#include +#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 diff --git a/plugins/channelrx/wdsprx/wdsprxsettings.cpp b/plugins/channelrx/wdsprx/wdsprxsettings.cpp new file mode 100644 index 000000000..ee1fab216 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxsettings.cpp @@ -0,0 +1,232 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#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; + } +} diff --git a/plugins/channelrx/wdsprx/wdsprxsettings.h b/plugins/channelrx/wdsprx/wdsprxsettings.h new file mode 100644 index 000000000..2495c3c21 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxsettings.h @@ -0,0 +1,104 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELRX_WDSPRX_WDSPRXSETTINGS_H_ +#define PLUGINS_CHANNELRX_WDSPRX_WDSPRXSETTINGS_H_ + +#include +#include + +#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 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_ */ diff --git a/plugins/channelrx/wdsprx/wdsprxsink.cpp b/plugins/channelrx/wdsprx/wdsprxsink.cpp new file mode 100644 index 000000000..299a5192f --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxsink.cpp @@ -0,0 +1,502 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include + +#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 dataPipes; + MainCore::instance()->getDataPipes().getDataPipes(m_channel, "demod", dataPipes); + + if (dataPipes.size() > 0) + { + QList::iterator it = dataPipes.begin(); + + for (; it != dataPipes.end(); ++it) + { + DataFifo *fifo = qobject_cast((*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< pipes; + MainCore::instance()->getMessagePipes().getMessagePipes(m_channel, "reportdemod", pipes); + + if (pipes.size() > 0) + { + for (const auto& pipe : pipes) + { + MessageQueue* messageQueue = qobject_cast(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<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; +} diff --git a/plugins/channelrx/wdsprx/wdsprxsink.h b/plugins/channelrx/wdsprx/wdsprxsink.h new file mode 100644 index 000000000..f2e9ccdac --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxsink.h @@ -0,0 +1,142 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2024 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_WDSPRXSINK_H +#define INCLUDE_WDSPRXSINK_H + +#include + +#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 m_squelchDelayLine; + bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) + Lowpass m_lowpassI; + Lowpass 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 m_demodBuffer; + int m_demodBufferFill; + + static const int m_ssbFftLen; + static const int m_agcTarget; + + void processOneSample(Complex &ci); +}; + +#endif // INCLUDE_SSBDEMODSINK_H diff --git a/plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp b/plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp new file mode 100644 index 000000000..60fd2b0c9 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxwebapiadapter.cpp @@ -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 // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#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; +} diff --git a/plugins/channelrx/wdsprx/wdsprxwebapiadapter.h b/plugins/channelrx/wdsprx/wdsprxwebapiadapter.h new file mode 100644 index 000000000..3a86ee623 --- /dev/null +++ b/plugins/channelrx/wdsprx/wdsprxwebapiadapter.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#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