diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt
index 3b65fc952..d6c03d9ce 100644
--- a/plugins/channelrx/CMakeLists.txt
+++ b/plugins/channelrx/CMakeLists.txt
@@ -22,6 +22,10 @@ if (CODEC2_FOUND)
add_subdirectory(demodfreedv)
endif(CODEC2_FOUND)
+if (LIBSIGMF_FOUND)
+ add_subdirectory(sigmffilesink)
+endif(LIBSIGMF_FOUND)
+
if(NOT SERVER_MODE)
add_subdirectory(chanalyzer)
add_subdirectory(demodatv)
diff --git a/plugins/channelrx/sigmffilesink/CMakeLists.txt b/plugins/channelrx/sigmffilesink/CMakeLists.txt
new file mode 100644
index 000000000..447d4ba65
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/CMakeLists.txt
@@ -0,0 +1,59 @@
+project(sigmffilesink)
+
+set(sigmffilesink_SOURCES
+ sigmffilesink.cpp
+ sigmffilesinkbaseband.cpp
+ sigmffilesinksink.cpp
+ sigmffilesinksettings.cpp
+ sigmffilesinkwebapiadapter.cpp
+ sigmffilesinkplugin.cpp
+)
+
+set(sigmffilesink_HEADERS
+ sigmffilesink.h
+ sigmffilesinkbaseband.h
+ sigmffilesinksink.h
+ sigmffilesinksettings.h
+ sigmffilesinkwebapiadapter.h
+ sigmffilesinkplugin.h
+)
+
+include_directories(
+ ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
+ ${Boost_INCLUDE_DIR}
+)
+
+if(NOT SERVER_MODE)
+ set(sigmffilesink_SOURCES
+ ${sigmffilesink_SOURCES}
+ sigmffilesinkgui.cpp
+ sigmffilesinkgui.ui
+ )
+ set(sigmffilesink_HEADERS
+ ${sigmffilesink_HEADERS}
+ sigmffilesinkgui.h
+ )
+ set(TARGET_NAME sigmffilesink)
+ set(TARGET_LIB "Qt5::Widgets")
+ set(TARGET_LIB_GUI "sdrgui")
+ set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
+else()
+ set(TARGET_NAME sigmffilesinksrv)
+ set(TARGET_LIB "")
+ set(TARGET_LIB_GUI "")
+ set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
+endif()
+
+add_library(${TARGET_NAME} SHARED
+ ${sigmffilesink_SOURCES}
+)
+
+target_link_libraries(${TARGET_NAME}
+ Qt5::Core
+ ${TARGET_LIB}
+ sdrbase
+ ${TARGET_LIB_GUI}
+ swagger
+)
+
+install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesink.cpp b/plugins/channelrx/sigmffilesink/sigmffilesink.cpp
new file mode 100644
index 000000000..fc235e10f
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesink.cpp
@@ -0,0 +1,436 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include "SWGChannelSettings.h"
+
+#include "util/simpleserializer.h"
+#include "dsp/dspcommands.h"
+#include "dsp/dspdevicesourceengine.h"
+#include "dsp/dspengine.h"
+#include "dsp/devicesamplesource.h"
+#include "dsp/hbfilterchainconverter.h"
+#include "dsp/devicesamplemimo.h"
+#include "device/deviceapi.h"
+
+#include "sigmffilesinkbaseband.h"
+#include "sigmffilesink.h"
+
+MESSAGE_CLASS_DEFINITION(SigMFFileSink::MsgConfigureSigMFFileSink, Message)
+MESSAGE_CLASS_DEFINITION(SigMFFileSink::MsgBasebandSampleRateNotification, Message)
+
+const QString SigMFFileSink::m_channelIdURI = "sdrangel.channel.sigmffilesink";
+const QString SigMFFileSink::m_channelId = "SigMFFileSink";
+
+SigMFFileSink::SigMFFileSink(DeviceAPI *deviceAPI) :
+ ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
+ m_deviceAPI(deviceAPI),
+ m_centerFrequency(0),
+ m_frequencyOffset(0),
+ m_basebandSampleRate(48000)
+{
+ setObjectName(m_channelId);
+
+ m_thread = new QThread(this);
+ m_basebandSink = new SigMFFileSinkBaseband();
+ 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*)));
+}
+
+SigMFFileSink::~SigMFFileSink()
+{
+ disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
+ delete m_networkManager;
+ m_deviceAPI->removeChannelSinkAPI(this);
+ m_deviceAPI->removeChannelSink(this);
+ delete m_basebandSink;
+ delete m_thread;
+}
+
+uint32_t SigMFFileSink::getNumberOfDeviceStreams() const
+{
+ return m_deviceAPI->getNbSourceStreams();
+}
+
+void SigMFFileSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
+{
+ (void) firstOfBurst;
+ m_basebandSink->feed(begin, end);
+}
+
+void SigMFFileSink::start()
+{
+ qDebug("SigMFFileSink::start");
+ m_basebandSink->reset();
+ m_thread->start();
+}
+
+void SigMFFileSink::stop()
+{
+ qDebug("SigMFFileSink::stop");
+ m_thread->exit();
+ m_thread->wait();
+}
+
+bool SigMFFileSink::handleMessage(const Message& cmd)
+{
+ if (DSPSignalNotification::match(cmd))
+ {
+ DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
+
+ qDebug() << "SigMFFileSink::handleMessage: DSPSignalNotification:"
+ << " inputSampleRate: " << notif.getSampleRate()
+ << " centerFrequency: " << notif.getCenterFrequency();
+
+ m_basebandSampleRate = notif.getSampleRate();
+ m_centerFrequency = notif.getCenterFrequency();
+
+ calculateFrequencyOffset(m_settings.m_log2Decim, m_settings.m_filterChainHash); // This is when device sample rate changes
+
+ DSPSignalNotification *msg = new DSPSignalNotification(notif.getSampleRate(), notif.getCenterFrequency());
+ m_basebandSink->getInputMessageQueue()->push(msg);
+
+ if (getMessageQueueToGUI())
+ {
+ MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(notif.getSampleRate());
+ getMessageQueueToGUI()->push(msg);
+ }
+
+ return true;
+ }
+ else if (MsgConfigureSigMFFileSink::match(cmd))
+ {
+ MsgConfigureSigMFFileSink& cfg = (MsgConfigureSigMFFileSink&) cmd;
+ qDebug() << "SigMFFileSink::handleMessage: MsgConfigureSigMFFileSink";
+ applySettings(cfg.getSettings(), cfg.getForce());
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+QByteArray SigMFFileSink::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool SigMFFileSink::deserialize(const QByteArray& data)
+{
+ (void) data;
+ if (m_settings.deserialize(data))
+ {
+ MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(m_settings, true);
+ m_inputMessageQueue.push(msg);
+ return true;
+ }
+ else
+ {
+ m_settings.resetToDefaults();
+ MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(m_settings, true);
+ m_inputMessageQueue.push(msg);
+ return false;
+ }
+}
+
+void SigMFFileSink::getLocalDevices(std::vector& indexes)
+{
+ indexes.clear();
+ DSPEngine *dspEngine = DSPEngine::instance();
+
+ for (uint32_t i = 0; i < dspEngine->getDeviceSourceEnginesNumber(); i++)
+ {
+ DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(i);
+ DeviceSampleSource *deviceSource = deviceSourceEngine->getSource();
+
+ if (deviceSource->getDeviceDescription() == "LocalInput") {
+ indexes.push_back(i);
+ }
+ }
+}
+
+DeviceSampleSource *SigMFFileSink::getLocalDevice(uint32_t index)
+{
+ DSPEngine *dspEngine = DSPEngine::instance();
+
+ if (index < dspEngine->getDeviceSourceEnginesNumber())
+ {
+ DSPDeviceSourceEngine *deviceSourceEngine = dspEngine->getDeviceSourceEngineByIndex(index);
+ DeviceSampleSource *deviceSource = deviceSourceEngine->getSource();
+
+ if (deviceSource->getDeviceDescription() == "LocalInput")
+ {
+ if (!getDeviceAPI()) {
+ qDebug("SigMFFileSink::getLocalDevice: the parent device is unset");
+ } else if (getDeviceAPI()->getDeviceUID() == deviceSourceEngine->getUID()) {
+ qDebug("SigMFFileSink::getLocalDevice: source device at index %u is the parent device", index);
+ } else {
+ return deviceSource;
+ }
+ }
+ else
+ {
+ qDebug("SigMFFileSink::getLocalDevice: source device at index %u is not a Local Input source", index);
+ }
+ }
+ else
+ {
+ qDebug("SigMFFileSink::getLocalDevice: non existent source device index: %u", index);
+ }
+
+ return nullptr;
+}
+
+void SigMFFileSink::applySettings(const SigMFFileSinkSettings& settings, bool force)
+{
+ qDebug() << "SigMFFileSink::applySettings:"
+ << "force: " << force;
+
+ QList reverseAPIKeys;
+
+ if ((settings.m_log2Decim != m_settings.m_log2Decim) || force) {
+ reverseAPIKeys.append("log2Decim");
+ }
+ if ((settings.m_filterChainHash != m_settings.m_filterChainHash) || force) {
+ reverseAPIKeys.append("filterChainHash");
+ }
+
+ if ((settings.m_log2Decim != m_settings.m_log2Decim)
+ || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force)
+ {
+ calculateFrequencyOffset(settings.m_log2Decim, settings.m_filterChainHash);
+ }
+
+ SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband::create(settings, force);
+ m_basebandSink->getInputMessageQueue()->push(msg);
+
+ if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0))
+ {
+ 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;
+}
+
+void SigMFFileSink::record(bool record)
+{
+ SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork *msg = SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork::create(record);
+ m_basebandSink->getInputMessageQueue()->push(msg);
+}
+
+void SigMFFileSink::validateFilterChainHash(SigMFFileSinkSettings& settings)
+{
+ unsigned int s = 1;
+
+ for (unsigned int i = 0; i < settings.m_log2Decim; i++) {
+ s *= 3;
+ }
+
+ settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash;
+}
+
+void SigMFFileSink::calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash)
+{
+ double shiftFactor = HBFilterChainConverter::getShiftFactor(log2Decim, filterChainHash);
+ m_frequencyOffset = m_basebandSampleRate * shiftFactor;
+}
+
+int SigMFFileSink::webapiSettingsGet(
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ response.setLocalSinkSettings(new SWGSDRangel::SWGLocalSinkSettings());
+ response.getLocalSinkSettings()->init();
+ webapiFormatChannelSettings(response, m_settings);
+ return 200;
+}
+
+int SigMFFileSink::webapiSettingsPutPatch(
+ bool force,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ SigMFFileSinkSettings settings = m_settings;
+ webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
+
+ MsgConfigureSigMFFileSink *msg = MsgConfigureSigMFFileSink::create(settings, force);
+ m_inputMessageQueue.push(msg);
+
+ qDebug("SigMFFileSink::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
+ if (m_guiMessageQueue) // forward to GUI if any
+ {
+ MsgConfigureSigMFFileSink *msgToGUI = MsgConfigureSigMFFileSink::create(settings, force);
+ m_guiMessageQueue->push(msgToGUI);
+ }
+
+ webapiFormatChannelSettings(response, settings);
+
+ return 200;
+}
+
+void SigMFFileSink::webapiUpdateChannelSettings(
+ SigMFFileSinkSettings& settings,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response)
+{
+ if (channelSettingsKeys.contains("rgbColor")) {
+ settings.m_rgbColor = response.getLocalSinkSettings()->getRgbColor();
+ }
+ if (channelSettingsKeys.contains("title")) {
+ settings.m_title = *response.getLocalSinkSettings()->getTitle();
+ }
+ if (channelSettingsKeys.contains("log2Decim")) {
+ settings.m_log2Decim = response.getLocalSinkSettings()->getLog2Decim();
+ }
+
+ if (channelSettingsKeys.contains("filterChainHash"))
+ {
+ settings.m_filterChainHash = response.getLocalSinkSettings()->getFilterChainHash();
+ validateFilterChainHash(settings);
+ }
+
+ if (channelSettingsKeys.contains("useReverseAPI")) {
+ settings.m_useReverseAPI = response.getLocalSinkSettings()->getUseReverseApi() != 0;
+ }
+ if (channelSettingsKeys.contains("reverseAPIAddress")) {
+ settings.m_reverseAPIAddress = *response.getLocalSinkSettings()->getReverseApiAddress();
+ }
+ if (channelSettingsKeys.contains("reverseAPIPort")) {
+ settings.m_reverseAPIPort = response.getLocalSinkSettings()->getReverseApiPort();
+ }
+ if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
+ settings.m_reverseAPIDeviceIndex = response.getLocalSinkSettings()->getReverseApiDeviceIndex();
+ }
+ if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
+ settings.m_reverseAPIChannelIndex = response.getLocalSinkSettings()->getReverseApiChannelIndex();
+ }
+}
+
+void SigMFFileSink::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const SigMFFileSinkSettings& settings)
+{
+ response.getLocalSinkSettings()->setRgbColor(settings.m_rgbColor);
+
+ if (response.getLocalSinkSettings()->getTitle()) {
+ *response.getLocalSinkSettings()->getTitle() = settings.m_title;
+ } else {
+ response.getLocalSinkSettings()->setTitle(new QString(settings.m_title));
+ }
+
+ response.getLocalSinkSettings()->setLog2Decim(settings.m_log2Decim);
+ response.getLocalSinkSettings()->setFilterChainHash(settings.m_filterChainHash);
+ response.getLocalSinkSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
+
+ if (response.getLocalSinkSettings()->getReverseApiAddress()) {
+ *response.getLocalSinkSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
+ } else {
+ response.getLocalSinkSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
+ }
+
+ response.getLocalSinkSettings()->setReverseApiPort(settings.m_reverseAPIPort);
+ response.getLocalSinkSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
+ response.getLocalSinkSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
+}
+
+void SigMFFileSink::webapiReverseSendSettings(QList& channelSettingsKeys, const SigMFFileSinkSettings& settings, bool force)
+{
+ SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
+ swgChannelSettings->setDirection(0); // single sink (Rx)
+ swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
+ swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
+ swgChannelSettings->setChannelType(new QString("SigMFFileSink"));
+ swgChannelSettings->setLocalSinkSettings(new SWGSDRangel::SWGLocalSinkSettings());
+ SWGSDRangel::SWGLocalSinkSettings *swgLocalSinkSettings = swgChannelSettings->getLocalSinkSettings();
+
+ // transfer data that has been modified. When force is on transfer all data except reverse API data
+
+ if (channelSettingsKeys.contains("rgbColor") || force) {
+ swgLocalSinkSettings->setRgbColor(settings.m_rgbColor);
+ }
+ if (channelSettingsKeys.contains("title") || force) {
+ swgLocalSinkSettings->setTitle(new QString(settings.m_title));
+ }
+ if (channelSettingsKeys.contains("log2Decim") || force) {
+ swgLocalSinkSettings->setLog2Decim(settings.m_log2Decim);
+ }
+ if (channelSettingsKeys.contains("filterChainHash") || force) {
+ swgLocalSinkSettings->setFilterChainHash(settings.m_filterChainHash);
+ }
+
+ 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 SigMFFileSink::networkManagerFinished(QNetworkReply *reply)
+{
+ QNetworkReply::NetworkError replyError = reply->error();
+
+ if (replyError)
+ {
+ qWarning() << "v::networkManagerFinished:"
+ << " error(" << (int) replyError
+ << "): " << replyError
+ << ": " << reply->errorString();
+ }
+ else
+ {
+ QString answer = reply->readAll();
+ answer.chop(1); // remove last \n
+ qDebug("SigMFFileSink::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
+ }
+
+ reply->deleteLater();
+}
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesink.h b/plugins/channelrx/sigmffilesink/sigmffilesink.h
new file mode 100644
index 000000000..e1a591175
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesink.h
@@ -0,0 +1,161 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SIGMFFILESINK_H_
+#define INCLUDE_SIGMFFILESINK_H_
+
+#include
+#include
+#include
+
+#include "dsp/basebandsamplesink.h"
+#include "channel/channelapi.h"
+
+#include "sigmffilesinksettings.h"
+
+class QNetworkAccessManager;
+class QNetworkReply;
+class QThread;
+
+class DeviceAPI;
+class DeviceSampleSource;
+class SigMFFileSinkBaseband;
+
+class SigMFFileSink : public BasebandSampleSink, public ChannelAPI {
+ Q_OBJECT
+public:
+ class MsgConfigureSigMFFileSink : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const SigMFFileSinkSettings& getSettings() const { return m_settings; }
+ bool getForce() const { return m_force; }
+
+ static MsgConfigureSigMFFileSink* create(const SigMFFileSinkSettings& settings, bool force)
+ {
+ return new MsgConfigureSigMFFileSink(settings, force);
+ }
+
+ private:
+ SigMFFileSinkSettings m_settings;
+ bool m_force;
+
+ MsgConfigureSigMFFileSink(const SigMFFileSinkSettings& settings, bool force) :
+ Message(),
+ m_settings(settings),
+ m_force(force)
+ { }
+ };
+
+ class MsgBasebandSampleRateNotification : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgBasebandSampleRateNotification* create(int sampleRate) {
+ return new MsgBasebandSampleRateNotification(sampleRate);
+ }
+
+ int getSampleRate() const { return m_sampleRate; }
+
+ private:
+
+ MsgBasebandSampleRateNotification(int sampleRate) :
+ Message(),
+ m_sampleRate(sampleRate)
+ { }
+
+ int m_sampleRate;
+ };
+
+ SigMFFileSink(DeviceAPI *deviceAPI);
+ virtual ~SigMFFileSink();
+ virtual void destroy() { delete this; }
+
+ 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 void getTitle(QString& title) { title = "SigMF File Sink"; }
+ virtual qint64 getCenterFrequency() const { return m_frequencyOffset; }
+
+ 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 m_frequencyOffset;
+ }
+
+ 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 SigMFFileSinkSettings& settings);
+
+ static void webapiUpdateChannelSettings(
+ SigMFFileSinkSettings& settings,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response);
+
+ void getLocalDevices(std::vector& indexes);
+ uint32_t getNumberOfDeviceStreams() const;
+ void record(bool record);
+
+ static const QString m_channelIdURI;
+ static const QString m_channelId;
+
+private:
+ DeviceAPI *m_deviceAPI;
+ QThread *m_thread;
+ SigMFFileSinkBaseband *m_basebandSink;
+ SigMFFileSinkSettings m_settings;
+
+ uint64_t m_centerFrequency;
+ int64_t m_frequencyOffset;
+ uint32_t m_basebandSampleRate;
+
+ QNetworkAccessManager *m_networkManager;
+ QNetworkRequest m_networkRequest;
+
+ void applySettings(const SigMFFileSinkSettings& settings, bool force = false);
+ void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Decim);
+ static void validateFilterChainHash(SigMFFileSinkSettings& settings);
+ void calculateFrequencyOffset(uint32_t log2Decim, uint32_t filterChainHash);
+ DeviceSampleSource *getLocalDevice(uint32_t index);
+
+ void webapiReverseSendSettings(QList& channelSettingsKeys, const SigMFFileSinkSettings& settings, bool force);
+
+private slots:
+ void networkManagerFinished(QNetworkReply *reply);
+};
+
+#endif /* INCLUDE_SIGMFFILESINK_H_ */
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.cpp
new file mode 100644
index 000000000..13c6c4a0e
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.cpp
@@ -0,0 +1,166 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "dsp/downchannelizer.h"
+#include "dsp/dspengine.h"
+#include "dsp/dspcommands.h"
+
+#include "sigmffilesinkbaseband.h"
+
+MESSAGE_CLASS_DEFINITION(SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkBaseband, Message)
+MESSAGE_CLASS_DEFINITION(SigMFFileSinkBaseband::MsgConfigureSigMFFileSinkWork, Message)
+
+SigMFFileSinkBaseband::SigMFFileSinkBaseband() :
+ m_mutex(QMutex::Recursive)
+{
+ m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
+ m_channelizer = new DownChannelizer(&m_sink);
+
+ qDebug("SigMFFileSinkBaseband::SigMFFileSinkBaseband");
+ QObject::connect(
+ &m_sampleFifo,
+ &SampleSinkFifo::dataReady,
+ this,
+ &SigMFFileSinkBaseband::handleData,
+ Qt::QueuedConnection
+ );
+
+ connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
+}
+
+SigMFFileSinkBaseband::~SigMFFileSinkBaseband()
+{
+ delete m_channelizer;
+}
+
+void SigMFFileSinkBaseband::reset()
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ m_sampleFifo.reset();
+}
+
+void SigMFFileSinkBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
+{
+ m_sampleFifo.write(begin, end);
+}
+
+void SigMFFileSinkBaseband::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 SigMFFileSinkBaseband::handleInputMessages()
+{
+ Message* message;
+
+ while ((message = m_inputMessageQueue.pop()) != nullptr)
+ {
+ if (handleMessage(*message)) {
+ delete message;
+ }
+ }
+}
+
+bool SigMFFileSinkBaseband::handleMessage(const Message& cmd)
+{
+ if (MsgConfigureSigMFFileSinkBaseband::match(cmd))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ MsgConfigureSigMFFileSinkBaseband& cfg = (MsgConfigureSigMFFileSinkBaseband&) cmd;
+ qDebug() << "SigMFFileSinkBaseband::handleMessage: MsgConfigureSigMFFileSinkBaseband";
+
+ applySettings(cfg.getSettings(), cfg.getForce());
+
+ return true;
+ }
+ else if (DSPSignalNotification::match(cmd))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
+ qDebug() << "SigMFFileSinkBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
+ m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
+ m_channelizer->setBasebandSampleRate(notif.getSampleRate(), true); // apply decimation
+ m_sink.setSampleRate(getChannelSampleRate());
+
+ return true;
+ }
+ else if (MsgConfigureSigMFFileSinkWork::match(cmd))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ MsgConfigureSigMFFileSinkWork& conf = (MsgConfigureSigMFFileSinkWork&) cmd;
+ qDebug() << "SigMFFileSinkBaseband::handleMessage: MsgConfigureSigMFFileSinkWork: " << conf.isWorking();
+
+ if (conf.isWorking()) {
+ m_sink.startRecording();
+ } else {
+ m_sink.stopRecording();
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void SigMFFileSinkBaseband::applySettings(const SigMFFileSinkSettings& settings, bool force)
+{
+ qDebug() << "SigMFFileSinkBaseband::applySettings:"
+ << "m_log2Decim:" << settings.m_log2Decim
+ << "m_filterChainHash:" << settings.m_filterChainHash
+ << " force: " << force;
+
+ if ((settings.m_log2Decim != m_settings.m_log2Decim)
+ || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force)
+ {
+ m_channelizer->setDecimation(settings.m_log2Decim, settings.m_filterChainHash);
+ m_sink.setSampleRate(getChannelSampleRate());
+ }
+
+ //m_source.applySettings(settings, force);
+ m_settings = settings;
+}
+
+int SigMFFileSinkBaseband::getChannelSampleRate() const
+{
+ return m_channelizer->getChannelSampleRate();
+}
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.h b/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.h
new file mode 100644
index 000000000..04be9198f
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkbaseband.h
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SIFMFFILESINKBASEBAND_H_
+#define INCLUDE_SIFMFFILESINKBASEBAND_H_
+
+#include
+#include
+
+#include "dsp/samplesinkfifo.h"
+#include "util/message.h"
+#include "util/messagequeue.h"
+
+#include "sigmffilesinksink.h"
+#include "sigmffilesinksettings.h"
+
+class DownChannelizer;
+
+class SigMFFileSinkBaseband : public QObject
+{
+ Q_OBJECT
+public:
+ class MsgConfigureSigMFFileSinkBaseband : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const SigMFFileSinkSettings& getSettings() const { return m_settings; }
+ bool getForce() const { return m_force; }
+
+ static MsgConfigureSigMFFileSinkBaseband* create(const SigMFFileSinkSettings& settings, bool force)
+ {
+ return new MsgConfigureSigMFFileSinkBaseband(settings, force);
+ }
+
+ private:
+ SigMFFileSinkSettings m_settings;
+ bool m_force;
+
+ MsgConfigureSigMFFileSinkBaseband(const SigMFFileSinkSettings& settings, bool force) :
+ Message(),
+ m_settings(settings),
+ m_force(force)
+ { }
+ };
+
+ class MsgConfigureSigMFFileSinkWork : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ bool isWorking() const { return m_working; }
+
+ static MsgConfigureSigMFFileSinkWork* create(bool working)
+ {
+ return new MsgConfigureSigMFFileSinkWork(working);
+ }
+
+ private:
+ bool m_working;
+
+ MsgConfigureSigMFFileSinkWork(bool working) :
+ Message(),
+ m_working(working)
+ { }
+ };
+
+ SigMFFileSinkBaseband();
+ ~SigMFFileSinkBaseband();
+
+ void reset();
+ void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
+ MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
+ int getChannelSampleRate() const;
+ void setBasebandSampleRate(int sampleRate);
+
+private:
+ SampleSinkFifo m_sampleFifo;
+ DownChannelizer *m_channelizer;
+ SigMFFileSinkSink m_sink;
+ MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
+ SigMFFileSinkSettings m_settings;
+ QMutex m_mutex;
+
+ bool handleMessage(const Message& cmd);
+ void applySettings(const SigMFFileSinkSettings& settings, bool force = false);
+
+private slots:
+ void handleInputMessages();
+ void handleData(); //!< Handle data when samples have to be processed
+
+};
+
+
+#endif // INCLUDE_SIFMFFILESINKBASEBAND_H_
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp
new file mode 100644
index 000000000..59c0721e2
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp
@@ -0,0 +1,339 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "device/deviceuiset.h"
+#include "gui/basicchannelsettingsdialog.h"
+#include "gui/devicestreamselectiondialog.h"
+#include "dsp/hbfilterchainconverter.h"
+#include "mainwindow.h"
+
+#include "sigmffilesinkgui.h"
+#include "sigmffilesink.h"
+#include "ui_sigmffilesinkgui.h"
+
+SigMFFileSinkGUI* SigMFFileSinkGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelRx)
+{
+ SigMFFileSinkGUI* gui = new SigMFFileSinkGUI(pluginAPI, deviceUISet, channelRx);
+ return gui;
+}
+
+void SigMFFileSinkGUI::destroy()
+{
+ delete this;
+}
+
+void SigMFFileSinkGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString SigMFFileSinkGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 SigMFFileSinkGUI::getCenterFrequency() const {
+ return 0;
+}
+
+void SigMFFileSinkGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ (void) centerFrequency;
+}
+
+void SigMFFileSinkGUI::resetToDefaults()
+{
+ m_settings.resetToDefaults();
+ displaySettings();
+ applySettings(true);
+}
+
+QByteArray SigMFFileSinkGUI::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool SigMFFileSinkGUI::deserialize(const QByteArray& data)
+{
+ if (m_settings.deserialize(data))
+ {
+ displaySettings();
+ applySettings(true);
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool SigMFFileSinkGUI::handleMessage(const Message& message)
+{
+ if (SigMFFileSink::MsgBasebandSampleRateNotification::match(message))
+ {
+ SigMFFileSink::MsgBasebandSampleRateNotification& notif = (SigMFFileSink::MsgBasebandSampleRateNotification&) message;
+ //m_channelMarker.setBandwidth(notif.getSampleRate());
+ m_basebandSampleRate = notif.getSampleRate();
+ displayRateAndShift();
+ return true;
+ }
+ else if (SigMFFileSink::MsgConfigureSigMFFileSink::match(message))
+ {
+ const SigMFFileSink::MsgConfigureSigMFFileSink& cfg = (SigMFFileSink::MsgConfigureSigMFFileSink&) message;
+ m_settings = cfg.getSettings();
+ blockApplySettings(true);
+ displaySettings();
+ blockApplySettings(false);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+SigMFFileSinkGUI::SigMFFileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *channelrx, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::SigMFFileSinkGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceUISet(deviceUISet),
+ m_basebandSampleRate(0),
+ m_tickCount(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_sigMFFileSink = (SigMFFileSink*) channelrx;
+ m_sigMFFileSink->setMessageQueueToGUI(getInputMessageQueue());
+
+ ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
+ ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
+ ui->deltaFrequency->setValueRange(false, 8, -99999999, 99999999);
+
+ m_channelMarker.blockSignals(true);
+ m_channelMarker.setColor(m_settings.m_rgbColor);
+ m_channelMarker.setCenterFrequency(0);
+ m_channelMarker.setTitle("SigMF File Sink");
+ m_channelMarker.blockSignals(false);
+ m_channelMarker.setVisible(true); // activate signal on the last setting only
+
+ m_settings.setChannelMarker(&m_channelMarker);
+
+ m_deviceUISet->registerRxChannelInstance(SigMFFileSink::m_channelIdURI, this);
+ m_deviceUISet->addChannelMarker(&m_channelMarker);
+ m_deviceUISet->addRollupWidget(this);
+
+ connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
+ //connect(&(m_deviceUISet->m_deviceSourceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
+
+ displaySettings();
+ applySettings(true);
+}
+
+SigMFFileSinkGUI::~SigMFFileSinkGUI()
+{
+ m_deviceUISet->removeRxChannelInstance(this);
+ delete m_sigMFFileSink; // TODO: check this: when the GUI closes it has to delete the demodulator
+ delete ui;
+}
+
+void SigMFFileSinkGUI::blockApplySettings(bool block)
+{
+ m_doApplySettings = !block;
+}
+
+void SigMFFileSinkGUI::applySettings(bool force)
+{
+ if (m_doApplySettings)
+ {
+ setTitleColor(m_channelMarker.getColor());
+
+ SigMFFileSink::MsgConfigureSigMFFileSink* message = SigMFFileSink::MsgConfigureSigMFFileSink::create(m_settings, force);
+ m_sigMFFileSink->getInputMessageQueue()->push(message);
+ }
+}
+
+void SigMFFileSinkGUI::displaySettings()
+{
+ m_channelMarker.blockSignals(true);
+ m_channelMarker.setCenterFrequency(0);
+ m_channelMarker.setTitle(m_settings.m_title);
+ m_channelMarker.setBandwidth(m_basebandSampleRate / (1<decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
+ applyDecimation();
+ displayStreamIndex();
+
+ blockApplySettings(false);
+}
+
+void SigMFFileSinkGUI::displayStreamIndex()
+{
+ if (m_deviceUISet->m_deviceMIMOEngine) {
+ setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
+ } else {
+ setStreamIndicator("S"); // single channel indicator
+ }
+}
+
+void SigMFFileSinkGUI::displayRateAndShift()
+{
+ int shift = m_shiftFrequencyFactor * m_basebandSampleRate;
+ ui->deltaFrequency->setValue(shift);
+ //QLocale loc;
+ //ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
+ double channelSampleRate = ((double) m_basebandSampleRate) / (1<channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
+ m_channelMarker.setCenterFrequency(shift);
+ m_channelMarker.setBandwidth(channelSampleRate);
+}
+
+void SigMFFileSinkGUI::leaveEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(false);
+}
+
+void SigMFFileSinkGUI::enterEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(true);
+}
+
+void SigMFFileSinkGUI::handleSourceMessages()
+{
+ Message* message;
+
+ while ((message = getInputMessageQueue()->pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void SigMFFileSinkGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+ (void) widget;
+ (void) rollDown;
+}
+
+void SigMFFileSinkGUI::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_sigMFFileSink->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();
+}
+
+void SigMFFileSinkGUI::on_decimationFactor_currentIndexChanged(int index)
+{
+ m_settings.m_log2Decim = index;
+ applyDecimation();
+}
+
+void SigMFFileSinkGUI::on_position_valueChanged(int value)
+{
+ m_settings.m_filterChainHash = value;
+ applyPosition();
+}
+
+void SigMFFileSinkGUI::on_record_toggled(bool checked)
+{
+ m_sigMFFileSink->record(checked);
+}
+
+void SigMFFileSinkGUI::applyDecimation()
+{
+ uint32_t maxHash = 1;
+
+ for (uint32_t i = 0; i < m_settings.m_log2Decim; i++) {
+ maxHash *= 3;
+ }
+
+ ui->position->setMaximum(maxHash-1);
+ ui->position->setValue(m_settings.m_filterChainHash);
+ m_settings.m_filterChainHash = ui->position->value();
+ applyPosition();
+}
+
+void SigMFFileSinkGUI::applyPosition()
+{
+ ui->filterChainIndex->setText(tr("%1").arg(m_settings.m_filterChainHash));
+ QString s;
+ m_shiftFrequencyFactor = HBFilterChainConverter::convertToString(m_settings.m_log2Decim, m_settings.m_filterChainHash, s);
+ ui->filterChainText->setText(s);
+
+ displayRateAndShift();
+ applySettings();
+}
+
+void SigMFFileSinkGUI::tick()
+{
+ if (++m_tickCount == 20) { // once per second
+ m_tickCount = 0;
+ }
+}
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h
new file mode 100644
index 000000000..cdc3b8f94
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKGUI_H_
+#define PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKGUI_H_
+
+#include
+
+#include
+
+#include "plugin/plugininstancegui.h"
+#include "dsp/channelmarker.h"
+#include "gui/rollupwidget.h"
+#include "util/messagequeue.h"
+
+#include "sigmffilesinksettings.h"
+
+class PluginAPI;
+class DeviceUISet;
+class SigMFFileSink;
+class BasebandSampleSink;
+
+namespace Ui {
+ class SigMFFileSinkGUI;
+}
+
+class SigMFFileSinkGUI : public RollupWidget, public PluginInstanceGUI {
+ Q_OBJECT
+public:
+ static SigMFFileSinkGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
+ virtual void destroy();
+
+ void setName(const QString& name);
+ QString getName() const;
+ virtual qint64 getCenterFrequency() const;
+ virtual void setCenterFrequency(qint64 centerFrequency);
+
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
+ virtual bool handleMessage(const Message& message);
+
+private:
+ Ui::SigMFFileSinkGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceUISet* m_deviceUISet;
+ ChannelMarker m_channelMarker;
+ SigMFFileSinkSettings m_settings;
+ int m_basebandSampleRate;
+ double m_shiftFrequencyFactor; //!< Channel frequency shift factor
+ bool m_doApplySettings;
+
+ SigMFFileSink* m_sigMFFileSink;
+ MessageQueue m_inputMessageQueue;
+
+ uint32_t m_tickCount;
+
+ explicit SigMFFileSinkGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = nullptr);
+ virtual ~SigMFFileSinkGUI();
+
+ void blockApplySettings(bool block);
+ void applySettings(bool force = false);
+ void displaySettings();
+ void displayStreamIndex();
+ void displayRateAndShift();
+ void updateLocalDevices();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+
+ void applyDecimation();
+ void applyPosition();
+
+private slots:
+ void handleSourceMessages();
+ void on_decimationFactor_currentIndexChanged(int index);
+ void on_position_valueChanged(int value);
+ void on_record_toggled(bool checked);
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDialogCalled(const QPoint& p);
+ void tick();
+};
+
+
+
+#endif /* PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKGUI_H_ */
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui
new file mode 100644
index 000000000..565d30a50
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui
@@ -0,0 +1,350 @@
+
+
+ SigMFFileSinkGUI
+
+
+
+ 0
+ 0
+ 340
+ 112
+
+
+
+
+ 340
+ 100
+
+
+
+
+ Liberation Sans
+ 9
+
+
+
+ SigMF File Sink
+
+
+ SigMF File Sink
+
+
+
+
+ 0
+ 0
+ 341
+ 101
+
+
+
+ Settings
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+ 3
+
+
-
+
+
+ 2
+
+
-
+
+
+
+ 16
+ 0
+
+
+
+ Df
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Liberation Mono
+ 12
+
+
+
+ PointingHandCursor
+
+
+ Qt::StrongFocus
+
+
+ Demod shift frequency from center in Hz
+
+
+
+ -
+
+
+ Hz
+
+
+
+ -
+
+
+ NCO
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Dec
+
+
+
+ -
+
+
+
+ 55
+ 16777215
+
+
+
+ Decimation factor
+
+
-
+
+ 1
+
+
+ -
+
+ 2
+
+
+ -
+
+ 4
+
+
+ -
+
+ 8
+
+
+ -
+
+ 16
+
+
+ -
+
+ 32
+
+
+ -
+
+ 64
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ Effective channel rate (kS/s)
+
+
+ 0000k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+ 10
+
+
-
+
+
+ Pos
+
+
+
+ -
+
+
+ Center frequency position
+
+
+ 2
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 24
+ 0
+
+
+
+ Filter chain hash code
+
+
+ 000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ Filter chain stages left to right (L: low, C: center, H: high)
+
+
+ LLLLLL
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 24
+ 16777215
+
+
+
+
+
+
+
+ :/record_off.png:/record_off.png
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+ ValueDialZ
+ QWidget
+
+ 1
+
+
+
+
+
+
+
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.cpp
new file mode 100644
index 000000000..be93c2c1c
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.cpp
@@ -0,0 +1,85 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+
+#include
+#include "plugin/pluginapi.h"
+
+#ifndef SERVER_MODE
+#include "sigmffilesinkgui.h"
+#endif
+#include "sigmffilesink.h"
+#include "sigmffilesinkwebapiadapter.h"
+#include "sigmffilesinkplugin.h"
+
+const PluginDescriptor SigMFFileSinkPlugin::m_pluginDescriptor = {
+ SigMFFileSink::m_channelId,
+ QString("SigMF File Sink"),
+ QString("5.8.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+SigMFFileSinkPlugin::SigMFFileSinkPlugin(QObject* parent) :
+ QObject(parent),
+ m_pluginAPI(0)
+{
+}
+
+const PluginDescriptor& SigMFFileSinkPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void SigMFFileSinkPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register channel Source
+ m_pluginAPI->registerRxChannel(SigMFFileSink::m_channelIdURI, SigMFFileSink::m_channelId, this);
+}
+
+#ifdef SERVER_MODE
+PluginInstanceGUI* SigMFFileSinkPlugin::createRxChannelGUI(
+ DeviceUISet *deviceUISet,
+ BasebandSampleSink *rxChannel) const
+{
+ return 0;
+}
+#else
+PluginInstanceGUI* SigMFFileSinkPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
+{
+ return SigMFFileSinkGUI::create(m_pluginAPI, deviceUISet, rxChannel);
+}
+#endif
+
+BasebandSampleSink* SigMFFileSinkPlugin::createRxChannelBS(DeviceAPI *deviceAPI) const
+{
+ return new SigMFFileSink(deviceAPI);
+}
+
+ChannelAPI* SigMFFileSinkPlugin::createRxChannelCS(DeviceAPI *deviceAPI) const
+{
+ return new SigMFFileSink(deviceAPI);
+}
+
+ChannelWebAPIAdapter* SigMFFileSinkPlugin::createChannelWebAPIAdapter() const
+{
+ return new SigMFFileSinkWebAPIAdapter();
+}
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.h b/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.h
new file mode 100644
index 000000000..f1059e744
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkplugin.h
@@ -0,0 +1,50 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKPLUGIN_H_
+#define PLUGINS_CHANNELRX_SIGMFFILESINK_SIGMFFILESINKPLUGIN_H_
+
+
+#include
+#include "plugin/plugininterface.h"
+
+class DeviceUISet;
+class BasebandSampleSink;
+
+class SigMFFileSinkPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "sdrangel.demod.sigmffilesink")
+
+public:
+ explicit SigMFFileSinkPlugin(QObject* parent = 0);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
+ virtual BasebandSampleSink* createRxChannelBS(DeviceAPI *deviceAPI) const;
+ virtual ChannelAPI* createRxChannelCS(DeviceAPI *deviceAPI) const;
+ virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+};
+
+#endif /* PLUGINS_CHANNELRX_LOCALSINK_LOCALSINKPLUGIN_H_ */
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp
new file mode 100644
index 000000000..21c68b2f2
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp
@@ -0,0 +1,119 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "util/simpleserializer.h"
+#include "settings/serializable.h"
+
+#include "sigmffilesinksettings.h"
+
+SigMFFileSinkSettings::SigMFFileSinkSettings()
+{
+ resetToDefaults();
+}
+
+void SigMFFileSinkSettings::resetToDefaults()
+{
+ m_ncoMode = false;
+ m_inputFrequencyOffset = 0;
+ m_fileRecordName = "";
+ m_rgbColor = QColor(140, 4, 4).rgb();
+ m_title = "Local sink";
+ m_log2Decim = 0;
+ m_filterChainHash = 0;
+ m_channelMarker = nullptr;
+ m_streamIndex = 0;
+ m_useReverseAPI = false;
+ m_reverseAPIAddress = "127.0.0.1";
+ m_reverseAPIPort = 8888;
+ m_reverseAPIDeviceIndex = 0;
+ m_reverseAPIChannelIndex = 0;
+}
+
+QByteArray SigMFFileSinkSettings::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_inputFrequencyOffset);
+ s.writeBool(2, m_ncoMode);
+ s.writeString(3, m_fileRecordName);
+ s.writeS32(4, m_streamIndex);
+ s.writeU32(5, m_rgbColor);
+ s.writeString(6, m_title);
+ s.writeBool(7, m_useReverseAPI);
+ s.writeString(8, m_reverseAPIAddress);
+ s.writeU32(9, m_reverseAPIPort);
+ s.writeU32(10, m_reverseAPIDeviceIndex);
+ s.writeU32(11, m_reverseAPIChannelIndex);
+ s.writeU32(12, m_log2Decim);
+ s.writeU32(13, m_filterChainHash);
+
+ return s.final();
+}
+
+bool SigMFFileSinkSettings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid())
+ {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1)
+ {
+ uint32_t tmp;
+ QString strtmp;
+
+ d.readS32(1, &m_inputFrequencyOffset, 0);
+ d.readBool(2, &m_ncoMode, false);
+ d.readString(3, &m_fileRecordName, "");
+ d.readS32(4, &m_streamIndex, 0);
+ d.readU32(5, &m_rgbColor, QColor(0, 255, 255).rgb());
+ d.readString(6, &m_title, "Local sink");
+ d.readBool(7, &m_useReverseAPI, false);
+ d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
+ d.readU32(9, &tmp, 0);
+
+ if ((tmp > 1023) && (tmp < 65535)) {
+ m_reverseAPIPort = tmp;
+ } else {
+ m_reverseAPIPort = 8888;
+ }
+
+ d.readU32(10, &tmp, 0);
+ m_reverseAPIDeviceIndex = tmp > 99 ? 99 : tmp;
+ d.readU32(11, &tmp, 0);
+ m_reverseAPIChannelIndex = tmp > 99 ? 99 : tmp;
+ d.readU32(12, &tmp, 0);
+ m_log2Decim = tmp > 6 ? 6 : tmp;
+ d.readU32(13, &m_filterChainHash, 0);
+
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+
+
+
+
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h
new file mode 100644
index 000000000..d5ad9b8a4
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SIFMFFILESINKSETTINGS_H_
+#define INCLUDE_SIFMFFILESINKSETTINGS_H_
+
+#include
+#include
+
+class Serializable;
+
+struct SigMFFileSinkSettings
+{
+ bool m_ncoMode;
+ qint32 m_inputFrequencyOffset;
+ QString m_fileRecordName;
+ quint32 m_rgbColor;
+ QString m_title;
+ uint32_t m_log2Decim;
+ uint32_t m_filterChainHash;
+ 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;
+
+ Serializable *m_channelMarker;
+
+ SigMFFileSinkSettings();
+ void resetToDefaults();
+ void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+};
+
+#endif /* INCLUDE_SIFMFFILESINKSETTINGS_H_ */
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp
new file mode 100644
index 000000000..44399c376
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp
@@ -0,0 +1,57 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "dsp/filerecord.h"
+
+#include "sigmffilesinksink.h"
+
+SigMFFileSinkSink::SigMFFileSinkSink() :
+ m_record(false)
+{}
+
+SigMFFileSinkSink::~SigMFFileSinkSink()
+{}
+
+void SigMFFileSinkSink::startRecording()
+{
+ QString fileBase;
+ FileRecordInterface::RecordType recordType = FileRecordInterface::guessTypeFromFileName(m_settings.m_fileRecordName, fileBase);
+
+ if (recordType == FileRecordInterface::RecordTypeSigMF)
+ {
+ m_fileSink.setFileName(fileBase);
+ m_fileSink.startRecording();
+ m_record = true;
+ }
+}
+
+void SigMFFileSinkSink::stopRecording()
+{
+ m_record = false;
+ m_fileSink.stopRecording();
+}
+
+void SigMFFileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
+{
+ if (m_record) {
+ m_fileSink.feed(begin, end, true);
+ }
+}
+
+void SigMFFileSinkSink::setSampleRate(int sampleRate)
+{
+}
\ No newline at end of file
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksink.h b/plugins/channelrx/sigmffilesink/sigmffilesinksink.h
new file mode 100644
index 000000000..0df70ad85
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinksink.h
@@ -0,0 +1,54 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SIFMFFILESINKSINK_H_
+#define INCLUDE_SIFMFFILESINKSINK_H_
+
+#include "dsp/channelsamplesink.h"
+#include "dsp/sigmffilerecord.h"
+
+#include "sigmffilesinksettings.h"
+
+class FileRecordInterface;
+
+class SigMFFileSinkSink : public ChannelSampleSink {
+public:
+ SigMFFileSinkSink();
+ ~SigMFFileSinkSink();
+
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
+
+ SigMFFileRecord *getFileSink() { return &m_fileSink; }
+ void startRecording();
+ void stopRecording();
+ void setDeviceHwId(const QString& hwId) { m_deviceHwId = hwId; }
+ void setDeviceUId(int uid) { m_deviceUId = uid; }
+ void setSampleRate(int sampleRate);
+ void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
+ void applySettings(const SigMFFileSinkSettings& settings, bool force = false);
+
+private:
+ int m_channelSampleRate;
+ int m_channelFrequencyOffset;
+ SigMFFileSinkSettings m_settings;
+ SigMFFileRecord m_fileSink;
+ bool m_record;
+ QString m_deviceHwId;
+ int m_deviceUId;
+};
+
+#endif // INCLUDE_SIFMFFILESINKSINK_H_
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.cpp
new file mode 100644
index 000000000..62ed15523
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.cpp
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "SWGChannelSettings.h"
+#include "sigmffilesink.h"
+#include "sigmffilesinkwebapiadapter.h"
+
+SigMFFileSinkWebAPIAdapter::SigMFFileSinkWebAPIAdapter()
+{}
+
+SigMFFileSinkWebAPIAdapter::~SigMFFileSinkWebAPIAdapter()
+{}
+
+int SigMFFileSinkWebAPIAdapter::webapiSettingsGet(
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ (void) response;
+ // response.setLocalSinkSettings(new SWGSDRangel::SWGLocalSinkSettings());
+ // response.getLocalSinkSettings()->init();
+ // LocalSink::webapiFormatChannelSettings(response, m_settings);
+
+ return 200;
+}
+
+int SigMFFileSinkWebAPIAdapter::webapiSettingsPutPatch(
+ bool force,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ SigMFFileSink::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
+
+ return 200;
+}
diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.h b/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.h
new file mode 100644
index 000000000..7639600d2
--- /dev/null
+++ b/plugins/channelrx/sigmffilesink/sigmffilesinkwebapiadapter.h
@@ -0,0 +1,49 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SIGMFFILESINK_WEBAPIADAPTER_H
+#define INCLUDE_SIGMFFILESINK_WEBAPIADAPTER_H
+
+#include "channel/channelwebapiadapter.h"
+#include "sigmffilesinksettings.h"
+
+/**
+ * Standalone API adapter only for the settings
+ */
+class SigMFFileSinkWebAPIAdapter : public ChannelWebAPIAdapter {
+public:
+ SigMFFileSinkWebAPIAdapter();
+ virtual ~SigMFFileSinkWebAPIAdapter();
+
+ 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:
+ SigMFFileSinkSettings m_settings;
+};
+
+#endif // INCLUDE_LOCALSINK_WEBAPIADAPTER_H