From 8a5685cdfd2d3ceb441b1f24e7dd7f9660bf8ed2 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Fri, 16 Apr 2021 22:56:15 +0100 Subject: [PATCH] Add DAB demodulator --- .appveyor.yml | 4 +- CMakeLists.txt | 15 + cmake/Modules/FindFAAD.cmake | 31 + debian/control | 11 +- doc/img/DABDemod_plugin.png | Bin 0 -> 24240 bytes external/CMakeLists.txt | 40 + plugins/channelrx/CMakeLists.txt | 4 + plugins/channelrx/demoddab/CMakeLists.txt | 64 ++ plugins/channelrx/demoddab/dabdemod.cpp | 541 +++++++++ plugins/channelrx/demoddab/dabdemod.h | 375 ++++++ .../channelrx/demoddab/dabdemodbaseband.cpp | 203 ++++ plugins/channelrx/demoddab/dabdemodbaseband.h | 95 ++ plugins/channelrx/demoddab/dabdemoddevice.cpp | 120 ++ plugins/channelrx/demoddab/dabdemoddevice.h | 38 + plugins/channelrx/demoddab/dabdemodgui.cpp | 651 +++++++++++ plugins/channelrx/demoddab/dabdemodgui.h | 113 ++ plugins/channelrx/demoddab/dabdemodgui.ui | 1017 +++++++++++++++++ plugins/channelrx/demoddab/dabdemodplugin.cpp | 92 ++ plugins/channelrx/demoddab/dabdemodplugin.h | 49 + .../channelrx/demoddab/dabdemodsettings.cpp | 149 +++ plugins/channelrx/demoddab/dabdemodsettings.h | 59 + plugins/channelrx/demoddab/dabdemodsink.cpp | 644 +++++++++++ plugins/channelrx/demoddab/dabdemodsink.h | 147 +++ .../demoddab/dabdemodwebapiadapter.cpp | 52 + .../demoddab/dabdemodwebapiadapter.h | 50 + plugins/channelrx/demoddab/readme.md | 80 ++ .../demodanalyzer/demodanalyzersettings.cpp | 2 + sdrbase/webapi/webapirequestmapper.cpp | 6 + sdrbase/webapi/webapiutils.cpp | 2 + .../api/swagger/include/ChannelSettings.yaml | 2 + .../api/swagger/include/DABDemod.yaml | 36 + .../code/qt5/client/SWGChannelSettings.cpp | 25 + .../code/qt5/client/SWGChannelSettings.h | 7 + .../code/qt5/client/SWGDABDemodSettings.cpp | 415 +++++++ .../code/qt5/client/SWGDABDemodSettings.h | 137 +++ .../code/qt5/client/SWGModelFactory.h | 4 + 36 files changed, 5274 insertions(+), 6 deletions(-) create mode 100644 cmake/Modules/FindFAAD.cmake create mode 100644 doc/img/DABDemod_plugin.png create mode 100644 plugins/channelrx/demoddab/CMakeLists.txt create mode 100644 plugins/channelrx/demoddab/dabdemod.cpp create mode 100644 plugins/channelrx/demoddab/dabdemod.h create mode 100644 plugins/channelrx/demoddab/dabdemodbaseband.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodbaseband.h create mode 100644 plugins/channelrx/demoddab/dabdemoddevice.cpp create mode 100644 plugins/channelrx/demoddab/dabdemoddevice.h create mode 100644 plugins/channelrx/demoddab/dabdemodgui.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodgui.h create mode 100644 plugins/channelrx/demoddab/dabdemodgui.ui create mode 100644 plugins/channelrx/demoddab/dabdemodplugin.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodplugin.h create mode 100644 plugins/channelrx/demoddab/dabdemodsettings.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodsettings.h create mode 100644 plugins/channelrx/demoddab/dabdemodsink.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodsink.h create mode 100644 plugins/channelrx/demoddab/dabdemodwebapiadapter.cpp create mode 100644 plugins/channelrx/demoddab/dabdemodwebapiadapter.h create mode 100644 plugins/channelrx/demoddab/readme.md create mode 100644 swagger/sdrangel/api/swagger/include/DABDemod.yaml create mode 100644 swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.cpp create mode 100644 swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.h diff --git a/.appveyor.yml b/.appveyor.yml index 3922409b1..79e99bb99 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -79,9 +79,9 @@ for: qttools5-dev qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins libqt5websockets5-dev \ libqt5quick5 \ qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \ - qml-module-qtquick-controls qml-module-qtquick-layouts \ + qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts \ libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \ - libqt5charts5-dev libqt5texttospeech5-dev \ + libqt5charts5-dev libqt5texttospeech5-dev libfaad-dev zlib1g-dev \ libusb-1.0-0-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \ libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \ libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \ diff --git a/CMakeLists.txt b/CMakeLists.txt index bc27aed72..e7da761be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,11 @@ elseif (WIN32) set(UHD_LIBRARIES "${EXTERNAL_LIBRARY_FOLDER}/uhd/lib/uhd.lib" CACHE INTERNAL "") set(UHD_DLL_DIR "${EXTERNAL_LIBRARY_FOLDER}/uhd/bin" CACHE INTERNAL "") + set(DAB_FOUND ON CACHE INTERNAL "") + set(DAB_INCLUDE_DIR "${EXTERNAL_LIBRARY_FOLDER}/dab" CACHE INTERNAL "") + set(DAB_LIBRARIES "${EXTERNAL_LIBRARY_FOLDER}/dab/dab_lib.lib" CACHE INTERNAL "") + set(DAB_DLL_DIR "${EXTERNAL_LIBRARY_FOLDER}/dab" CACHE INTERNAL "") + set(OPENSSL_FOUND ON CACHE INTERNAL "") set(OPENSSL_DLL_DIR "${EXTERNAL_LIBRARY_FOLDER}/openssl" CACHE INTERNAL "") @@ -255,6 +260,14 @@ elseif (WIN32) set(SDRPLAY_LIBRARIES "${EXTERNAL_LIBRARY_FOLDER}/sdrplay/x64/sdrplay_api.lib" CACHE INTERNAL "") set(SDRPLAY_DLL_DIR "${EXTERNAL_LIBRARY_FOLDER}/sdrplay/x64/" CACHE INTERNAL "") + set(FAAD_FOUND ON CACHE INTERNAL "") + set(FAAD_INCLUDE_DIR "${EXTERNAL_LIBRARY_FOLDER}/faad2/include" CACHE INTERNAL "") + set(FAAD_LIBRARY "${EXTERNAL_LIBRARY_FOLDER}/faad2/lib/libfaad.lib" CACHE INTERNAL "") + + set(ZLIB_FOUND ON CACHE INTERNAL "") + set(ZLIB_INCLUDE_DIRS "${EXTERNAL_LIBRARY_FOLDER}/zlib/include" CACHE INTERNAL "") + set(ZLIB_LIBRARIES "${EXTERNAL_LIBRARY_FOLDER}/zlib/lib/zlibstaticd.lib" CACHE INTERNAL "") + # used on fixup_bundle phase set(WINDOWS_FIXUP_BUNDLE_LIB_DIRS "${EXTERNAL_LIBRARY_FOLDER}/fftw-3" @@ -330,6 +343,8 @@ find_package(FFTW3F REQUIRED) find_package(LibUSB REQUIRED) # used by so many packages find_package(OpenCV OPTIONAL_COMPONENTS core highgui imgproc imgcodecs videoio) # channeltx/modatv find_package(LibSigMF) # SigMF recording files support +find_package(ZLIB) # For DAB +find_package(FAAD) # For DAB if (LIBSIGMF_FOUND AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_definitions(-DHAS_LIBSIGMF) diff --git a/cmake/Modules/FindFAAD.cmake b/cmake/Modules/FindFAAD.cmake new file mode 100644 index 000000000..249f0c98e --- /dev/null +++ b/cmake/Modules/FindFAAD.cmake @@ -0,0 +1,31 @@ +IF(NOT FAAD_FOUND) + INCLUDE(FindPkgConfig) + PKG_CHECK_MODULES(PC_FAAD faad2) + + FIND_PATH( + FAAD_INCLUDE_DIR + NAMES faad.h + HINTS ${FAAD_DIR}/include + ${PC_FAAD_INCLUDE_DIRS} + PATHS /usr/local/include + /usr/include + ) + + FIND_LIBRARY( + FAAD_LIBRARY + NAMES faad + HINTS ${FAAD_DIR}/lib + ${PC_FAAD_LIBRARY_DIRS} + PATHS /usr/local/lib + /usr/lib + /usr/lib64 + ) + + message(STATUS "FAAD LIBRARY " ${FAAD_LIBRARY}) + message(STATUS "FAAD INCLUDE DIRS " ${FAAD_INCLUDE_DIR}) + + INCLUDE(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(FAAD DEFAULT_MSG FAAD_LIBRARY FAAD_INCLUDE_DIR) + MARK_AS_ADVANCED(FAAD_LIBRARY FAAD_INCLUDE_DIR) + +ENDIF(NOT FAAD_FOUND) diff --git a/debian/control b/debian/control index 803d4f468..c0443cdfe 100644 --- a/debian/control +++ b/debian/control @@ -20,6 +20,7 @@ Build-Depends: debhelper (>= 9), qml-module-qtquick-window2, qml-module-qtquick-dialogs, qml-module-qtquick-controls, + qml-module-qtquick-controls2, qml-module-qtquick-layouts, libqt5serialport5-dev, libqt5charts5-dev, @@ -36,12 +37,14 @@ Build-Depends: debhelper (>= 9), bison, flex, ffmpeg, + libfaad-dev, libavcodec-dev, libavformat-dev, libopus-dev, libairspy-dev, libhackrf-dev, libuhd-dev + zlib1g-dev # TODO: # - more dependencies based on version; newer has more devices # - manage dependencies not present upstream @@ -51,16 +54,16 @@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libasound2, libgl1-mesa-glx, libqt5multimedia5-plugins, pulseaudio, ffmpeg Description: SDR/Analyzer/Generator front-end for various hardware SDR/Analyzer/Generator front-end for Airspy, BladeRF, HackRF, - RTL-SDR, FunCube, LimeSDR, PlutoSDR, USRP. + RTL-SDR, FunCube, LimeSDR, PlutoSDR, RSP, USRP. Also File source and sink for I/Q samples, network I/Q sources with remote instance. Based on Qt5 framework and OpenGL for the spectrum and scope rendering. Builds on Linux, Windows and Mac O/S Reception modes supported: - Analog: AM, ATV, NFM, WFM, SSB, broadcast FM - Digital: D-Star, Yaesu SF, DMR, dPMR, LoRa, ADS-B, Packet (AX.25/APRS) + Analog: AM, ATV, NFM, WFM, SSB, broadcast FM, APT + Digital: D-Star, Yaesu SF, DMR, dPMR, FreeDV, DAB, DVB-S, LoRa, ADS-B, Packet (AX.25/APRS) Analyzer: Generic channel Transmission modes supported: Analog: AM, ATV, NFM, SSB, WFM - Digital: Packet (AX.25), 802.15.4 + Digital: DVB-S, Packet (AX.25), 802.15.4 Homepage: https://github.com/f4exb/sdrangel diff --git a/doc/img/DABDemod_plugin.png b/doc/img/DABDemod_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..c00ab73e85a244cb722dbaa08a6c7e753bfec1d5 GIT binary patch literal 24240 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. // +/////////////////////////////////////////////////////////////////////////////////// + +#include "dabdemod.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGDABDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGMapItem.h" + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "device/deviceapi.h" +#include "feature/feature.h" +#include "util/db.h" +#include "maincore.h" + +MESSAGE_CLASS_DEFINITION(DABDemod::MsgConfigureDABDemod, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABEnsembleName, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramName, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramData, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABSystemData, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABProgramQuality, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABFIBQuality, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABSampleRate, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABData, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABReset, Message) +MESSAGE_CLASS_DEFINITION(DABDemod::MsgDABResetService, Message) + +const char * const DABDemod::m_channelIdURI = "sdrangel.channel.dabdemod"; +const char * const DABDemod::m_channelId = "DABDemod"; + +DABDemod::DABDemod(DeviceAPI *deviceAPI) : + ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), + m_deviceAPI(deviceAPI), + m_basebandSampleRate(0) +{ + setObjectName(m_channelId); + + m_basebandSink = new DABDemodBaseband(this); + m_basebandSink->setMessageQueueToChannel(getInputMessageQueue()); + m_basebandSink->setChannel(this); + m_basebandSink->moveToThread(&m_thread); + + applySettings(m_settings, true); + + m_deviceAPI->addChannelSink(this); + m_deviceAPI->addChannelSinkAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +DABDemod::~DABDemod() +{ + qDebug("DABDemod::~DABDemod"); + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + + if (m_basebandSink->isRunning()) { + stop(); + } + + delete m_basebandSink; +} + +uint32_t DABDemod::getNumberOfDeviceStreams() const +{ + return m_deviceAPI->getNbSourceStreams(); +} + +void DABDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) +{ + (void) firstOfBurst; + m_basebandSink->feed(begin, end); +} + +void DABDemod::start() +{ + qDebug("DABDemod::start"); + + m_basebandSink->reset(); + m_basebandSink->startWork(); + m_thread.start(); + + DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency); + m_basebandSink->getInputMessageQueue()->push(dspMsg); + + DABDemodBaseband::MsgConfigureDABDemodBaseband *msg = DABDemodBaseband::MsgConfigureDABDemodBaseband::create(m_settings, true); + m_basebandSink->getInputMessageQueue()->push(msg); +} + +void DABDemod::stop() +{ + qDebug("DABDemod::stop"); + m_basebandSink->stopWork(); + m_thread.quit(); + m_thread.wait(); +} + +bool DABDemod::handleMessage(const Message& cmd) +{ + if (MsgConfigureDABDemod::match(cmd)) + { + MsgConfigureDABDemod& cfg = (MsgConfigureDABDemod&) cmd; + qDebug() << "DABDemod::handleMessage: MsgConfigureDABDemod"; + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + m_centerFrequency = notif.getCenterFrequency(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "DABDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + // Forward to GUI if any + if (m_guiMessageQueue) + { + rep = new DSPSignalNotification(notif); + m_guiMessageQueue->push(rep); + } + + return true; + } + else if (MsgDABSystemData::match(cmd)) + { + MsgDABSystemData& report = (MsgDABSystemData&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABSystemData(report)); + } + + return true; + } + else if (MsgDABProgramQuality::match(cmd)) + { + MsgDABProgramQuality& report = (MsgDABProgramQuality&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABProgramQuality(report)); + } + + return true; + } + else if (MsgDABFIBQuality::match(cmd)) + { + MsgDABFIBQuality& report = (MsgDABFIBQuality&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABFIBQuality(report)); + } + + return true; + } + else if (MsgDABSampleRate::match(cmd)) + { + MsgDABSampleRate& report = (MsgDABSampleRate&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABSampleRate(report)); + } + + return true; + } + else if (MsgDABEnsembleName::match(cmd)) + { + MsgDABEnsembleName& report = (MsgDABEnsembleName&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABEnsembleName(report)); + } + + return true; + } + else if (MsgDABProgramName::match(cmd)) + { + MsgDABProgramName& report = (MsgDABProgramName&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABProgramName(report)); + } + + return true; + } + else if (MsgDABProgramData::match(cmd)) + { + MsgDABProgramData& report = (MsgDABProgramData&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABProgramData(report)); + } + + return true; + } + else if (MsgDABData::match(cmd)) + { + MsgDABData& report = (MsgDABData&)cmd; + if (getMessageQueueToGUI()) + { + getMessageQueueToGUI()->push(new MsgDABData(report)); + } + + return true; + } + else if (MsgDABReset::match(cmd)) + { + MsgDABReset& report = (MsgDABReset&)cmd; + m_basebandSink->getInputMessageQueue()->push(new MsgDABReset(report)); + + return true; + } + else if (MsgDABResetService::match(cmd)) + { + MsgDABResetService& report = (MsgDABResetService&)cmd; + m_basebandSink->getInputMessageQueue()->push(new MsgDABResetService(report)); + + return true; + } + else + { + return false; + } +} + +void DABDemod::applySettings(const DABDemodSettings& settings, bool force) +{ + qDebug() << "DABDemod::applySettings:" + << " 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 ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); + } + if ((settings.m_program != m_settings.m_program) || force) { + reverseAPIKeys.append("program"); + } + if ((settings.m_volume != m_settings.m_volume) || force) { + reverseAPIKeys.append("volume"); + } + if ((settings.m_audioMute != m_settings.m_audioMute) || force) { + reverseAPIKeys.append("audioMute"); + } + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { + reverseAPIKeys.append("audioDeviceName"); + } + 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); + } + + reverseAPIKeys.append("streamIndex"); + } + + DABDemodBaseband::MsgConfigureDABDemodBaseband *msg = DABDemodBaseband::MsgConfigureDABDemodBaseband::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); + } + + m_settings = settings; +} + +QByteArray DABDemod::serialize() const +{ + return m_settings.serialize(); +} + +bool DABDemod::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int DABDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings()); + response.getDabDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int DABDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + DABDemodSettings settings = m_settings; + webapiUpdateChannelSettings(settings, channelSettingsKeys, response); + + MsgConfigureDABDemod *msg = MsgConfigureDABDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("DABDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureDABDemod *msgToGUI = MsgConfigureDABDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +void DABDemod::webapiUpdateChannelSettings( + DABDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response) +{ + if (channelSettingsKeys.contains("inputFrequencyOffset")) { + settings.m_inputFrequencyOffset = response.getDabDemodSettings()->getInputFrequencyOffset(); + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getDabDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("program")) { + settings.m_program = *response.getDabDemodSettings()->getProgram(); + } + if (channelSettingsKeys.contains("m_volume")) { + settings.m_volume = response.getDabDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getDabDemodSettings()->getAudioMute(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getDabDemodSettings()->getAudioDeviceName(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getDabDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getDabDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getDabDemodSettings()->getStreamIndex(); + } + if (channelSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getDabDemodSettings()->getUseReverseApi() != 0; + } + if (channelSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getDabDemodSettings()->getReverseApiAddress(); + } + if (channelSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getDabDemodSettings()->getReverseApiPort(); + } + if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getDabDemodSettings()->getReverseApiDeviceIndex(); + } + if (channelSettingsKeys.contains("reverseAPIChannelIndex")) { + settings.m_reverseAPIChannelIndex = response.getDabDemodSettings()->getReverseApiChannelIndex(); + } +} + +void DABDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DABDemodSettings& settings) +{ + response.getDabDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getDabDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getDabDemodSettings()->setProgram(new QString(settings.m_program)); + response.getDabDemodSettings()->setVolume(settings.m_volume); + response.getDabDemodSettings()->setAudioMute(settings.m_audioMute); + response.getDabDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + + response.getDabDemodSettings()->setRgbColor(settings.m_rgbColor); + if (response.getDabDemodSettings()->getTitle()) { + *response.getDabDemodSettings()->getTitle() = settings.m_title; + } else { + response.getDabDemodSettings()->setTitle(new QString(settings.m_title)); + } + + response.getDabDemodSettings()->setStreamIndex(settings.m_streamIndex); + response.getDabDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getDabDemodSettings()->getReverseApiAddress()) { + *response.getDabDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getDabDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getDabDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getDabDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); + response.getDabDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex); +} + +void DABDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const DABDemodSettings& 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 DABDemod::webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const DABDemodSettings& settings, + bool force +) +{ + swgChannelSettings->setDirection(0); // Single sink (Rx) + swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet()); + swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex()); + swgChannelSettings->setChannelType(new QString("DABDemod")); + swgChannelSettings->setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings()); + SWGSDRangel::SWGDABDemodSettings *swgDABDemodSettings = swgChannelSettings->getDabDemodSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgDABDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgDABDemodSettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("program") || force) { + swgDABDemodSettings->setProgram(new QString(settings.m_program)); + } + if (channelSettingsKeys.contains("volume") || force) { + swgDABDemodSettings->setVolume(settings.m_volume); + } + if (channelSettingsKeys.contains("audioMute") || force) { + swgDABDemodSettings->setAudioMute(settings.m_audioMute); + } + if (channelSettingsKeys.contains("audioDeviceName") || force) { + swgDABDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgDABDemodSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgDABDemodSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("streamIndex") || force) { + swgDABDemodSettings->setStreamIndex(settings.m_streamIndex); + } +} + +void DABDemod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "DABDemod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("DABDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/channelrx/demoddab/dabdemod.h b/plugins/channelrx/demoddab/dabdemod.h new file mode 100644 index 000000000..aa627e356 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemod.h @@ -0,0 +1,375 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 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_DABDEMOD_H +#define INCLUDE_DABDEMOD_H + +#include + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelapi.h" +#include "util/message.h" + +#include "dabdemodbaseband.h" +#include "dabdemodsettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class DeviceAPI; + +class DABDemod : public BasebandSampleSink, public ChannelAPI { + Q_OBJECT +public: + class MsgConfigureDABDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DABDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDABDemod* create(const DABDemodSettings& settings, bool force) + { + return new MsgConfigureDABDemod(settings, force); + } + + private: + DABDemodSettings m_settings; + bool m_force; + + MsgConfigureDABDemod(const DABDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgDABEnsembleName : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString getName() const { return m_name; } + int getId() const { return m_id; } + + static MsgDABEnsembleName* create(const QString& name, int id) + { + return new MsgDABEnsembleName(name, id); + } + + private: + QString m_name; + int m_id; + + MsgDABEnsembleName(const QString& name, int id) : + Message(), + m_name(name), + m_id(id) + { } + }; + + class MsgDABProgramName : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString getName() const { return m_name; } + int getId() const { return m_id; } + + static MsgDABProgramName* create(const QString& name, int id) + { + return new MsgDABProgramName(name, id); + } + + private: + QString m_name; + int m_id; + + MsgDABProgramName(const QString& name, int id) : + Message(), + m_name(name), + m_id(id) + { } + }; + + class MsgDABProgramData: public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getBitrate() const { return m_bitrate; } + const QString getAudio() const { return m_audio; } + const QString getLanguage() const { return m_language; } + const QString getProgramType() const { return m_programType; } + + + static MsgDABProgramData* create(int bitrate, const QString& audio, const QString& language, const QString& programType) + { + return new MsgDABProgramData(bitrate, audio, language, programType); + } + + private: + int m_bitrate; + QString m_audio; + QString m_language; + QString m_programType; + + MsgDABProgramData(int bitrate, const QString& audio, const QString& language, const QString& programType) : + Message(), + m_bitrate(bitrate), + m_audio(audio), + m_language(language), + m_programType(programType) + { } + }; + + class MsgDABSystemData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getSync() const { return m_sync; } + int16_t getSNR() const { return m_snr; } + int32_t getFrequencyOffset() const { return m_frequencyOffset; } + + static MsgDABSystemData* create(bool sync, int16_t snr, int32_t frequencyOffset) + { + return new MsgDABSystemData(sync, snr, frequencyOffset); + } + + private: + bool m_sync; + int16_t m_snr; + int32_t m_frequencyOffset; + + MsgDABSystemData(bool sync, int16_t snr, int32_t frequencyOffset) : + Message(), + m_sync(sync), + m_snr(snr), + m_frequencyOffset(frequencyOffset) + { } + }; + + class MsgDABProgramQuality : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getFrames() const { return m_frames; } + int getRS() const { return m_rs; } + int getAAC() const { return m_aac; } + + static MsgDABProgramQuality* create(int frames, int rs, int aac) + { + return new MsgDABProgramQuality(frames, rs, aac); + } + + private: + int m_frames; + int m_rs; + int m_aac; + + MsgDABProgramQuality(int frames, int rs, int aac) : + Message(), + m_frames(frames), + m_rs(rs), + m_aac(aac) + { } + }; + + class MsgDABFIBQuality : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getPercent() const { return m_percent; } + + static MsgDABFIBQuality* create(int percent) + { + return new MsgDABFIBQuality(percent); + } + + private: + int m_percent; + + MsgDABFIBQuality(int percent) : + Message(), + m_percent(percent) + { } + }; + + class MsgDABSampleRate : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + + static MsgDABSampleRate* create(int sampleRate) + { + return new MsgDABSampleRate(sampleRate); + } + + private: + int m_sampleRate; + + MsgDABSampleRate(int sampleRate) : + Message(), + m_sampleRate(sampleRate) + { } + }; + + class MsgDABData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString getData() const { return m_data; } + + static MsgDABData* create(const QString& data) + { + return new MsgDABData(data); + } + + private: + QString m_data; + + MsgDABData(const QString& data) : + Message(), + m_data(data) + { } + }; + + class MsgDABReset : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgDABReset* create() + { + return new MsgDABReset(); + } + + private: + + MsgDABReset() : + Message() + { } + }; + + class MsgDABResetService : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgDABResetService* create() + { + return new MsgDABResetService(); + } + + private: + + MsgDABResetService() : + Message() + { } + }; + + + DABDemod(DeviceAPI *deviceAPI); + virtual ~DABDemod(); + virtual void destroy() { delete this; } + + 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 bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual const QString& getURI() const { return getName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return 0; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int getNbSinkStreams() const { return 1; } + virtual int getNbSourceStreams() const { return 0; } + + virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const + { + (void) streamIndex; + (void) sinkElseSource; + return 0; + } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + static void webapiFormatChannelSettings( + SWGSDRangel::SWGChannelSettings& response, + const DABDemodSettings& settings); + + static void webapiUpdateChannelSettings( + DABDemodSettings& settings, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response); + + double getMagSq() const { return m_basebandSink->getMagSq(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_basebandSink->getMagSqLevels(avg, peak, nbSamples); + } +/* void setMessageQueueToGUI(MessageQueue* queue) override { + ChannelAPI::setMessageQueueToGUI(queue); + m_basebandSink->setMessageQueueToGUI(queue); + }*/ + + uint32_t getNumberOfDeviceStreams() const; + + static const char * const m_channelIdURI; + static const char * const m_channelId; + +private: + DeviceAPI *m_deviceAPI; + QThread m_thread; + DABDemodBaseband* m_basebandSink; + DABDemodSettings m_settings; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink + qint64 m_centerFrequency; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const DABDemodSettings& settings, bool force = false); + void webapiReverseSendSettings(QList& channelSettingsKeys, const DABDemodSettings& settings, bool force); + void webapiFormatChannelSettings( + QList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings *swgChannelSettings, + const DABDemodSettings& settings, + bool force + ); + +private slots: + void networkManagerFinished(QNetworkReply *reply); + +}; + +#endif // INCLUDE_DABDEMOD_H diff --git a/plugins/channelrx/demoddab/dabdemodbaseband.cpp b/plugins/channelrx/demoddab/dabdemodbaseband.cpp new file mode 100644 index 000000000..0a6d227f4 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodbaseband.cpp @@ -0,0 +1,203 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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 "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downchannelizer.h" + +#include "dabdemodbaseband.h" +#include "dabdemod.h" + +MESSAGE_CLASS_DEFINITION(DABDemodBaseband::MsgConfigureDABDemodBaseband, Message) + +DABDemodBaseband::DABDemodBaseband(DABDemod *packetDemod) : + m_sink(packetDemod), + m_running(false), + m_mutex(QMutex::Recursive) +{ + qDebug("DABDemodBaseband::DABDemodBaseband"); + + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(1000000)); + m_channelizer = new DownChannelizer(&m_sink); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); +} + +DABDemodBaseband::~DABDemodBaseband() +{ + m_inputMessageQueue.clear(); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + delete m_channelizer; +} + +void DABDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_inputMessageQueue.clear(); + m_sampleFifo.reset(); +} + +void DABDemodBaseband::startWork() +{ + QMutexLocker mutexLocker(&m_mutex); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &DABDemodBaseband::handleData, + Qt::QueuedConnection + ); + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + m_running = true; +} + +void DABDemodBaseband::stopWork() +{ + QMutexLocker mutexLocker(&m_mutex); + disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + QObject::disconnect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &DABDemodBaseband::handleData + ); + m_running = false; +} + +void DABDemodBaseband::setChannel(ChannelAPI *channel) +{ + m_sink.setChannel(channel); +} + +void DABDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void DABDemodBaseband::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 DABDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool DABDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureDABDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureDABDemodBaseband& cfg = (MsgConfigureDABDemodBaseband&) cmd; + qDebug() << "DABDemodBaseband::handleMessage: MsgConfigureDABDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "DABDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + setBasebandSampleRate(notif.getSampleRate()); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + + return true; + } + else if (DABDemod::MsgDABReset::match(cmd)) + { + m_sink.reset(); + return true; + } + else if (DABDemod::MsgDABResetService::match(cmd)) + { + m_sink.resetService(); + return true; + } + else + { + return false; + } +} + +void DABDemodBaseband::applySettings(const DABDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(DABDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->removeAudioSink(m_sink.getAudioFifo()); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); + int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_sink.getAudioSampleRate() != audioSampleRate) + { + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + m_sink.applyAudioSampleRate(audioSampleRate); + } + } + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +void DABDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); +} diff --git a/plugins/channelrx/demoddab/dabdemodbaseband.h b/plugins/channelrx/demoddab/dabdemodbaseband.h new file mode 100644 index 000000000..983574691 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodbaseband.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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_DABDEMODBASEBAND_H +#define INCLUDE_DABDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "dabdemodsink.h" + +class DownChannelizer; +class ChannelAPI; +class DABDemod; + +class DABDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureDABDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DABDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDABDemodBaseband* create(const DABDemodSettings& settings, bool force) + { + return new MsgConfigureDABDemodBaseband(settings, force); + } + + private: + DABDemodSettings m_settings; + bool m_force; + + MsgConfigureDABDemodBaseband(const DABDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + DABDemodBaseband(DABDemod *packetDemod); + ~DABDemodBaseband(); + void reset(); + void startWork(); + void stopWork(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_sink.getMagSqLevels(avg, peak, nbSamples); + } + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); } + void setBasebandSampleRate(int sampleRate); + void setChannel(ChannelAPI *channel); + double getMagSq() const { return m_sink.getMagSq(); } + bool isRunning() const { return m_running; } + +private: + SampleSinkFifo m_sampleFifo; + DownChannelizer *m_channelizer; + DABDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + DABDemodSettings m_settings; + bool m_running; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const DABDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_DABDEMODBASEBAND_H diff --git a/plugins/channelrx/demoddab/dabdemoddevice.cpp b/plugins/channelrx/demoddab/dabdemoddevice.cpp new file mode 100644 index 000000000..713260113 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemoddevice.cpp @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2010, 2011, 2012 Jan van Katwijk (J.vanKatwijk@gmail.com) +// Copyright (C) 2021 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 "dabdemoddevice.h" + +// Implementation of default device, as not included in library + +deviceHandler::deviceHandler(void) +{ + lastFrequency = 100000; +} + +deviceHandler::~deviceHandler(void) +{ +} + +bool deviceHandler::restartReader(int32_t) +{ + return true; +} + +void deviceHandler::stopReader(void) +{ +} + +void deviceHandler::run(void) +{ +} + +int32_t deviceHandler::getSamples(std::complex *v, int32_t amount) +{ + (void)v; + (void)amount; + return 0; +} + +int32_t deviceHandler::Samples(void) +{ + return 0; +} + +int32_t deviceHandler::defaultFrequency(void) +{ + return 220000000; +} + +void deviceHandler::resetBuffer(void) +{ +} + +void deviceHandler::setGain(int32_t x) +{ + (void)x; +} + +bool deviceHandler::has_autogain(void) +{ + return false; +} + +void deviceHandler::set_autogain(bool b) +{ + (void)b; +} + +void deviceHandler::set_ifgainReduction(int x) +{ + (void)x; +} + +void deviceHandler::set_lnaState(int x) +{ + (void)x; +} + +// Device with source data from SDRangel +// Other methods not needed for DAB library + +DABDemodDevice::DABDemodDevice() : + m_iqBuffer (4 * 1024 * 1024) +{ +} + +void DABDemodDevice::putSample(std::complex s) +{ + std::complex localBuf[1]; + localBuf[0] = s; + if (m_iqBuffer.GetRingBufferWriteAvailable() > 0) + m_iqBuffer.putDataIntoBuffer(localBuf, 1); +} + +int32_t DABDemodDevice::getSamples(std::complex *buf, int32_t size) +{ + return m_iqBuffer.getDataFromBuffer(buf, size); +} + +int32_t DABDemodDevice::Samples(void) +{ + return m_iqBuffer.GetRingBufferReadAvailable(); +} + +void DABDemodDevice::resetBuffer(void) +{ + m_iqBuffer.FlushRingBuffer(); +} diff --git a/plugins/channelrx/demoddab/dabdemoddevice.h b/plugins/channelrx/demoddab/dabdemoddevice.h new file mode 100644 index 000000000..d375f99a9 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemoddevice.h @@ -0,0 +1,38 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 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_DABDEMODDEVICE_H +#define INCLUDE_DABDEMODDEVICE_H + +#include + +class DABDemodDevice : public deviceHandler { + +public: + + DABDemodDevice(); + void putSample(std::complex s); + int32_t getSamples(std::complex *buf, int32_t size) override; + int32_t Samples(void) override; + void resetBuffer(void) override; + +private: + RingBuffer> m_iqBuffer; + +}; + +#endif // INCLUDE_DABDEMODDEVICE_H diff --git a/plugins/channelrx/demoddab/dabdemodgui.cpp b/plugins/channelrx/demoddab/dabdemodgui.cpp new file mode 100644 index 000000000..c1b615f01 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodgui.cpp @@ -0,0 +1,651 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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 + +#include +#include +#include + +#include "dabdemodgui.h" + +#include "device/deviceuiset.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "ui_dabdemodgui.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "gui/audioselectdialog.h" +#include "gui/basicchannelsettingsdialog.h" +#include "gui/devicestreamselectiondialog.h" +#include "dsp/dspengine.h" +#include "gui/crightclickenabler.h" +#include "channel/channelwebapiutils.h" +#include "maincore.h" + +#include "dabdemod.h" +#include "dabdemodsink.h" + +// Table column indexes +#define PROGRAMS_COL_NAME 0 +#define PROGRAMS_COL_ID 1 +#define PROGRAMS_COL_FREQUENCY 2 + +void DABDemodGUI::resizeTable() +{ + // Fill table with a row of dummy data that will size the columns nicely + // Trailing spaces are for sort arrow + int row = ui->programs->rowCount(); + ui->programs->setRowCount(row + 1); + ui->programs->setItem(row, PROGRAMS_COL_NAME, new QTableWidgetItem("Some Random Radio Station")); + ui->programs->setItem(row, PROGRAMS_COL_ID, new QTableWidgetItem("123456")); + ui->programs->setItem(row, PROGRAMS_COL_FREQUENCY, new QTableWidgetItem("200.000")); + ui->programs->resizeColumnsToContents(); + ui->programs->removeRow(row); +} + +// Columns in table reordered +void DABDemodGUI::programs_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex) +{ + (void) oldVisualIndex; + + m_settings.m_columnIndexes[logicalIndex] = newVisualIndex; +} + +// Column in table resized (when hidden size is 0) +void DABDemodGUI::programs_sectionResized(int logicalIndex, int oldSize, int newSize) +{ + (void) oldSize; + + m_settings.m_columnSizes[logicalIndex] = newSize; +} + +// Right click in table header - show column select menu +void DABDemodGUI::columnSelectMenu(QPoint pos) +{ + menu->popup(ui->programs->horizontalHeader()->viewport()->mapToGlobal(pos)); +} + +// Hide/show column when menu selected +void DABDemodGUI::columnSelectMenuChecked(bool checked) +{ + (void) checked; + + QAction* action = qobject_cast(sender()); + if (action != nullptr) + { + int idx = action->data().toInt(nullptr); + ui->programs->setColumnHidden(idx, !action->isChecked()); + } +} + +// Create column select menu item +QAction *DABDemodGUI::createCheckableItem(QString &text, int idx, bool checked) +{ + QAction *action = new QAction(text, this); + action->setCheckable(true); + action->setChecked(checked); + action->setData(QVariant(idx)); + connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked())); + return action; +} + +DABDemodGUI* DABDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + DABDemodGUI* gui = new DABDemodGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void DABDemodGUI::destroy() +{ + delete this; +} + +void DABDemodGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); +} + +QByteArray DABDemodGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool DABDemodGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + applySettings(true); + return true; + } else { + resetToDefaults(); + return false; + } +} + +// Add row to table +void DABDemodGUI::addProgramName(const DABDemod::MsgDABProgramName& program) +{ + ui->programs->setSortingEnabled(false); + int row = ui->programs->rowCount(); + ui->programs->setRowCount(row + 1); + + QTableWidgetItem *nameItem = new QTableWidgetItem(); + QTableWidgetItem *idItem = new QTableWidgetItem(); + QTableWidgetItem *frequencyItem = new QTableWidgetItem(); + ui->programs->setItem(row, PROGRAMS_COL_NAME, nameItem); + ui->programs->setItem(row, PROGRAMS_COL_ID, idItem); + ui->programs->setItem(row, PROGRAMS_COL_FREQUENCY, frequencyItem); + nameItem->setText(program.getName()); + idItem->setText(QString::number(program.getId())); + double frequencyInHz; + if (ChannelWebAPIUtils::getCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz)) + { + double frequencyInMHz = (frequencyInHz+m_settings.m_inputFrequencyOffset)/1e6; + frequencyItem->setText(QString::number(frequencyInMHz, 'f', 3)); + frequencyItem->setData(Qt::UserRole, frequencyInHz+m_settings.m_inputFrequencyOffset); + } + else + frequencyItem->setData(Qt::UserRole, 0.0); + ui->programs->setSortingEnabled(true); + filterRow(row); +} + +// Tune to the selected program +void DABDemodGUI::on_programs_cellDoubleClicked(int row, int column) +{ + (void) column; + + m_settings.m_program = ui->programs->item(row, PROGRAMS_COL_NAME)->text(); + + double frequencyInHz = ui->programs->item(row, PROGRAMS_COL_FREQUENCY)->data(Qt::UserRole).toDouble(); + ChannelWebAPIUtils::setCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz-m_settings.m_inputFrequencyOffset); + + applySettings(); +} + +bool DABDemodGUI::handleMessage(const Message& message) +{ + if (DABDemod::MsgConfigureDABDemod::match(message)) + { + qDebug("DABDemodGUI::handleMessage: DABDemod::MsgConfigureDABDemod"); + const DABDemod::MsgConfigureDABDemod& cfg = (DABDemod::MsgConfigureDABDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPSignalNotification::match(message)) + { + DSPSignalNotification& notif = (DSPSignalNotification&) message; + m_basebandSampleRate = notif.getSampleRate(); + return true; + } + else if (DABDemod::MsgDABEnsembleName::match(message)) + { + DABDemod::MsgDABEnsembleName& report = (DABDemod::MsgDABEnsembleName&) message; + ui->ensemble->setText(report.getName()); + return true; + } + else if (DABDemod::MsgDABProgramName::match(message)) + { + DABDemod::MsgDABProgramName& report = (DABDemod::MsgDABProgramName&) message; + addProgramName(report); + return true; + } + else if (DABDemod::MsgDABProgramData::match(message)) + { + DABDemod::MsgDABProgramData& report = (DABDemod::MsgDABProgramData&) message; + ui->program->setText(m_settings.m_program); + ui->bitrate->setText(QString("%1kbps").arg(report.getBitrate())); + ui->audio->setText(report.getAudio()); + ui->language->setText(report.getLanguage()); + ui->programType->setText(report.getProgramType()); + return true; + } + else if (DABDemod::MsgDABSystemData::match(message)) + { + DABDemod::MsgDABSystemData& report = (DABDemod::MsgDABSystemData&) message; + if (report.getSync()) + ui->sync->setText("Yes"); + else + ui->sync->setText("No"); + ui->snr->setText(QString("%1").arg(report.getSNR())); + ui->freqOffset->setText(QString("%1").arg(report.getFrequencyOffset())); + return true; + } + else if (DABDemod::MsgDABProgramQuality::match(message)) + { + DABDemod::MsgDABProgramQuality& report = (DABDemod::MsgDABProgramQuality&) message; + ui->frames->setText(QString("%1%").arg(report.getFrames())); + ui->rs->setText(QString("%1%").arg(report.getRS())); + ui->aac->setText(QString("%1%").arg(report.getAAC())); + return true; + } + else if (DABDemod::MsgDABFIBQuality::match(message)) + { + DABDemod::MsgDABFIBQuality& report = (DABDemod::MsgDABFIBQuality&) message; + ui->fib->setText(QString("%1%").arg(report.getPercent())); + return true; + } + else if (DABDemod::MsgDABSampleRate::match(message)) + { + DABDemod::MsgDABSampleRate& report = (DABDemod::MsgDABSampleRate&) message; + qDebug() << "Ssample rate: " << report.getSampleRate(); + ui->sampleRate->setText(QString("%1k").arg(report.getSampleRate()/1000.0, 0, 'f', 0)); + return true; + } + else if (DABDemod::MsgDABData::match(message)) + { + DABDemod::MsgDABData& report = (DABDemod::MsgDABData&) message; + ui->data->setText(report.getData()); + return true; + } + + return false; +} + +void DABDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void DABDemodGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void DABDemodGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +void DABDemodGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void DABDemodGUI::on_audioMute_toggled(bool checked) +{ + m_settings.m_audioMute = checked; + applySettings(); +} + +void DABDemodGUI::on_volume_valueChanged(int value) +{ + ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + m_settings.m_volume = value / 10.0; + applySettings(); +} + +void DABDemodGUI::on_rfBW_valueChanged(int value) +{ + float bw = value * 100.0f; + ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1)); + m_channelMarker.setBandwidth(bw); + m_settings.m_rfBandwidth = bw; + applySettings(); +} + +void DABDemodGUI::on_filter_editingFinished() +{ + m_settings.m_filter = ui->filter->text(); + filter(); + applySettings(); +} + +void DABDemodGUI::on_clearTable_clicked() +{ + ui->programs->setRowCount(0); + // Reset the DAB library, so it re-outputs program names + DABDemod::MsgDABReset* message = DABDemod::MsgDABReset::create(); + m_dabDemod->getInputMessageQueue()->push(message); +} + +void DABDemodGUI::filterRow(int row) +{ + bool hidden = false; + if (m_settings.m_filter != "") + { + QRegExp re(m_settings.m_filter); + QTableWidgetItem *fromItem = ui->programs->item(row, PROGRAMS_COL_NAME); + if (re.indexIn(fromItem->text()) == -1) + hidden = true; + } + ui->programs->setRowHidden(row, hidden); +} + +void DABDemodGUI::filter() +{ + for (int i = 0; i < ui->programs->rowCount(); i++) + { + filterRow(i); + } +} + +void DABDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +void DABDemodGUI::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.move(p); + 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); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); + } + else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine)) + { + DeviceStreamSelectionDialog dialog(this); + dialog.setNumberOfStreams(m_dabDemod->getNumberOfDeviceStreams()); + dialog.setStreamIndex(m_settings.m_streamIndex); + dialog.move(p); + dialog.exec(); + + m_settings.m_streamIndex = dialog.getSelectedStreamIndex(); + m_channelMarker.clearStreamIndexes(); + m_channelMarker.addStreamIndex(m_settings.m_streamIndex); + displayStreamIndex(); + applySettings(); + } + + resetContextMenuType(); +} + +DABDemodGUI::DABDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + ChannelGUI(parent), + ui(new Ui::DABDemodGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_tickCount(0), + m_channelFreq(0.0) +{ + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_dabDemod = reinterpret_cast(rxChannel); + m_dabDemod->setMessageQueueToGUI(getInputMessageQueue()); + + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); + + connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms + + 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); + + m_channelMarker.blockSignals(true); + m_channelMarker.setColor(Qt::yellow); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle("Packet 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_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + // Resize the table using dummy data + resizeTable(); + // Allow user to reorder columns + ui->programs->horizontalHeader()->setSectionsMovable(true); + // Allow user to sort table by clicking on headers + ui->programs->setSortingEnabled(true); + // Add context menu to allow hiding/showing of columns + menu = new QMenu(ui->programs); + for (int i = 0; i < ui->programs->horizontalHeader()->count(); i++) + { + QString text = ui->programs->horizontalHeaderItem(i)->text(); + menu->addAction(createCheckableItem(text, i, true)); + } + ui->programs->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->programs->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint))); + // Get signals when columns change + connect(ui->programs->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(programs_sectionMoved(int, int, int))); + connect(ui->programs->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(programs_sectionResized(int, int, int))); + + displaySettings(); + applySettings(true); +} + +DABDemodGUI::~DABDemodGUI() +{ + delete ui; +} + +void DABDemodGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void DABDemodGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + DABDemod::MsgConfigureDABDemod* message = DABDemod::MsgConfigureDABDemod::create( m_settings, force); + m_dabDemod->getInputMessageQueue()->push(message); + } +} + +void DABDemodGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setTitle(m_settings.m_title); + 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()); + + blockApplySettings(true); + + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + ui->audioMute->setChecked(m_settings.m_audioMute); + + ui->volume->setValue(m_settings.m_volume * 10.0); + ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 0, 'f', 1)); + + ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1)); + ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0); + + displayStreamIndex(); + + ui->filter->setText(m_settings.m_filter); + + // Order and size columns + QHeaderView *header = ui->programs->horizontalHeader(); + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + { + bool hidden = m_settings.m_columnSizes[i] == 0; + header->setSectionHidden(i, hidden); + menu->actions().at(i)->setChecked(!hidden); + if (m_settings.m_columnSizes[i] > 0) + ui->programs->setColumnWidth(i, m_settings.m_columnSizes[i]); + header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]); + } + + filter(); + + blockApplySettings(false); +} + +void DABDemodGUI::displayStreamIndex() +{ + if (m_deviceUISet->m_deviceMIMOEngine) { + setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex)); + } else { + setStreamIndicator("S"); // single channel indicator + } +} + +void DABDemodGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void DABDemodGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void DABDemodGUI::resetService() +{ + // Reset DAB audio service, to avoid unpleasent noise when changing frequency + DABDemod::MsgDABResetService* message = DABDemod::MsgDABResetService::create(); + m_dabDemod->getInputMessageQueue()->push(message); + // Clear current program + ui->program->setText("-"); + ui->ensemble->setText("-"); + ui->programType->setText("-"); + ui->language->setText("-"); + ui->audio->setText("-"); + ui->bitrate->setText("-"); + ui->sampleRate->setText("-"); + ui->data->setText(""); +} + +void DABDemodGUI::on_channel_currentIndexChanged(int index) +{ + (void) index; + + QString text = ui->channel->currentText(); + if (!text.isEmpty()) + { + resetService(); + // Tune to requested channel + QString freq = text.split(" ")[2]; + m_channelFreq = freq.toDouble() * 1e6; + ChannelWebAPIUtils::setCenterFrequency(m_dabDemod->getDeviceSetIndex(), m_channelFreq - m_settings.m_inputFrequencyOffset); + } +} + +void DABDemodGUI::audioSelect() +{ + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + +void DABDemodGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_dabDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + ui->channelPowerMeter->levelChanged( + (100.0f + powDbAvg) / 100.0f, + (100.0f + powDbPeak) / 100.0f, + nbMagsqSamples); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); + } + + // Update channel combobox according to current frequency + double frequencyInHz; + if (ChannelWebAPIUtils::getCenterFrequency(m_dabDemod->getDeviceSetIndex(), frequencyInHz)) + { + frequencyInHz += m_settings.m_inputFrequencyOffset; + double frequencyInkHz = std::round(frequencyInHz / 1e3); + double frequencyInMHz = frequencyInkHz / 1000.0; + + if (m_channelFreq != frequencyInMHz * 1e6) + { + QString freqText = QString::number(frequencyInMHz, 'f', 3); + int i; + for (i = 0; i < ui->channel->count(); i++) + { + if (ui->channel->itemText(i).contains(freqText)) + { + ui->channel->blockSignals(true); + ui->channel->setCurrentIndex(i); + ui->channel->blockSignals(false); + break; + } + } + if (i == ui->channel->count()) + { + ui->channel->setCurrentIndex(-1); + m_channelFreq = -1.0; + } + } + } + + m_tickCount++; +} diff --git a/plugins/channelrx/demoddab/dabdemodgui.h b/plugins/channelrx/demoddab/dabdemodgui.h new file mode 100644 index 000000000..79be7765a --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodgui.h @@ -0,0 +1,113 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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_DABDEMODGUI_H +#define INCLUDE_DABDEMODGUI_H + +#include +#include +#include + +#include "channel/channelgui.h" +#include "dsp/channelmarker.h" +#include "dsp/movingaverage.h" +#include "util/messagequeue.h" +#include "dabdemodsettings.h" +#include "dabdemod.h" + +class PluginAPI; +class DeviceUISet; +class BasebandSampleSink; +class DABDemod; +class DABDemodGUI; + +namespace Ui { + class DABDemodGUI; +} +class DABDemodGUI; + +class DABDemodGUI : public ChannelGUI { + Q_OBJECT + +public: + static DABDemodGUI* 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; } + +public slots: + void channelMarkerChangedByCursor(); + void channelMarkerHighlightedByCursor(); + +private: + Ui::DABDemodGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + DABDemodSettings m_settings; + bool m_doApplySettings; + + DABDemod* m_dabDemod; + int m_basebandSampleRate; + uint32_t m_tickCount; + MessageQueue m_inputMessageQueue; + double m_channelFreq; + + QMenu *menu; // Column select context menu + + explicit DABDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~DABDemodGUI(); + + void blockApplySettings(bool block); + void applySettings(bool force = false); + void displaySettings(); + void displayStreamIndex(); + void addProgramName(const DABDemod::MsgDABProgramName& program); + bool handleMessage(const Message& message); + void audioSelect(); + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + void resetService(); + void resizeTable(); + QAction *createCheckableItem(QString& text, int idx, bool checked); + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_audioMute_toggled(bool checked); + void on_volume_valueChanged(int value); + void on_rfBW_valueChanged(int index); + void on_filter_editingFinished(); + void on_clearTable_clicked(); + void on_programs_cellDoubleClicked(int row, int column); + void on_channel_currentIndexChanged(int index); + void filterRow(int row); + void filter(); + void programs_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); + void programs_sectionResized(int logicalIndex, int oldSize, int newSize); + void columnSelectMenu(QPoint pos); + void columnSelectMenuChecked(bool checked = false); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void tick(); +}; + +#endif // INCLUDE_DABDEMODGUI_H diff --git a/plugins/channelrx/demoddab/dabdemodgui.ui b/plugins/channelrx/demoddab/dabdemodgui.ui new file mode 100644 index 000000000..d37e01596 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodgui.ui @@ -0,0 +1,1017 @@ + + + DABDemodGUI + + + + 0 + 0 + 398 + 612 + + + + + 0 + 0 + + + + + 352 + 0 + + + + + Liberation Sans + 9 + + + + Qt::StrongFocus + + + DAB Demodulator + + + DAB Demodulator + + + + + 0 + 0 + 390 + 131 + + + + + 350 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Channel power + + + Qt::RightToLeft + + + 0.0 + + + + + + + dB + + + + + + + + + Left: Mute/Unmute audio Right: view/select audio device + + + ... + + + + :/sound_on.png + :/sound_off.png:/sound_on.png + + + true + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + RF level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold + + + + + + + + + + + Channel + + + + + + + + 100 + 0 + + + + DAB channel number and frequency in MHz + + + + 5A - 174.928 + + + + + 5B - 176.640 + + + + + 5C - 178.352 + + + + + 5D - 180.064 + + + + + 6A - 181.936 + + + + + 6B - 183.648 + + + + + 6C - 185.360 + + + + + 6D - 187.072 + + + + + 7A - 188.928 + + + + + 7B - 190.640 + + + + + 7C - 192.352 + + + + + 7D - 194.064 + + + + + 8A - 195.936 + + + + + 8B - 197.648 + + + + + 8C - 199.360 + + + + + 8D - 201.072 + + + + + 9A - 202.928 + + + + + 9B - 204.640 + + + + + 9C - 206.352 + + + + + 9D - 208.064 + + + + + 10A - 209.936 + + + + + 10B - 211.648 + + + + + 10C - 213.360 + + + + + 10D - 215.072 + + + + + 11A - 216.928 + + + + + 11B - 218.640 + + + + + 11C - 220.352 + + + + + 11D - 222.064 + + + + + 12A - 223.936 + + + + + 12B - 225.648 + + + + + 12C - 227.360 + + + + + 12D - 229.072 + + + + + 13A - 230.784 + + + + + 13B - 232.208 + + + + + 13C - 234.208 + + + + + 13D - 235.776 + + + + + 13E - 237.448 + + + + + 13F - 239.200 + + + + + + + + Qt::Vertical + + + + + + + BW + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + RF bandwidth + + + 10000 + + + 40000 + + + 1 + + + 16000 + + + Qt::Horizontal + + + + + + + + 30 + 0 + + + + 10.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Vol + + + + + + + Volume + + + 100 + + + 1 + + + 20 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + 2.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Find + + + + + + + Display only programs that match the specified regular expression + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Clear programs from table + + + + + + + :/bin.png:/bin.png + + + + + + + + + + + 0 + 140 + 381 + 211 + + + + + 0 + 0 + + + + Programs + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + Programs + + + QAbstractItemView::NoEditTriggers + + + + Program + + + + + ID + + + + + Frequency + + + + + + + + + + 0 + 380 + 381 + 101 + + + + + 0 + 0 + + + + Current Program + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Language: + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + Program Type: + + + + + + + Audio: + + + + + + + - + + + + + + + - + + + + + + + Ensemble: + + + + + + + Bitrate: + + + + + + + - + + + + + + + Program: + + + + + + + Sample rate: + + + + + + + Audio sample rate (in Sa/s) + + + - + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 0 + 500 + 381 + 91 + + + + + 0 + 0 + + + + Statistics + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + + Frequency offset in Hz + + + - + + + + + + + Frames: + + + + + + + SNR: + + + + + + + Freq Offset: + + + + + + + Time synchronized + + + - + + + + + + + Percentage of valid frames + + + - + + + + + + + Sync: + + + + + + + Signal to noise ratio + + + - + + + + + + + RS: + + + + + + + For DAB+, percentage of packages passing Reed Solomon correction + + + - + + + + + + + AAC: + + + + + + + For DAB+, percentage of valid AAC frames + + + - + + + + + + + FIB: + + + + + + + Percent of FIB packages with valid CRC + + + - + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+
+ + programs + + + + + +
diff --git a/plugins/channelrx/demoddab/dabdemodplugin.cpp b/plugins/channelrx/demoddab/dabdemodplugin.cpp new file mode 100644 index 000000000..a33bc7417 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodplugin.cpp @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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 "plugin/pluginapi.h" + +#ifndef SERVER_MODE +#include "dabdemodgui.h" +#endif +#include "dabdemod.h" +#include "dabdemodwebapiadapter.h" +#include "dabdemodplugin.h" + +const PluginDescriptor DABDemodPlugin::m_pluginDescriptor = { + DABDemod::m_channelId, + QStringLiteral("DAB Demodulator"), + QStringLiteral("6.9.0"), + QStringLiteral("(c) Jon Beniston, M7RCE. DAB library by Jvan Katwijk"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +DABDemodPlugin::DABDemodPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& DABDemodPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void DABDemodPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerRxChannel(DABDemod::m_channelIdURI, DABDemod::m_channelId, this); +} + +void DABDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const +{ + if (bs || cs) + { + DABDemod *instance = new DABDemod(deviceAPI); + + if (bs) { + *bs = instance; + } + + if (cs) { + *cs = instance; + } + } +} + +#ifdef SERVER_MODE +ChannelGUI* DABDemodPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet, + BasebandSampleSink *rxChannel) const +{ + (void) deviceUISet; + (void) rxChannel; + return 0; +} +#else +ChannelGUI* DABDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const +{ + return DABDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +ChannelWebAPIAdapter* DABDemodPlugin::createChannelWebAPIAdapter() const +{ + return new DABDemodWebAPIAdapter(); +} diff --git a/plugins/channelrx/demoddab/dabdemodplugin.h b/plugins/channelrx/demoddab/dabdemodplugin.h new file mode 100644 index 000000000..736e70dac --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodplugin.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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_DABDEMODPLUGIN_H +#define INCLUDE_DABDEMODPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class DABDemodPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.dabdemod") + +public: + explicit DABDemodPlugin(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_DABDEMODPLUGIN_H diff --git a/plugins/channelrx/demoddab/dabdemodsettings.cpp b/plugins/channelrx/demoddab/dabdemodsettings.cpp new file mode 100644 index 000000000..be43e0db0 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodsettings.cpp @@ -0,0 +1,149 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 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 "dsp/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "dabdemodsettings.h" + +DABDemodSettings::DABDemodSettings() : + m_channelMarker(0) +{ + resetToDefaults(); +} + +void DABDemodSettings::resetToDefaults() +{ + m_inputFrequencyOffset = 0; + m_rfBandwidth = 1537000.0f; + m_filter = ""; + m_volume = 5.0f; + m_audioMute = false; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + + m_rgbColor = QColor(77, 105, 25).rgb(); + m_title = "DAB Demodulator"; + m_streamIndex = 0; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; + + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + { + m_columnIndexes[i] = i; + m_columnSizes[i] = -1; // Autosize + } +} + +QByteArray DABDemodSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, m_streamIndex); + s.writeString(3, m_filter); + s.writeFloat(4, m_rfBandwidth); + s.writeFloat(5, m_volume); + s.writeBool(6, m_audioMute); + s.writeString(7, m_audioDeviceName); + + if (m_channelMarker) { + s.writeBlob(8, m_channelMarker->serialize()); + } + + s.writeU32(9, m_rgbColor); + s.writeString(10, m_title); + s.writeBool(11, m_useReverseAPI); + s.writeString(12, m_reverseAPIAddress); + s.writeU32(13, m_reverseAPIPort); + s.writeU32(14, m_reverseAPIDeviceIndex); + s.writeU32(15, m_reverseAPIChannelIndex); + + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + s.writeS32(100 + i, m_columnIndexes[i]); + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + s.writeS32(200 + i, m_columnSizes[i]); + + return s.final(); +} + +bool DABDemodSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + uint32_t utmp; + QString strtmp; + + d.readS32(1, &m_inputFrequencyOffset, 0); + d.readS32(2, &m_streamIndex, 0); + d.readString(3, &m_filter, ""); + d.readFloat(4, &m_rfBandwidth, 1537000.0f); + d.readFloat(5, &m_volume, 5.0f); + d.readBool(6, &m_audioMute, false); + d.readString(7, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + + d.readBlob(8, &bytetmp); + + if (m_channelMarker) { + m_channelMarker->deserialize(bytetmp); + } + + d.readU32(9, &m_rgbColor, QColor(77, 105, 25).rgb()); + d.readString(10, &m_title, "DAB Demodulator"); + d.readBool(11, &m_useReverseAPI, false); + d.readString(12, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(13, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(14, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(15, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + d.readS32(100 + i, &m_columnIndexes[i], i); + for (int i = 0; i < DABDEMOD_COLUMNS; i++) + d.readS32(200 + i, &m_columnSizes[i], -1); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + + diff --git a/plugins/channelrx/demoddab/dabdemodsettings.h b/plugins/channelrx/demoddab/dabdemodsettings.h new file mode 100644 index 000000000..6cd7fc173 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodsettings.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 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_DABDEMODSETTINGS_H +#define INCLUDE_DABDEMODSETTINGS_H + +#include + +class Serializable; + +// Number of columns in the table +#define DABDEMOD_COLUMNS 3 + +struct DABDemodSettings +{ + qint32 m_inputFrequencyOffset; + Real m_rfBandwidth; + QString m_filter; + QString m_program; + Real m_volume; + bool m_audioMute; + + quint32 m_rgbColor; + QString m_title; + Serializable *m_channelMarker; + 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_columnIndexes[DABDEMOD_COLUMNS];//!< How the columns are ordered in the table + int m_columnSizes[DABDEMOD_COLUMNS]; //!< Size of the columns in the table + + DABDemodSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); +}; + +#endif /* INCLUDE_DABDEMODSETTINGS_H */ diff --git a/plugins/channelrx/demoddab/dabdemodsink.cpp b/plugins/channelrx/demoddab/dabdemodsink.cpp new file mode 100644 index 000000000..4f34dd7e9 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodsink.cpp @@ -0,0 +1,644 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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 + +#include "dsp/dspengine.h" +#include "dsp/datafifo.h" +#include "util/db.h" +#include "pipes/pipeendpoint.h" +#include "maincore.h" + +#include "dabdemod.h" +#include "dabdemodsink.h" + +// Callbacks from DAB library + +void syncHandler(bool value, void *ctx) +{ + (void)value; + (void)ctx; +} + +void systemDataHandler(bool sync, int16_t snr, int32_t freqOffset, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->systemData(sync, snr, freqOffset); +} + +void ensembleNameHandler(std::string name, int32_t id, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->ensembleName(QString::fromStdString(name), id); +} + +void programNameHandler(std::string name, int32_t id, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->programName(QString::fromStdString(name), id); +} + +void fibQualityHandler(int16_t percent, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->fibQuality(percent); +} + +void audioHandler(int16_t *buffer, int size, int samplerate, bool stereo, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->audio(buffer, size, samplerate, stereo); +} + +void dataHandler(std::string data, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->data(QString::fromStdString(data)); +} + +void byteHandler(uint8_t *data, int16_t a, uint8_t b, void *ctx) +{ + (void)data; + (void)a; + (void)b; + (void)ctx; +} + +// Note: North America has different table +static const char *dabProgramType[] = +{ + "No programme type", + "News", + "Current Affairs", + "Information", + "Sport", + "Education", + "Drama", + "Culture", + "Science", + "Varied", + "Pop Music", + "Rock Music", + "Easy Listening Music", + "Light Classical", + "Serious Classical", + "Other Music", + "Weather/meteorology", + "Finance/Business", + "Children's programmes", + "Social Affairs", + "Religion", + "Phone In", + "Travel", + "Leisure", + "Jazz Music", + "Country Music", + "National Music", + "Oldies Music", + "Folk Music", + "Documentary", + "Not used", + "Not used", +}; + +static const char *dabLanguageCode[] = +{ + "Unknown", + "Albanian", + "Breton", + "Catalan", + "Croatian", + "Welsh", + "Czech", + "Danish", + "German", + "English", + "Spanish", + "Esperanto", + "Estonian", + "Basque", + "Faroese", + "French", + "Frisian", + "Irish", + "Gaelic", + "Galician", + "Icelandic", + "Italian", + "Sami", + "Latin", + "Latvian", + "Luxembourgian", + "Lithuanian", + "Hungarian", + "Maltese", + "Dutch", + "Norwegian", + "Occitan", + "Polish", + "Portuguese", + "Romanian", + "Romansh", + "Serbian", + "Slovak", + "Slovene", + "Finnish", + "Swedish", + "Turkish", + "Flemish", + "Walloon", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Background sound", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Zulu", + "Vietnamese", + "Uzbek", + "Urdu", + "Ukranian", + "Thai", + "Telugu", + "Tatar", + "Tamil", + "Tadzhik", + "Swahili", + "Sranan Tongo", + "Somali", + "Sinhalese", + "Shona", + "Serbo-Croat", + "Rusyn", + "Russian", + "Quechua", + "Pushtu", + "Punjabi", + "Persian", + "Papiamento", + "Oriya", + "Nepali", + "Ndebele", + "Marathi", + "Moldavian", + "Malaysian", + "Malagasay", + "Macedonian", + "Laotian", + "Korean", + "Khmer", + "Kazakh", + "Kannada", + "Japanese", + "Indonesian", + "Hindi", + "Hebrew", + "Hausa", + "Gurani", + "Gujurati", + "Greek", + "Georgian", + "Fulani", + "Dari", + "Chuvash", + "Chinese", + "Burmese", + "Bulgarian", + "Bengali", + "Belorussian", + "Bambora", + "Azerbaijani", + "Assamese", + "Armenian", + "Arabic", + "Amharic", +}; + +void programDataHandler(audiodata *data, void *ctx) +{ + QString audio; + if (data->ASCTy == 0) + audio = "DAB"; + else if (data->ASCTy == 63) + audio = "DAB+"; + else + audio = "Unknown"; + + QString language = ""; + if ((data->language < 0x80) && (data->language >= 0)) + language = dabLanguageCode[data->language & 0x7f]; + + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->programData(data->bitRate, audio, language, dabProgramType[data->programType & 0x1f]); +} + +void programQualityHandler(int16_t frames, int16_t rs, int16_t aac, void *ctx) +{ + DABDemodSink *sink = (DABDemodSink *)ctx; + sink->programQuality(frames, rs, aac); +} + +void motDataHandler(std::string data, int a, void *ctx) +{ + (void)data; + (void)ctx; +} + +void DABDemodSink::systemData(bool sync, int16_t snr, int32_t freqOffset) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABSystemData *msg = DABDemod::MsgDABSystemData::create(sync, snr, freqOffset); + getMessageQueueToChannel()->push(msg); + } +} +void DABDemodSink::ensembleName(const QString& name, int id) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABEnsembleName *msg = DABDemod::MsgDABEnsembleName::create(name, id); + getMessageQueueToChannel()->push(msg); + } +} + +void DABDemodSink::programName(const QString& name, int id) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABProgramName *msg = DABDemod::MsgDABProgramName::create(name, id); + getMessageQueueToChannel()->push(msg); + } +} + +void DABDemodSink::programData(int bitrate, const QString& audio, const QString& language, const QString& programType) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABProgramData *msg = DABDemod::MsgDABProgramData::create(bitrate, audio, language, programType); + getMessageQueueToChannel()->push(msg); + } +} + +void DABDemodSink::fibQuality(int16_t percent) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABFIBQuality *msg = DABDemod::MsgDABFIBQuality::create(percent); + getMessageQueueToChannel()->push(msg); + } +} + +void DABDemodSink::programQuality(int16_t frames, int16_t rs, int16_t aac) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABProgramQuality *msg = DABDemod::MsgDABProgramQuality::create(frames, rs, aac); + getMessageQueueToChannel()->push(msg); + } +} + +void DABDemodSink::data(const QString& data) +{ + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABData *msg = DABDemod::MsgDABData::create(data); + getMessageQueueToChannel()->push(msg); + } +} + +static int16_t scale(int16_t sample, float factor) +{ + int32_t prod = (int32_t)(((int32_t)sample) * factor); + prod = std::min(prod, 32767); + prod = std::max(prod, -32768); + return (int16_t)prod; +} + +void DABDemodSink::audio(int16_t *buffer, int size, int samplerate, bool stereo) +{ + (void)stereo; + (void)samplerate; + + if (samplerate != m_dabAudioSampleRate) + { + applyDABAudioSampleRate(samplerate); + if (getMessageQueueToChannel()) + { + DABDemod::MsgDABSampleRate *msg = DABDemod::MsgDABSampleRate::create(samplerate); + getMessageQueueToChannel()->push(msg); + } + } + + // buffer is always 2 channels + for (int i = 0; i < size; i+=2) + { + Complex ci, ca; + if (!m_settings.m_audioMute) + { + ci.real(buffer[i]); + ci.imag(buffer[i+1]); + } + else + { + ci.real(0.0f); + ci.imag(0.0f); + } + if (m_audioInterpolatorDistance < 1.0f) // interpolate + { + while (!m_audioInterpolator.interpolate(&m_audioInterpolatorDistanceRemain, ci, &ca)) + { + processOneAudioSample(ca); + m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance; + } + } + else // decimate + { + if (m_audioInterpolator.decimate(&m_audioInterpolatorDistanceRemain, ci, &ca)) + { + processOneAudioSample(ca); + m_audioInterpolatorDistanceRemain += m_audioInterpolatorDistance; + } + } + } +} + +void DABDemodSink::reset() +{ + dabReset(m_dab); +} + +void DABDemodSink::resetService() +{ + dabReset_msc(m_dab); +} + +void DABDemodSink::processOneAudioSample(Complex &ci) +{ + float factor = m_settings.m_volume / 5.0f; // Should this be 5 or 10? 5 allows some positive gain + + qint16 l = scale(ci.real(), factor); + qint16 r = scale(ci.real(), factor); + + m_audioBuffer[m_audioBufferFill].l = l; + m_audioBuffer[m_audioBufferFill].r = r; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("DABDemodSink::audio: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } + + m_demodBuffer[m_demodBufferFill++] = l; // FIXME: What about right channel? + if (m_demodBufferFill >= m_demodBuffer.size()) + { + QList *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod"); + + if (dataFifos) + { + QList::iterator it = dataFifos->begin(); + + for (; it != dataFifos->end(); ++it) { + (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16)); + } + } + + m_demodBufferFill = 0; + } +} + +DABDemodSink::DABDemodSink(DABDemod *packetDemod) : + m_dabDemod(packetDemod), + m_audioSampleRate(48000), + m_dabAudioSampleRate(10000), // Unused value to begin with + m_channelSampleRate(DABDEMOD_CHANNEL_SAMPLE_RATE), + m_channelFrequencyOffset(0), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_messageQueueToChannel(nullptr), + m_audioFifo(48000) +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + + m_magsq = 0.0; + + m_demodBuffer.resize(1<<12); + m_demodBufferFill = 0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); + + int mode = 1; // Latest DAB spec only has mode 1 + m_dab = dabInit(&m_device, + mode, + syncHandler, + systemDataHandler, + ensembleNameHandler, + programNameHandler, + fibQualityHandler, + audioHandler, + dataHandler, + byteHandler, + programDataHandler, + programQualityHandler, + motDataHandler, + nullptr, + nullptr, + this); + dabStartProcessing(m_dab); +} + +DABDemodSink::~DABDemodSink() +{ + dabExit(m_dab); +} + +void DABDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance == 1.0f) + { + processOneSample(c); + } + else if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else // decimate + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } +} + +void DABDemodSink::processOneSample(Complex &ci) +{ + // Calculate average and peak levels for level meter + double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag(); + Real magsq = (Real)(magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED)); + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + m_magsqCount++; + + // Send sample to DAB library + std::complex c; + c.real(ci.real()/SDR_RX_SCALED); + c.imag(ci.imag()/SDR_RX_SCALED); + m_device.putSample(c); +} + +void DABDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "DABDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if ((m_channelFrequencyOffset != channelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); + m_interpolatorDistance = (Real) channelSampleRate / (Real) DABDEMOD_CHANNEL_SAMPLE_RATE; + m_interpolatorDistanceRemain = m_interpolatorDistance; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void DABDemodSink::applySettings(const DABDemodSettings& settings, bool force) +{ + qDebug() << "DABDemodSink::applySettings:" + << " force: " << force; + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) DABDEMOD_CHANNEL_SAMPLE_RATE; + m_interpolatorDistanceRemain = m_interpolatorDistance; + } + + if ((settings.m_program != m_settings.m_program) || force) + { + if (!settings.m_program.isEmpty()) + { + QByteArray ba = settings.m_program.toLatin1(); + const char *program = ba.data(); + if (!is_audioService (m_dab, program)) + qWarning() << settings.m_program << " is not an audio service"; + else + { + dataforAudioService(m_dab, program, &m_ad, 0); + if (!m_ad.defined) + qWarning() << settings.m_program << " audio data is not defined"; + else + { + dabReset_msc(m_dab); + set_audioChannel(m_dab, &m_ad); + } + } + } + } + + m_settings = settings; +} + +// Called when audio device sample rate changes +void DABDemodSink::applyAudioSampleRate(int sampleRate) +{ + if (sampleRate < 0) + { + qWarning("DABDemodSink::applyAudioSampleRate: invalid sample rate: %d", sampleRate); + return; + } + + qDebug("DABDemodSink::applyAudioSampleRate: m_audioSampleRate: %d m_dabAudioSampleRate: %d", sampleRate, m_dabAudioSampleRate); + + m_audioInterpolator.create(16, m_dabAudioSampleRate, m_dabAudioSampleRate/2.2f); + m_audioInterpolatorDistanceRemain = 0; + m_audioInterpolatorDistance = (Real) m_dabAudioSampleRate / (Real) sampleRate; + + m_audioFifo.setSize(sampleRate); + + m_audioSampleRate = sampleRate; +} + +// Called when DAB audio sample rate changes +void DABDemodSink::applyDABAudioSampleRate(int sampleRate) +{ + qDebug("DABDemodSink::applyDABAudioSampleRate: m_audioSampleRate: %d new m_dabAudioSampleRate: %d", m_audioSampleRate, sampleRate); + + m_audioInterpolator.create(16, sampleRate, sampleRate/2.2f); + m_audioInterpolatorDistanceRemain = 0; + m_audioInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate; + + m_dabAudioSampleRate = sampleRate; +} diff --git a/plugins/channelrx/demoddab/dabdemodsink.h b/plugins/channelrx/demoddab/dabdemodsink.h new file mode 100644 index 000000000..8c2ab8e0e --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodsink.h @@ -0,0 +1,147 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB // +// Copyright (C) 2021 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_DABDEMODSINK_H +#define INCLUDE_DABDEMODSINK_H + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "util/movingaverage.h" +#include "util/messagequeue.h" +#include "audio/audiofifo.h" + +#include "dabdemodsettings.h" +#include "dabdemoddevice.h" + +#include + +#include + +#define DABDEMOD_CHANNEL_SAMPLE_RATE 2048000 + +class ChannelAPI; +class DABDemod; + +class DABDemodSink : public ChannelSampleSink { +public: + DABDemodSink(DABDemod *packetDemod); + ~DABDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const DABDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void applyDABAudioSampleRate(int sampleRate); + int getAudioSampleRate() const { return m_audioSampleRate; } + AudioFifo *getAudioFifo() { return &m_audioFifo; } + + void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; } + void setChannel(ChannelAPI *channel) { m_channel = channel; } + + double getMagSq() const { return m_magsq; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + void reset(); + void resetService(); + + // Callbacks + void systemData(bool sync, int16_t snr, int32_t freqOffset); + void ensembleName(const QString& name, int id); + void programName(const QString& name, int id); + void programData(int bitrate, const QString& audio, const QString& language, const QString& programType); + void audio(int16_t *buffer, int size, int samplerate, bool stereo); + void programQuality(int16_t frames, int16_t rs, int16_t aac); + void fibQuality(int16_t percent); + void data(const QString& data); + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + DABDemod *m_dabDemod; + DABDemodSettings m_settings; + ChannelAPI *m_channel; + + int m_audioSampleRate; // Output device sample rate + int m_dabAudioSampleRate; + int m_channelSampleRate; + int m_channelFrequencyOffset; + + void *m_dab; + DABDemodDevice m_device; + audiodata m_ad; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MessageQueue *m_messageQueueToChannel; + + MovingAverageUtil m_movingAverage; + + Interpolator m_audioInterpolator; + Real m_audioInterpolatorDistance; + Real m_audioInterpolatorDistanceRemain; + AudioVector m_audioBuffer; + AudioFifo m_audioFifo; + uint32_t m_audioBufferFill; + + QVector m_demodBuffer; + int m_demodBufferFill; + + void processOneSample(Complex &ci); + void processOneAudioSample(Complex &ci); + MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } +}; + +#endif // INCLUDE_DABDEMODSINK_H diff --git a/plugins/channelrx/demoddab/dabdemodwebapiadapter.cpp b/plugins/channelrx/demoddab/dabdemodwebapiadapter.cpp new file mode 100644 index 000000000..67b64154f --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodwebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2021 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 "SWGChannelSettings.h" +#include "dabdemod.h" +#include "dabdemodwebapiadapter.h" + +DABDemodWebAPIAdapter::DABDemodWebAPIAdapter() +{} + +DABDemodWebAPIAdapter::~DABDemodWebAPIAdapter() +{} + +int DABDemodWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings()); + response.getDabDemodSettings()->init(); + DABDemod::webapiFormatChannelSettings(response, m_settings); + + return 200; +} + +int DABDemodWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) force; + (void) errorMessage; + DABDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response); + + return 200; +} diff --git a/plugins/channelrx/demoddab/dabdemodwebapiadapter.h b/plugins/channelrx/demoddab/dabdemodwebapiadapter.h new file mode 100644 index 000000000..f274bafc2 --- /dev/null +++ b/plugins/channelrx/demoddab/dabdemodwebapiadapter.h @@ -0,0 +1,50 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 Edouard Griffiths, F4EXB. // +// Copyright (C) 2020 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_DABDEMOD_WEBAPIADAPTER_H +#define INCLUDE_DABDEMOD_WEBAPIADAPTER_H + +#include "channel/channelwebapiadapter.h" +#include "dabdemodsettings.h" + +/** + * Standalone API adapter only for the settings + */ +class DABDemodWebAPIAdapter : public ChannelWebAPIAdapter { +public: + DABDemodWebAPIAdapter(); + virtual ~DABDemodWebAPIAdapter(); + + 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: + DABDemodSettings m_settings; +}; + +#endif // INCLUDE_DABDEMOD_WEBAPIADAPTER_H diff --git a/plugins/channelrx/demoddab/readme.md b/plugins/channelrx/demoddab/readme.md new file mode 100644 index 000000000..a3085c986 --- /dev/null +++ b/plugins/channelrx/demoddab/readme.md @@ -0,0 +1,80 @@ +

DAB demodulator plugin

+ +

Introduction

+ +This plugin can be used to demodulate DAB and DAB+ radio. + +The DAB demodulator uses a sample rate of 2.048MHz. + +

Interface

+ +![DAB Demodulator plugin GUI](../../../doc/img/DABDemod_plugin.png) + +

1: Frequency shift from center frequency of reception

+ +Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2. + +

2: Channel power

+ +Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band. + +

3: Audio mute

+ +Left click on this button to toggle audio mute for this channel. + +If you right click on it it will open a dialog to select the audio output device. See [audio management documentation](../../../sdrgui/audio.md) for details. + +

4: RF level meter in dB

+ + - top bar (green): average value + - bottom bar (blue green): instantaneous peak value + - tip vertical bar (bright green): peak hold value + +

5: Audio volume

+ +This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button. A value of 5.0 corresponds to a gain of 0dB. + +

8: Channel

+ +Displays a list of DAB Band III channels and frequencies. Selecting an item will set the Device center frequency accordingly. +If the center frequency is set manually, this box will be updated to reflect the corresponding channel, or left blank if there is not a channel that corresponds to the current centre frequency. + +

5: RF Bandwidth

+ +This specifies the bandwidth of a filter that is applied to the input signal before decimation or interpolation to limit the received signal's bandwidth. This should typically be 1.537 MHz. + +

6: Find

+ +Enter a regular expression used to filter the program table. + +

7: Clear programs

+ +Clear all programs in the table. + +

Program Table

+ +The program table shows programs that have been detected within a tuned ensemble. Double clicking on a program will cause +the demodulator to set the corresponding center frequency and then to play audio for the program. + +

Current Program

+ +The current program area display information about the currently playing program, including: + +* Program name. +* Ensemble name. +* Program type (E.g. News / Pop). +* Language. +* Audio (DAB or DAB+). +* Bitrate in kbps. +* Audio sample rate (in kSa/s). If this does not match the sample rate of the selected audio device, it will be resampled to match. +* Data broadcast with the program (E.g. song name). + +

Statistics

+ +The statitics areas displays statistics generated by the demodulator that may give an indiciation of the quality of the received signal. + +If you are hearing dropouts in audio, try adjusting your antenna in order to improve the reported SNR. + +

Attribution

+ +The DAB demodulator used DAB library by Jvan Katwijk. diff --git a/plugins/feature/demodanalyzer/demodanalyzersettings.cpp b/plugins/feature/demodanalyzer/demodanalyzersettings.cpp index 260c7c85c..df2a01cf0 100644 --- a/plugins/feature/demodanalyzer/demodanalyzersettings.cpp +++ b/plugins/feature/demodanalyzer/demodanalyzersettings.cpp @@ -25,6 +25,7 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = { QStringLiteral("AMDemod"), QStringLiteral("AMMod"), + QStringLiteral("DABDemod"), QStringLiteral("DSDDemod"), QStringLiteral("NFMDemod"), QStringLiteral("NFMMod"), @@ -39,6 +40,7 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = { const QStringList DemodAnalyzerSettings::m_channelURIs = { QStringLiteral("sdrangel.channel.amdemod"), QStringLiteral("sdrangel.channeltx.modam"), + QStringLiteral("sdrangel.channel.dabdemod"), QStringLiteral("sdrangel.channel.dsddemod"), QStringLiteral("sdrangel.channel.nfmdemod"), QStringLiteral("sdrangel.channeltx.modnfm"), diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 7ca1a9f38..7021e226f 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -3871,6 +3871,11 @@ bool WebAPIRequestMapper::getChannelSettings( channelSettings->setDatvModSettings(new SWGSDRangel::SWGDATVModSettings()); channelSettings->getDatvModSettings()->fromJsonObject(settingsJsonObject); } + else if (channelSettingsKey == "DABDemodSettings") + { + channelSettings->setDabDemodSettings(new SWGSDRangel::SWGDABDemodSettings()); + channelSettings->getDabDemodSettings()->fromJsonObject(settingsJsonObject); + } else if (channelSettingsKey == "DSDDemodSettings") { channelSettings->setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings()); @@ -4595,6 +4600,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings& channelSettings.setAtvModSettings(nullptr); channelSettings.setBfmDemodSettings(nullptr); channelSettings.setDatvModSettings(nullptr); + channelSettings.setDabDemodSettings(nullptr); channelSettings.setDsdDemodSettings(nullptr); channelSettings.setIeee802154ModSettings(nullptr); channelSettings.setNfmDemodSettings(nullptr); diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index 134368b34..933fc6a3e 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -37,6 +37,7 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { {"sdrangel.channel.modchirpchat", "ChirpChatModSettings"}, {"sdrangel.channel.demodatv", "ATVDemodSettings"}, {"sdrangel.channel.demoddatv", "DATVDemodSettings"}, + {"sdrangel.channel.dabdemod", "DABDemodSettings"}, {"sdrangel.channel.dsddemod", "DSDDemodSettings"}, {"sdrangel.channel.filesink", "FileSinkSettings"}, {"sdrangel.channeltx.filesource", "FileSourceSettings"}, @@ -132,6 +133,7 @@ const QMap WebAPIUtils::m_channelTypeToSettingsKey = { {"ChirpChatMod", "ChirpChatModSettings"}, {"DATVDemod", "DATVDemodSettings"}, {"DATVMod", "DATVModSettings"}, + {"DABDemod", "DABDemodSettings"}, {"DSDDemod", "DSDDemodSettings"}, {"FileSink", "FileSinkSettings"}, {"FileSource", "FileSourceSettings"}, diff --git a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml index 8deb48415..5bd15c325 100644 --- a/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/ChannelSettings.yaml @@ -43,6 +43,8 @@ ChannelSettings: $ref: "http://swgserver:8081/api/swagger/include/DATVMod.yaml#/DATVModSettings" DATVDemodSettings: $ref: "http://swgserver:8081/api/swagger/include/DATVDemod.yaml#/DATVDemodSettings" + DABDemodSettings: + $ref: "http://swgserver:8081/api/swagger/include/DABDemod.yaml#/DABDemodSettings" DSDDemodSettings: $ref: "http://swgserver:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodSettings" FileSinkSettings: diff --git a/swagger/sdrangel/api/swagger/include/DABDemod.yaml b/swagger/sdrangel/api/swagger/include/DABDemod.yaml new file mode 100644 index 000000000..90e1ed840 --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/DABDemod.yaml @@ -0,0 +1,36 @@ +DABDemodSettings: + description: DABDemod + properties: + inputFrequencyOffset: + type: integer + format: int64 + rfBandwidth: + type: number + format: float + program: + type: string + volume: + type: number + format: float + audioMute: + type: integer + audioDeviceName: + type: string + rgbColor: + type: integer + title: + type: string + streamIndex: + description: MIMO channel. Not relevant when connected to SI (single Rx). + type: integer + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer + reverseAPIChannelIndex: + type: integer diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp index e1fa2cc89..188d7a738 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.cpp @@ -62,6 +62,8 @@ SWGChannelSettings::SWGChannelSettings() { m_datv_mod_settings_isSet = false; datv_demod_settings = nullptr; m_datv_demod_settings_isSet = false; + dab_demod_settings = nullptr; + m_dab_demod_settings_isSet = false; dsd_demod_settings = nullptr; m_dsd_demod_settings_isSet = false; file_sink_settings = nullptr; @@ -154,6 +156,8 @@ SWGChannelSettings::init() { m_datv_mod_settings_isSet = false; datv_demod_settings = new SWGDATVDemodSettings(); m_datv_demod_settings_isSet = false; + dab_demod_settings = new SWGDABDemodSettings(); + m_dab_demod_settings_isSet = false; dsd_demod_settings = new SWGDSDDemodSettings(); m_dsd_demod_settings_isSet = false; file_sink_settings = new SWGFileSinkSettings(); @@ -253,6 +257,9 @@ SWGChannelSettings::cleanup() { if(datv_demod_settings != nullptr) { delete datv_demod_settings; } + if(dab_demod_settings != nullptr) { + delete dab_demod_settings; + } if(dsd_demod_settings != nullptr) { delete dsd_demod_settings; } @@ -375,6 +382,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&datv_demod_settings, pJson["DATVDemodSettings"], "SWGDATVDemodSettings", "SWGDATVDemodSettings"); + ::SWGSDRangel::setValue(&dab_demod_settings, pJson["DABDemodSettings"], "SWGDABDemodSettings", "SWGDABDemodSettings"); + ::SWGSDRangel::setValue(&dsd_demod_settings, pJson["DSDDemodSettings"], "SWGDSDDemodSettings", "SWGDSDDemodSettings"); ::SWGSDRangel::setValue(&file_sink_settings, pJson["FileSinkSettings"], "SWGFileSinkSettings", "SWGFileSinkSettings"); @@ -492,6 +501,9 @@ SWGChannelSettings::asJsonObject() { if((datv_demod_settings != nullptr) && (datv_demod_settings->isSet())){ toJsonValue(QString("DATVDemodSettings"), datv_demod_settings, obj, QString("SWGDATVDemodSettings")); } + if((dab_demod_settings != nullptr) && (dab_demod_settings->isSet())){ + toJsonValue(QString("DABDemodSettings"), dab_demod_settings, obj, QString("SWGDABDemodSettings")); + } if((dsd_demod_settings != nullptr) && (dsd_demod_settings->isSet())){ toJsonValue(QString("DSDDemodSettings"), dsd_demod_settings, obj, QString("SWGDSDDemodSettings")); } @@ -741,6 +753,16 @@ SWGChannelSettings::setDatvDemodSettings(SWGDATVDemodSettings* datv_demod_settin this->m_datv_demod_settings_isSet = true; } +SWGDABDemodSettings* +SWGChannelSettings::getDabDemodSettings() { + return dab_demod_settings; +} +void +SWGChannelSettings::setDabDemodSettings(SWGDABDemodSettings* dab_demod_settings) { + this->dab_demod_settings = dab_demod_settings; + this->m_dab_demod_settings_isSet = true; +} + SWGDSDDemodSettings* SWGChannelSettings::getDsdDemodSettings() { return dsd_demod_settings; @@ -1047,6 +1069,9 @@ SWGChannelSettings::isSet(){ if(datv_demod_settings && datv_demod_settings->isSet()){ isObjectUpdated = true; break; } + if(dab_demod_settings && dab_demod_settings->isSet()){ + isObjectUpdated = true; break; + } if(dsd_demod_settings && dsd_demod_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h index 0dba9c8ca..c86733487 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChannelSettings.h @@ -33,6 +33,7 @@ #include "SWGChannelAnalyzerSettings.h" #include "SWGChirpChatDemodSettings.h" #include "SWGChirpChatModSettings.h" +#include "SWGDABDemodSettings.h" #include "SWGDATVDemodSettings.h" #include "SWGDATVModSettings.h" #include "SWGDSDDemodSettings.h" @@ -131,6 +132,9 @@ public: SWGDATVDemodSettings* getDatvDemodSettings(); void setDatvDemodSettings(SWGDATVDemodSettings* datv_demod_settings); + SWGDABDemodSettings* getDabDemodSettings(); + void setDabDemodSettings(SWGDABDemodSettings* dab_demod_settings); + SWGDSDDemodSettings* getDsdDemodSettings(); void setDsdDemodSettings(SWGDSDDemodSettings* dsd_demod_settings); @@ -261,6 +265,9 @@ private: SWGDATVDemodSettings* datv_demod_settings; bool m_datv_demod_settings_isSet; + SWGDABDemodSettings* dab_demod_settings; + bool m_dab_demod_settings_isSet; + SWGDSDDemodSettings* dsd_demod_settings; bool m_dsd_demod_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.cpp new file mode 100644 index 000000000..61d7fe1e8 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.cpp @@ -0,0 +1,415 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGDABDemodSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGDABDemodSettings::SWGDABDemodSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGDABDemodSettings::SWGDABDemodSettings() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + program = nullptr; + m_program_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + audio_device_name = nullptr; + m_audio_device_name_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = nullptr; + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +SWGDABDemodSettings::~SWGDABDemodSettings() { + this->cleanup(); +} + +void +SWGDABDemodSettings::init() { + input_frequency_offset = 0L; + m_input_frequency_offset_isSet = false; + rf_bandwidth = 0.0f; + m_rf_bandwidth_isSet = false; + program = new QString(""); + m_program_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + audio_mute = 0; + m_audio_mute_isSet = false; + audio_device_name = new QString(""); + m_audio_device_name_isSet = false; + rgb_color = 0; + m_rgb_color_isSet = false; + title = new QString(""); + m_title_isSet = false; + stream_index = 0; + m_stream_index_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; + reverse_api_channel_index = 0; + m_reverse_api_channel_index_isSet = false; +} + +void +SWGDABDemodSettings::cleanup() { + + + if(program != nullptr) { + delete program; + } + + + if(audio_device_name != nullptr) { + delete audio_device_name; + } + + if(title != nullptr) { + delete title; + } + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + + +} + +SWGDABDemodSettings* +SWGDABDemodSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGDABDemodSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", ""); + + ::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", ""); + + ::SWGSDRangel::setValue(&program, pJson["program"], "QString", "QString"); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", ""); + + ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); + + ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); + + ::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", ""); + +} + +QString +SWGDABDemodSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGDABDemodSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(m_input_frequency_offset_isSet){ + obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset)); + } + if(m_rf_bandwidth_isSet){ + obj->insert("rfBandwidth", QJsonValue(rf_bandwidth)); + } + if(program != nullptr && *program != QString("")){ + toJsonValue(QString("program"), program, obj, QString("QString")); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_audio_mute_isSet){ + obj->insert("audioMute", QJsonValue(audio_mute)); + } + if(audio_device_name != nullptr && *audio_device_name != QString("")){ + toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString")); + } + if(m_rgb_color_isSet){ + obj->insert("rgbColor", QJsonValue(rgb_color)); + } + if(title != nullptr && *title != QString("")){ + toJsonValue(QString("title"), title, obj, QString("QString")); + } + if(m_stream_index_isSet){ + obj->insert("streamIndex", QJsonValue(stream_index)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + if(m_reverse_api_channel_index_isSet){ + obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index)); + } + + return obj; +} + +qint64 +SWGDABDemodSettings::getInputFrequencyOffset() { + return input_frequency_offset; +} +void +SWGDABDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) { + this->input_frequency_offset = input_frequency_offset; + this->m_input_frequency_offset_isSet = true; +} + +float +SWGDABDemodSettings::getRfBandwidth() { + return rf_bandwidth; +} +void +SWGDABDemodSettings::setRfBandwidth(float rf_bandwidth) { + this->rf_bandwidth = rf_bandwidth; + this->m_rf_bandwidth_isSet = true; +} + +QString* +SWGDABDemodSettings::getProgram() { + return program; +} +void +SWGDABDemodSettings::setProgram(QString* program) { + this->program = program; + this->m_program_isSet = true; +} + +float +SWGDABDemodSettings::getVolume() { + return volume; +} +void +SWGDABDemodSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +qint32 +SWGDABDemodSettings::getAudioMute() { + return audio_mute; +} +void +SWGDABDemodSettings::setAudioMute(qint32 audio_mute) { + this->audio_mute = audio_mute; + this->m_audio_mute_isSet = true; +} + +QString* +SWGDABDemodSettings::getAudioDeviceName() { + return audio_device_name; +} +void +SWGDABDemodSettings::setAudioDeviceName(QString* audio_device_name) { + this->audio_device_name = audio_device_name; + this->m_audio_device_name_isSet = true; +} + +qint32 +SWGDABDemodSettings::getRgbColor() { + return rgb_color; +} +void +SWGDABDemodSettings::setRgbColor(qint32 rgb_color) { + this->rgb_color = rgb_color; + this->m_rgb_color_isSet = true; +} + +QString* +SWGDABDemodSettings::getTitle() { + return title; +} +void +SWGDABDemodSettings::setTitle(QString* title) { + this->title = title; + this->m_title_isSet = true; +} + +qint32 +SWGDABDemodSettings::getStreamIndex() { + return stream_index; +} +void +SWGDABDemodSettings::setStreamIndex(qint32 stream_index) { + this->stream_index = stream_index; + this->m_stream_index_isSet = true; +} + +qint32 +SWGDABDemodSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGDABDemodSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGDABDemodSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGDABDemodSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGDABDemodSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGDABDemodSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGDABDemodSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGDABDemodSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + +qint32 +SWGDABDemodSettings::getReverseApiChannelIndex() { + return reverse_api_channel_index; +} +void +SWGDABDemodSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) { + this->reverse_api_channel_index = reverse_api_channel_index; + this->m_reverse_api_channel_index_isSet = true; +} + + +bool +SWGDABDemodSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(m_input_frequency_offset_isSet){ + isObjectUpdated = true; break; + } + if(m_rf_bandwidth_isSet){ + isObjectUpdated = true; break; + } + if(program && *program != QString("")){ + isObjectUpdated = true; break; + } + if(m_volume_isSet){ + isObjectUpdated = true; break; + } + if(m_audio_mute_isSet){ + isObjectUpdated = true; break; + } + if(audio_device_name && *audio_device_name != QString("")){ + isObjectUpdated = true; break; + } + if(m_rgb_color_isSet){ + isObjectUpdated = true; break; + } + if(title && *title != QString("")){ + isObjectUpdated = true; break; + } + if(m_stream_index_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_channel_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.h new file mode 100644 index 000000000..42cee2212 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGDABDemodSettings.h @@ -0,0 +1,137 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGDABDemodSettings.h + * + * DABDemod + */ + +#ifndef SWGDABDemodSettings_H_ +#define SWGDABDemodSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGDABDemodSettings: public SWGObject { +public: + SWGDABDemodSettings(); + SWGDABDemodSettings(QString* json); + virtual ~SWGDABDemodSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGDABDemodSettings* fromJson(QString &jsonString) override; + + qint64 getInputFrequencyOffset(); + void setInputFrequencyOffset(qint64 input_frequency_offset); + + float getRfBandwidth(); + void setRfBandwidth(float rf_bandwidth); + + QString* getProgram(); + void setProgram(QString* program); + + float getVolume(); + void setVolume(float volume); + + qint32 getAudioMute(); + void setAudioMute(qint32 audio_mute); + + QString* getAudioDeviceName(); + void setAudioDeviceName(QString* audio_device_name); + + qint32 getRgbColor(); + void setRgbColor(qint32 rgb_color); + + QString* getTitle(); + void setTitle(QString* title); + + qint32 getStreamIndex(); + void setStreamIndex(qint32 stream_index); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + qint32 getReverseApiChannelIndex(); + void setReverseApiChannelIndex(qint32 reverse_api_channel_index); + + + virtual bool isSet() override; + +private: + qint64 input_frequency_offset; + bool m_input_frequency_offset_isSet; + + float rf_bandwidth; + bool m_rf_bandwidth_isSet; + + QString* program; + bool m_program_isSet; + + float volume; + bool m_volume_isSet; + + qint32 audio_mute; + bool m_audio_mute_isSet; + + QString* audio_device_name; + bool m_audio_device_name_isSet; + + qint32 rgb_color; + bool m_rgb_color_isSet; + + QString* title; + bool m_title_isSet; + + qint32 stream_index; + bool m_stream_index_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + + qint32 reverse_api_channel_index; + bool m_reverse_api_channel_index_isSet; + +}; + +} + +#endif /* SWGDABDemodSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index 3548dd07b..2d1dbd50b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -72,6 +72,7 @@ #include "SWGChirpChatModSettings.h" #include "SWGCommand.h" #include "SWGComplex.h" +#include "SWGDABDemodSettings.h" #include "SWGDATVDemodSettings.h" #include "SWGDATVModReport.h" #include "SWGDATVModSettings.h" @@ -436,6 +437,9 @@ namespace SWGSDRangel { if(QString("SWGComplex").compare(type) == 0) { return new SWGComplex(); } + if(QString("SWGDABDemodSettings").compare(type) == 0) { + return new SWGDABDemodSettings(); + } if(QString("SWGDATVDemodSettings").compare(type) == 0) { return new SWGDATVDemodSettings(); }