diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 7da77d43d..2f6274f9b 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -34,6 +34,7 @@ endif()
add_subdirectory(channelrx)
add_subdirectory(channeltx)
+add_subdirectory(channelmimo)
add_subdirectory(samplemimo)
add_subdirectory(samplesource)
add_subdirectory(samplesink)
diff --git a/plugins/channelmimo/CMakeLists.txt b/plugins/channelmimo/CMakeLists.txt
new file mode 100644
index 000000000..1f153b8d3
--- /dev/null
+++ b/plugins/channelmimo/CMakeLists.txt
@@ -0,0 +1,5 @@
+project(channelmimo)
+
+if (NOT SERVER_MODE)
+ add_subdirectory(interferometer)
+endif()
diff --git a/plugins/channelmimo/interferometer/CMakeLists.txt b/plugins/channelmimo/interferometer/CMakeLists.txt
new file mode 100644
index 000000000..b6aa9bf80
--- /dev/null
+++ b/plugins/channelmimo/interferometer/CMakeLists.txt
@@ -0,0 +1,60 @@
+project(interferometer)
+
+set(interferometer_SOURCES
+ interferometer.cpp
+ interferometercorr.cpp
+ interferometersettings.cpp
+ interferometersink.cpp
+ interferometerstreamsink.cpp
+ interferometerwebapiadapter.cpp
+)
+
+set(interferometer_HEADERS
+ interferometer.h
+ interferometercorr.h
+ interferometersettings.h
+ interferometersink.h
+ interferometerstreamsink.h
+ interferometerwebapiadapter.h
+)
+
+include_directories(
+ ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
+ ${Boost_INCLUDE_DIR}
+)
+
+if (NOT SERVER_MODE)
+ set(interferometer_SOURCES
+ ${interferometer_SOURCES}
+ interferometergui.cpp
+ interferometergui.ui
+ )
+ set(interferometer_HEADERS
+ ${interferometer_HEADERS}
+ interferometergui.h
+ )
+
+ set(TARGET_NAME interferometer)
+ set(TARGET_LIB "Qt5::Widgets")
+ set(TARGET_LIB_GUI "sdrgui")
+ set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
+else()
+ set(TARGET_NAME interferometersrv)
+ set(TARGET_LIB "")
+ set(TARGET_LIB_GUI "")
+ set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
+endif()
+
+add_library(${TARGET_NAME} SHARED
+ ${interferometer_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/channelmimo/interferometer/interferometer.cpp b/plugins/channelmimo/interferometer/interferometer.cpp
index ff3717e0d..834cce90d 100644
--- a/plugins/channelmimo/interferometer/interferometer.cpp
+++ b/plugins/channelmimo/interferometer/interferometer.cpp
@@ -15,65 +15,268 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
+#include
+#include
+#include
+#include
+#include
+
#include "device/deviceapi.h"
#include "dsp/downchannelizer.h"
#include "dsp/threadedbasebandsamplesink.h"
+#include "SWGChannelSettings.h"
+
+#include "interferometersink.h"
#include "interferometer.h"
+MESSAGE_CLASS_DEFINITION(Interferometer::MsgConfigureInterferometer, Message)
+MESSAGE_CLASS_DEFINITION(Interferometer::MsgConfigureChannelizer, Message)
+MESSAGE_CLASS_DEFINITION(Interferometer::MsgSampleRateNotification, Message)
+
const QString Interferometer::m_channelIdURI = "sdrangel.channel.interferometer";
const QString Interferometer::m_channelId = "Interferometer";
Interferometer::Interferometer(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
- m_correlator(4096),
m_spectrumSink(nullptr),
m_scopeSink(nullptr)
{
- m_correlator.setCorrIndex(1);
- connect(&m_correlator, SIGNAL(dataReady(int, int)), this, SLOT(handleData(int, int)));
-
- for (int i = 0; i < m_deviceAPI->getNbSourceStreams(); i++)
- {
- m_sinks.push_back(InterferometerSink(&m_correlator));
- m_channelizers.push_back(new DownChannelizer(&m_sinks.back()));
- m_threadedBasebandSampleSinks.push_back(new ThreadedBasebandSampleSink(m_channelizers.back(), &m_sinks.back()));
- m_deviceAPI->addChannelSink(m_threadedBasebandSampleSinks.back(), i);
-
- if (i == 2) { // 2 way interferometer
- break;
- }
- }
-
m_deviceAPI->addChannelSinkAPI(this);
- m_sinks.back().setProcessingUnit(true); // The last one is processed last by the engine
+ m_thread = new QThread(this);
+ m_sink = new InterferometerSink();
+ m_sink->moveToThread(m_thread);
+ start();
}
Interferometer::~Interferometer()
{
- while (!m_threadedBasebandSampleSinks.empty())
- {
- m_deviceAPI->removeChannelSink(m_threadedBasebandSampleSinks.back());
- delete m_threadedBasebandSampleSinks.back();
- m_threadedBasebandSampleSinks.pop_back();
- }
-
- while (!m_channelizers.empty())
- {
- delete m_channelizers.back();
- m_channelizers.pop_back();
- }
-
m_deviceAPI->removeChannelSinkAPI(this);
+
+ if (m_thread->isRunning()) {
+ stop();
+ }
+
+ delete m_sink;
+ delete m_thread;
}
-void Interferometer::handleData(int start, int stop)
+void Interferometer::setSpectrumSink(BasebandSampleSink *spectrumSink)
{
- if ((m_settings.m_correlationType == InterferometerSettings::CorrelationAdd)
- || (m_settings.m_correlationType == InterferometerSettings::CorrelationMultiply))
+ m_spectrumSink = spectrumSink;
+ m_sink->setSpectrumSink(spectrumSink);
+}
+
+void Interferometer::setScopeSink(BasebandSampleSink *scopeSink)
+{
+ m_scopeSink = scopeSink;
+ m_sink->setScopeSink(scopeSink);
+}
+
+void Interferometer::start()
+{
+ m_thread->start();
+}
+
+void Interferometer::stop()
+{
+ m_thread->exit();
+ m_thread->wait();
+}
+
+void Interferometer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex)
+{
+ m_sink->feed(begin, end, sinkIndex);
+}
+
+void Interferometer::applySettings(const InterferometerSettings& settings, bool force)
+{
+ if ((m_settings.m_log2Decim != settings.m_log2Decim)
+ || (m_settings.m_filterChainHash != settings.m_filterChainHash) || force)
{
-
-
+ InterferometerSink::MsgConfigureChannelizer *msg = InterferometerSink::MsgConfigureChannelizer::create(
+ settings.m_log2Decim, settings.m_filterChainHash);
+ m_sink->getInputMessageQueue()->push(msg);
}
-}
\ No newline at end of file
+}
+
+bool Interferometer::handleMessage(const Message& cmd)
+{
+ if (MsgConfigureInterferometer::match(cmd))
+ {
+ MsgConfigureInterferometer& cfg = (MsgConfigureInterferometer&) cmd;
+ qDebug() << "Interferometer::handleMessage: MsgConfigureInterferometer";
+ applySettings(cfg.getSettings(), cfg.getForce());
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void Interferometer::validateFilterChainHash(InterferometerSettings& 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;
+}
+
+int Interferometer::webapiSettingsGet(
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ response.setInterferometerSettings(new SWGSDRangel::SWGInterferometerSettings());
+ response.getInterferometerSettings()->init();
+ webapiFormatChannelSettings(response, m_settings);
+ return 200;
+}
+
+int Interferometer::webapiSettingsPutPatch(
+ bool force,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ InterferometerSettings settings = m_settings;
+ webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
+
+ MsgConfigureInterferometer *msg = MsgConfigureInterferometer::create(settings, force);
+ m_inputMessageQueue.push(msg);
+
+ webapiFormatChannelSettings(response, settings);
+
+ return 200;
+}
+
+void Interferometer::webapiUpdateChannelSettings(
+ InterferometerSettings& settings,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response)
+{
+ if (channelSettingsKeys.contains("rgbColor")) {
+ settings.m_rgbColor = response.getInterferometerSettings()->getRgbColor();
+ }
+ if (channelSettingsKeys.contains("title")) {
+ settings.m_title = *response.getInterferometerSettings()->getTitle();
+ }
+ if (channelSettingsKeys.contains("log2Decim")) {
+ settings.m_log2Decim = response.getInterferometerSettings()->getLog2Decim();
+ }
+
+ if (channelSettingsKeys.contains("filterChainHash"))
+ {
+ settings.m_filterChainHash = response.getInterferometerSettings()->getFilterChainHash();
+ validateFilterChainHash(settings);
+ }
+
+ if (channelSettingsKeys.contains("useReverseAPI")) {
+ settings.m_useReverseAPI = response.getInterferometerSettings()->getUseReverseApi() != 0;
+ }
+ if (channelSettingsKeys.contains("reverseAPIAddress")) {
+ settings.m_reverseAPIAddress = *response.getInterferometerSettings()->getReverseApiAddress();
+ }
+ if (channelSettingsKeys.contains("reverseAPIPort")) {
+ settings.m_reverseAPIPort = response.getInterferometerSettings()->getReverseApiPort();
+ }
+ if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
+ settings.m_reverseAPIDeviceIndex = response.getInterferometerSettings()->getReverseApiDeviceIndex();
+ }
+ if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
+ settings.m_reverseAPIChannelIndex = response.getInterferometerSettings()->getReverseApiChannelIndex();
+ }
+}
+
+void Interferometer::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const InterferometerSettings& settings)
+{
+ response.getInterferometerSettings()->setRgbColor(settings.m_rgbColor);
+
+ if (response.getInterferometerSettings()->getTitle()) {
+ *response.getInterferometerSettings()->getTitle() = settings.m_title;
+ } else {
+ response.getInterferometerSettings()->setTitle(new QString(settings.m_title));
+ }
+
+ response.getInterferometerSettings()->setLog2Decim(settings.m_log2Decim);
+ response.getInterferometerSettings()->setFilterChainHash(settings.m_filterChainHash);
+ response.getInterferometerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
+
+ if (response.getInterferometerSettings()->getReverseApiAddress()) {
+ *response.getInterferometerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
+ } else {
+ response.getInterferometerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
+ }
+
+ response.getInterferometerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
+ response.getInterferometerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
+ response.getInterferometerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
+}
+
+void Interferometer::webapiReverseSendSettings(QList& channelSettingsKeys, const InterferometerSettings& settings, bool force)
+{
+ SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
+ swgChannelSettings->setDirection(2); // MIMO sink
+ swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
+ swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
+ swgChannelSettings->setChannelType(new QString("Interferometer"));
+ swgChannelSettings->setInterferometerSettings(new SWGSDRangel::SWGInterferometerSettings());
+ SWGSDRangel::SWGInterferometerSettings *swgInterferometerSettings = swgChannelSettings->getInterferometerSettings();
+
+ // transfer data that has been modified. When force is on transfer all data except reverse API data
+
+ if (channelSettingsKeys.contains("rgbColor") || force) {
+ swgInterferometerSettings->setRgbColor(settings.m_rgbColor);
+ }
+ if (channelSettingsKeys.contains("title") || force) {
+ swgInterferometerSettings->setTitle(new QString(settings.m_title));
+ }
+ if (channelSettingsKeys.contains("log2Decim") || force) {
+ swgInterferometerSettings->setLog2Decim(settings.m_log2Decim);
+ }
+ if (channelSettingsKeys.contains("filterChainHash") || force) {
+ swgInterferometerSettings->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
+ m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
+
+ delete swgChannelSettings;
+}
+
+void Interferometer::networkManagerFinished(QNetworkReply *reply)
+{
+ QNetworkReply::NetworkError replyError = reply->error();
+
+ if (replyError)
+ {
+ qWarning() << "Interferometer::networkManagerFinished:"
+ << " error(" << (int) replyError
+ << "): " << replyError
+ << ": " << reply->errorString();
+ return;
+ }
+
+ QString answer = reply->readAll();
+ answer.chop(1); // remove last \n
+ qDebug("Interferometer::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
+}
diff --git a/plugins/channelmimo/interferometer/interferometer.h b/plugins/channelmimo/interferometer/interferometer.h
index 58730d207..28c4fc8d5 100644
--- a/plugins/channelmimo/interferometer/interferometer.h
+++ b/plugins/channelmimo/interferometer/interferometer.h
@@ -19,37 +19,148 @@
#define INCLUDE_INTERFEROMETER_H
#include
+#include
+#include "dsp/mimosamplesink.h"
#include "channel/channelapi.h"
-#include "interferometersink.h"
-#include "interferometercorr.h"
+#include "util/messagequeue.h"
+#include "util/message.h"
+#include "interferometersettings.h"
+
+class QThread;
class DeviceAPI;
-class DownChannelizer;
-class ThreadedBasebandSampleSink;
+class InterferometerSink;
+class QNetworkReply;
+class QNetworkAccessManager;
+class BasebandSampleSink;
-class Interferometer: public QObject, ChannelAPI
+class Interferometer: public MIMOSampleSink, ChannelAPI
{
Q_OBJECT
public:
+ class MsgConfigureInterferometer : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const InterferometerSettings& getSettings() const { return m_settings; }
+ bool getForce() const { return m_force; }
+
+ static MsgConfigureInterferometer* create(const InterferometerSettings& settings, bool force)
+ {
+ return new MsgConfigureInterferometer(settings, force);
+ }
+
+ private:
+ InterferometerSettings m_settings;
+ bool m_force;
+
+ MsgConfigureInterferometer(const InterferometerSettings& settings, bool force) :
+ Message(),
+ m_settings(settings),
+ m_force(force)
+ { }
+ };
+
+ class MsgConfigureChannelizer : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getLog2Decim() const { return m_log2Decim; }
+ int getFilterChainHash() const { return m_filterChainHash; }
+
+ static MsgConfigureChannelizer* create(unsigned int log2Decim, unsigned int filterChainHash) {
+ return new MsgConfigureChannelizer(log2Decim, filterChainHash);
+ }
+
+ private:
+ unsigned int m_log2Decim;
+ unsigned int m_filterChainHash;
+
+ MsgConfigureChannelizer(unsigned int log2Decim, unsigned int filterChainHash) :
+ Message(),
+ m_log2Decim(log2Decim),
+ m_filterChainHash(filterChainHash)
+ { }
+ };
+
+ class MsgSampleRateNotification : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ static MsgSampleRateNotification* create(int sampleRate) {
+ return new MsgSampleRateNotification(sampleRate);
+ }
+
+ int getSampleRate() const { return m_sampleRate; }
+
+ private:
+
+ MsgSampleRateNotification(int sampleRate) :
+ Message(),
+ m_sampleRate(sampleRate)
+ { }
+
+ int m_sampleRate;
+ };
+
Interferometer(DeviceAPI *deviceAPI);
virtual ~Interferometer();
virtual void destroy() { delete this; }
+ virtual void start(); //!< thread start()
+ virtual void stop(); //!< thread exit() and wait()
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex);
+ virtual bool handleMessage(const Message& cmd); //!< Processing of a message. Returns true if message has actually been processed
+
+ MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
+ virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; }
+ MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
+
+ void setSpectrumSink(BasebandSampleSink *spectrumSink);
+ void setScopeSink(BasebandSampleSink *scopeSink);
+
+ 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 InterferometerSettings& settings);
+
+ static void webapiUpdateChannelSettings(
+ InterferometerSettings& settings,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response);
+
static const QString m_channelIdURI;
static const QString m_channelId;
private:
DeviceAPI *m_deviceAPI;
- InterferometerCorrelator m_correlator;
- std::vector m_sinks;
- std::vector m_channelizers;
- std::vector m_threadedBasebandSampleSinks;
+ QThread *m_thread;
+ InterferometerSink* m_sink;
BasebandSampleSink* m_spectrumSink;
BasebandSampleSink* m_scopeSink;
InterferometerSettings m_settings;
+ MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
+ MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
+
+ QNetworkAccessManager *m_networkManager;
+ QNetworkRequest m_networkRequest;
+
+ void applySettings(const InterferometerSettings& settings, bool force = false);
+ static void validateFilterChainHash(InterferometerSettings& settings);
+ void webapiReverseSendSettings(QList& channelSettingsKeys, const InterferometerSettings& settings, bool force);
private slots:
+ void networkManagerFinished(QNetworkReply *reply);
void handleData(int start, int stop);
};
diff --git a/plugins/channelmimo/interferometer/interferometercorr.cpp b/plugins/channelmimo/interferometer/interferometercorr.cpp
index 5091d560d..3f7bfa755 100644
--- a/plugins/channelmimo/interferometer/interferometercorr.cpp
+++ b/plugins/channelmimo/interferometer/interferometercorr.cpp
@@ -20,33 +20,21 @@
#include "dsp/fftengine.h"
#include "interferometercorr.h"
-std::complex fcAdd(std::complex& a, const std::complex& b) {
- return a + b;
+Sample sAdd(const Sample& a, const Sample& b) { //!< Sample addition
+ return Sample{a.real() + b.real(), a.imag() + b.imag()};
}
-std::complex fcMul(std::complex& a, const std::complex& b) {
- return a * b;
+Sample sMulConj(const Sample& a, const Sample& b) { //!< Sample multiply with conjugate
+ return Sample{a.real()*b.real() + a.imag()*b.imag(), a.imag()*b.real() - a.real()*b.imag()};
}
-Sample cf2sAdd(std::complex& a, const std::complex& b)
-{
- std::complex c = a + b;
- return Sample{c.real(), c.imag()};
+Sample cf2s(const std::complex& a) { //!< Complex float to Sample
+ Sample s;
+ s.setReal(a.real()*SDR_RX_SCALEF);
+ s.setImag(a.imag()*SDR_RX_SCALEF);
+ return s;
}
-Sample cf2sMul(std::complex& a, const std::complex& b)
-{
- std::complex c = a * b;
- return Sample{c.real(), c.imag()};
-}
-
-Sample cf2s(std::complex& a)
-{
- return Sample{a.real(), a.imag()};
-}
-
-const unsigned int InterferometerCorrelator::m_nbFFTBlocks = 128;
-
InterferometerCorrelator::InterferometerCorrelator(int fftSize) :
m_corrType(InterferometerSettings::CorrelationAdd),
m_fftSize(fftSize)
@@ -55,156 +43,158 @@ InterferometerCorrelator::InterferometerCorrelator(int fftSize) :
{
m_fft[i] = FFTEngine::create();
m_fft[i]->configure(2*fftSize, false); // internally twice the data FFT size
- m_data[i] = new std::complex[fftSize*m_nbFFTBlocks];
- m_dataIndex[i] = 0;
}
m_invFFT = FFTEngine::create();
m_invFFT->configure(2*fftSize, true);
m_dataj = new std::complex[2*fftSize]; // receives actual FFT result hence twice the data FFT size
- m_scorr.resize(2*fftSize*m_nbFFTBlocks); // same size multiplied by the number of buffered FFT blocks
- m_tcorr.resize(2*fftSize*m_nbFFTBlocks); // same size multiplied by the number of buffered FFT blocks
+ m_scorr.resize(2*fftSize);
+ m_tcorr.resize(2*fftSize);
}
InterferometerCorrelator::~InterferometerCorrelator()
{
for (int i = 0; i < 2; i++)
{
- delete[] m_data[i];
delete[] m_fft[i];
}
delete[] m_dataj;
}
-void InterferometerCorrelator::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int argIndex)
+void InterferometerCorrelator::performCorr(const SampleVector& data0, const SampleVector& data1)
{
- if (argIndex > 1) {
- return;
- }
-
switch (m_corrType)
{
case InterferometerSettings::CorrelationAdd:
- feedOp(begin, end, argIndex, cf2sAdd);
+ performOpCorr(data0, data1, sAdd);
break;
case InterferometerSettings::CorrelationMultiply:
- feedOp(begin, end, argIndex, cf2sMul);
+ performOpCorr(data0, data1, sMulConj);
break;
case InterferometerSettings::CorrelationCorrelation:
- feedCorr(begin, end, argIndex);
+ performFFTCorr(data0, data1);
break;
default:
break;
}
}
-void InterferometerCorrelator::feedOp(
- const SampleVector::const_iterator& begin,
- const SampleVector::const_iterator& end,
- unsigned int argIndex,
- Sample complexOp(std::complex& a, const std::complex& b)
-)
+void InterferometerCorrelator::performOpCorr(const SampleVector& data0, const SampleVector& data1, Sample sampleOp(const Sample& a, const Sample& b))
{
- int size = (end - begin);
- int fill = m_fftSize*m_nbFFTBlocks - m_dataIndex[argIndex];
- int first = std::min(fill, size);
+ unsigned int size = std::min(data0.size(), data1.size());
+ adjustTCorrSize(size);
- std::transform(begin, begin + first, m_data[argIndex] + m_dataIndex[argIndex], [](const Sample& s) -> std::complex {
- return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
- });
+ std::transform(
+ data0.begin(),
+ data0.begin() + size,
+ data1.begin(),
+ m_tcorr.begin(),
+ sampleOp
+ );
- if (argIndex == 1)
+ m_processed = size;
+ m_remaining = 0;
+}
+
+void InterferometerCorrelator::performFFTCorr(const SampleVector& data0, const SampleVector& data1)
+{
+ unsigned int size = std::min(data0.size(), data1.size());
+ SampleVector::const_iterator begin0 = data0.begin();
+ SampleVector::const_iterator begin1 = data1.begin();
+ adjustSCorrSize(size);
+ adjustTCorrSize(size);
+
+ while (size > m_fftSize)
{
+ // FFT[0]
std::transform(
- m_data[0] + m_dataIndex[0], m_data[0] + m_dataIndex[0] + first, m_data[1] + m_dataIndex[1],
- m_tcorr.begin() + m_dataIndex[0],
- complexOp
+ begin0,
+ begin0 + m_fftSize,
+ m_fft[0]->in(),
+ [](const Sample& s) -> std::complex {
+ return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
+ }
+ );
+ std::fill(m_fft[0]->in() + m_fftSize, m_fft[0]->in() + 2*m_fftSize, std::complex{0, 0});
+ m_fft[0]->transform();
+
+ // FFT[1]
+ std::transform(
+ begin1,
+ begin1 + m_fftSize,
+ m_fft[1]->in(),
+ [](const Sample& s) -> std::complex {
+ return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
+ }
+ );
+ std::fill(m_fft[1]->in() + m_fftSize, m_fft[1]->in() + 2*m_fftSize, std::complex{0, 0});
+ m_fft[1]->transform();
+
+ // conjugate FFT[1]
+ std::transform(
+ m_fft[1]->out(),
+ m_fft[1]->out()+2*m_fftSize,
+ m_dataj,
+ [](const std::complex& c) -> std::complex {
+ return std::conj(c);
+ }
);
- emit dataReady(m_dataIndex[0], m_dataIndex[0] + first);
+ // product of FFT[1]* with FFT[0] and store in inverse FFT input
+ std::transform(
+ m_fft[0]->out(),
+ m_fft[0]->out()+2*m_fftSize,
+ m_dataj,
+ m_invFFT->in(),
+ [](std::complex& a, const std::complex& b) -> std::complex {
+ return a*b;
+ }
+ );
+
+ // copy product to correlation spectrum
+ std::transform(
+ m_invFFT->in(),
+ m_invFFT->in() + 2*m_fftSize,
+ m_scorr.begin(),
+ cf2s
+ );
+
+ // do the inverse FFT to get time correlation
+ m_invFFT->transform();
+ std::transform(
+ m_invFFT->out(),
+ m_invFFT->out() + 2*m_fftSize,
+ m_tcorr.begin(),
+ cf2s
+ );
+
+ // TODO: do something with the result
+ size -= m_fftSize;
+ begin0 += m_fftSize;
+ begin1 += m_fftSize;
}
- if (size > fill)
+ // update the samples counters
+ m_processed = begin0 - data0.begin();
+ m_remaining = size - m_fftSize;
+}
+
+void InterferometerCorrelator::adjustSCorrSize(int size)
+{
+ if (size > m_scorrSize)
{
- std::transform(begin, begin + size - fill , m_data[argIndex], [](const Sample& s) -> std::complex {
- return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
- });
-
- if (argIndex == 1)
- {
- std::transform(
- m_data[0], m_data[0] + size - fill, m_data[1],
- m_tcorr.begin(),
- complexOp
- );
-
- emit dataReady(0, size - fill);
- }
-
- m_dataIndex[argIndex] = size - fill;
- }
- else
- {
- m_dataIndex[argIndex] += size;
+ m_scorr.resize(size);
+ m_scorrSize = size;
}
}
-void InterferometerCorrelator::feedCorr(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int argIndex)
+void InterferometerCorrelator::adjustTCorrSize(int size)
{
- int size = (end - begin);
- int fill = m_fftSize*m_nbFFTBlocks - m_dataIndex[argIndex];
- int first = std::min(fill, size);
-
- std::transform(begin, begin + first, m_data[argIndex] + m_dataIndex[argIndex], [](const Sample& s) -> std::complex {
- return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
- });
- processFFTBlocks(argIndex, m_dataIndex[argIndex], first);
-
- if (size > fill)
+ if (size > m_tcorrSize)
{
- std::transform(begin, begin + size - fill, m_data[argIndex], [](const Sample& s) -> std::complex {
- return std::complex{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
- });
- processFFTBlocks(argIndex, 0, size - fill);
- m_dataIndex[argIndex] = size - fill;
- }
- else
- {
- m_dataIndex[argIndex] += size;
+ m_tcorr.resize(size);
+ m_tcorrSize = size;
}
}
-
-void InterferometerCorrelator::processFFTBlocks(unsigned int argIndex, unsigned int dataIndex, int length)
-{
- int start = dataIndex / m_fftSize;
- int stop = (dataIndex + length) / m_fftSize;
-
- for (int i = start; i < stop; i++)
- {
- m_window.apply(&m_data[argIndex][start*m_fftSize], m_fft[argIndex]->in());
- std::fill(m_fft[argIndex]->in() + m_fftSize, m_fft[argIndex]->in() + 2*m_fftSize, std::complex{0,0});
- m_fft[argIndex]->transform();
-
- if (argIndex == m_corrIndex)
- {
- // conjugate
- std::transform(m_fft[argIndex]->out(), m_fft[argIndex]->out()+2*m_fftSize, m_dataj, []
- (const std::complex& c) -> std::complex { return std::conj(c); }
- );
- // product with FFT[0] store in inverse FFT input
- std::transform(m_fft[0]->out(), m_fft[0]->out()+2*m_fftSize, m_dataj, m_invFFT->in(), []
- (std::complex& a, const std::complex& b) -> std::complex { return a*b; }
- );
- // copy to correlation spectrum and do the inverse FFT to get time correlation
- std::transform(m_invFFT->in(), m_invFFT->in() + 2*m_fftSize, m_scorr.begin() + 2*i*m_fftSize, cf2s);
- m_invFFT->transform();
- std::transform(m_invFFT->out(), m_invFFT->out() + 2*m_fftSize, m_tcorr.begin() + 2*i*m_fftSize, cf2s);
- }
- }
-
- if (start != stop) {
- emit dataReady(start, stop);
- }
-}
\ No newline at end of file
diff --git a/plugins/channelmimo/interferometer/interferometercorr.h b/plugins/channelmimo/interferometer/interferometercorr.h
index 6abafc5b5..ebfec4ffd 100644
--- a/plugins/channelmimo/interferometer/interferometercorr.h
+++ b/plugins/channelmimo/interferometer/interferometercorr.h
@@ -35,37 +35,33 @@ public:
InterferometerCorrelator(int fftSize);
~InterferometerCorrelator();
- void setCorrIndex(unsigned int corrIndex) { m_corrIndex = corrIndex; }
void setCorrType(InterferometerSettings::CorrelationType corrType) { m_corrType = corrType; }
- void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int argIndex);
+ InterferometerSettings::CorrelationType getCorrType() const { return m_corrType; }
+ void performCorr(const SampleVector& data0, const SampleVector& data1);
+ int getFullFFTSize() const { return 2*m_fftSize; }
SampleVector m_scorr; //!< raw correlation result (spectrum) - Sample vector expected
SampleVector m_tcorr; //!< correlation result (time or spectrum inverse FFT) - Sample vector expected
+ int m_processed; //!< number of samples processed at the end of correlation
+ int m_remaining; //!< number of samples remaining at the end of correlation
signals:
void dataReady(int start, int stop);
private:
- void feedOp(
- const SampleVector::const_iterator& begin,
- const SampleVector::const_iterator& end,
- unsigned int argIndex,
- Sample complexOp(std::complex& a, const std::complex& b)
- );
- void feedCorr(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int argIndex);
- void processFFTBlocks(unsigned int argIndex, unsigned int dataIndex, int length);
- void processFFT(unsigned int argIndex, int blockIndex);
+ void performOpCorr(const SampleVector& data0, const SampleVector& data1, Sample sampleOp(const Sample& a, const Sample& b));
+ void performFFTCorr(const SampleVector& data0, const SampleVector& data1);
+ void adjustSCorrSize(int size);
+ void adjustTCorrSize(int size);
InterferometerSettings::CorrelationType m_corrType;
int m_fftSize; //!< FFT length
FFTEngine *m_fft[2]; //!< FFT engines
FFTEngine *m_invFFT; //!< Inverse FFT engine
FFTWindow m_window; //!< FFT window
- std::complex *m_data[2]; //!< from input
std::complex *m_dataj; //!< conjuate of FFT transform
- unsigned int m_dataIndex[2]; //!< Current sample index in A
- unsigned int m_corrIndex; //!< Input index on which correlation is actioned
- static const unsigned int m_nbFFTBlocks; //!< number of buffered FFT blocks
+ int m_scorrSize; //!< spectrum correlations vector size
+ int m_tcorrSize; //!< time correlations vector size
};
#endif // INCLUDE_INTERFEROMETERCORR_H
diff --git a/plugins/channelmimo/interferometer/interferometergui.cpp b/plugins/channelmimo/interferometer/interferometergui.cpp
new file mode 100644
index 000000000..dc2730d44
--- /dev/null
+++ b/plugins/channelmimo/interferometer/interferometergui.cpp
@@ -0,0 +1,300 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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
+
+#include "device/deviceuiset.h"
+#include "gui/basicchannelsettingsdialog.h"
+#include "dsp/hbfilterchainconverter.h"
+
+#include "interferometergui.h"
+#include "interferometer.h"
+#include "ui_interferometergui.h"
+
+InterferometerGUI* InterferometerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOSampleSink *channelMIMO)
+{
+ InterferometerGUI* gui = new InterferometerGUI(pluginAPI, deviceUISet, channelMIMO);
+ return gui;
+}
+
+void InterferometerGUI::destroy()
+{
+ delete this;
+}
+
+void InterferometerGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString InterferometerGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 InterferometerGUI::getCenterFrequency() const {
+ return 0;
+}
+
+void InterferometerGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ (void) centerFrequency;
+}
+
+void InterferometerGUI::resetToDefaults()
+{
+ m_settings.resetToDefaults();
+ displaySettings();
+ applySettings(true);
+}
+
+QByteArray InterferometerGUI::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool InterferometerGUI::deserialize(const QByteArray& data)
+{
+ if(m_settings.deserialize(data)) {
+ displaySettings();
+ applySettings(true);
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool InterferometerGUI::handleMessage(const Message& message)
+{
+ if (Interferometer::MsgSampleRateNotification::match(message))
+ {
+ Interferometer::MsgSampleRateNotification& notif = (Interferometer::MsgSampleRateNotification&) message;
+ m_channelMarker.setBandwidth(notif.getSampleRate());
+ m_sampleRate = notif.getSampleRate();
+ displayRateAndShift();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+InterferometerGUI::InterferometerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOSampleSink *channelMIMO, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::InterferometerGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceUISet(deviceUISet),
+ m_sampleRate(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_interferometer = (Interferometer*) channelMIMO;
+ m_interferometer->setMessageQueueToGUI(getInputMessageQueue());
+
+ m_channelMarker.blockSignals(true);
+ m_channelMarker.setColor(m_settings.m_rgbColor);
+ m_channelMarker.setCenterFrequency(0);
+ m_channelMarker.setTitle("Interferometer");
+ m_channelMarker.blockSignals(false);
+ m_channelMarker.setVisible(true); // activate signal on the last setting only
+
+ m_settings.setChannelMarker(&m_channelMarker);
+
+ // m_deviceUISet->registerRxChannelInstance(LocalSink::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);
+}
+
+InterferometerGUI::~InterferometerGUI()
+{
+ //m_deviceUISet->removeRxChannelInstance(this);
+ delete m_interferometer; // TODO: check this: when the GUI closes it has to delete the demodulator
+ delete ui;
+}
+
+void InterferometerGUI::blockApplySettings(bool block)
+{
+ m_doApplySettings = !block;
+}
+
+void InterferometerGUI::applySettings(bool force)
+{
+ if (m_doApplySettings)
+ {
+ setTitleColor(m_channelMarker.getColor());
+
+ Interferometer::MsgConfigureInterferometer* message = Interferometer::MsgConfigureInterferometer::create(m_settings, force);
+ m_interferometer->getInputMessageQueue()->push(message);
+ }
+}
+
+void InterferometerGUI::applyChannelSettings()
+{
+ if (m_doApplySettings)
+ {
+ Interferometer::MsgConfigureChannelizer *msgChan = Interferometer::MsgConfigureChannelizer::create(
+ m_settings.m_log2Decim,
+ m_settings.m_filterChainHash);
+ m_interferometer->getInputMessageQueue()->push(msgChan);
+ }
+}
+
+void InterferometerGUI::displaySettings()
+{
+ m_channelMarker.blockSignals(true);
+ m_channelMarker.setCenterFrequency(0);
+ m_channelMarker.setTitle(m_settings.m_title);
+ m_channelMarker.setBandwidth(m_sampleRate);
+ m_channelMarker.setMovable(false); // do not let user move the center arbitrarily
+ 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->decimationFactor->setCurrentIndex(m_settings.m_log2Decim);
+ applyDecimation();
+ blockApplySettings(false);
+}
+
+void InterferometerGUI::displayRateAndShift()
+{
+ int shift = m_shiftFrequencyFactor * m_sampleRate;
+ double channelSampleRate = ((double) m_sampleRate) / (1<offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
+ ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
+ m_channelMarker.setCenterFrequency(shift);
+ m_channelMarker.setBandwidth(channelSampleRate);
+}
+
+void InterferometerGUI::leaveEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(false);
+}
+
+void InterferometerGUI::enterEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(true);
+}
+
+void InterferometerGUI::handleSourceMessages()
+{
+ Message* message;
+
+ while ((message = getInputMessageQueue()->pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void InterferometerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+ (void) widget;
+ (void) rollDown;
+}
+
+void InterferometerGUI::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();
+ }
+
+ resetContextMenuType();
+}
+
+void InterferometerGUI::on_decimationFactor_currentIndexChanged(int index)
+{
+ m_settings.m_log2Decim = index;
+ applyDecimation();
+}
+
+void InterferometerGUI::on_position_valueChanged(int value)
+{
+ m_settings.m_filterChainHash = value;
+ applyPosition();
+}
+
+void InterferometerGUI::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 InterferometerGUI::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();
+ applyChannelSettings();
+}
+
+void InterferometerGUI::tick()
+{
+ if (++m_tickCount == 20) { // once per second
+ m_tickCount = 0;
+ }
+}
diff --git a/plugins/channelmimo/interferometer/interferometergui.h b/plugins/channelmimo/interferometer/interferometergui.h
new file mode 100644
index 000000000..db932138d
--- /dev/null
+++ b/plugins/channelmimo/interferometer/interferometergui.h
@@ -0,0 +1,100 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_INTERFEROMETERGUI_H
+#define INCLUDE_INTERFEROMETERGUI_H
+
+#include "plugin/plugininstancegui.h"
+#include "gui/rollupwidget.h"
+#include "dsp/channelmarker.h"
+#include "util/movingaverage.h"
+#include "util/messagequeue.h"
+
+#include "interferometersettings.h"
+
+class PluginAPI;
+class DeviceUISet;
+class MIMOSampleSink;
+class Interferometer;
+class SpectrumVis;
+class ScopeVis;
+
+namespace Ui {
+ class InterferometerGUI;
+}
+
+class InterferometerGUI : public RollupWidget, public PluginInstanceGUI {
+ Q_OBJECT
+public:
+ static InterferometerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOSampleSink *mimoChannel);
+
+ virtual void destroy();
+ virtual void setName(const QString& name);
+ virtual QString getName() const;
+ virtual void resetToDefaults();
+ virtual qint64 getCenterFrequency() const;
+ virtual void setCenterFrequency(qint64 centerFrequency);
+ virtual QByteArray serialize() const;
+ virtual bool deserialize(const QByteArray& data);
+ virtual MessageQueue* getInputMessageQueue();
+ virtual bool handleMessage(const Message& message);
+
+public slots:
+ void channelMarkerChangedByCursor();
+ void channelMarkerHighlightedByCursor();
+
+private:
+ Ui::InterferometerGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceUISet* m_deviceUISet;
+ ChannelMarker m_channelMarker;
+ InterferometerSettings m_settings;
+ int m_sampleRate;
+ double m_shiftFrequencyFactor; //!< Channel frequency shift factor
+ bool m_doApplySettings;
+ MovingAverageUtil m_channelPowerAvg;
+ Interferometer *m_interferometer;
+ SpectrumVis* m_spectrumVis;
+ ScopeVis* m_scopeVis;
+ MessageQueue m_inputMessageQueue;
+ uint32_t m_tickCount;
+
+ explicit InterferometerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOSampleSink *rxChannel, QWidget* parent = nullptr);
+ virtual ~InterferometerGUI();
+
+ void blockApplySettings(bool block);
+ void applySettings(bool force = false);
+ void applyChannelSettings();
+ void applyDecimation();
+ void applyPosition();
+ void displaySettings();
+ void displayRateAndShift();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+
+private slots:
+ void handleSourceMessages();
+ void on_decimationFactor_currentIndexChanged(int index);
+ void on_position_valueChanged(int value);
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDialogCalled(const QPoint& p);
+ void handleInputMessages();
+ void tick();
+};
+
+#endif // INCLUDE_INTERFEROMETERGUI_H
\ No newline at end of file
diff --git a/plugins/channelmimo/interferometer/interferometergui.ui b/plugins/channelmimo/interferometer/interferometergui.ui
index 011650e80..6b96cabc0 100644
--- a/plugins/channelmimo/interferometer/interferometergui.ui
+++ b/plugins/channelmimo/interferometer/interferometergui.ui
@@ -1,7 +1,7 @@
- ChannelAnalyzerGUI
-
+ InterferometerGUI
+
0
@@ -54,141 +54,29 @@
2
-
-
-
- 2
+
+
+ 3
-
-
+
-
-
-
-
- 16
- 0
-
-
+
- Df
+ Dec
-
-
-
-
- 0
- 0
-
-
-
-
- 32
- 16
-
-
-
-
- Liberation Mono
- 12
-
-
-
- PointingHandCursor
-
-
- Qt::StrongFocus
-
-
- Demod shift frequency from center in Hz
-
-
-
- -
-
-
-
-
-
-
-
- 26
- 26
- 26
-
-
-
-
-
-
- 255
- 255
- 255
-
-
-
-
-
-
-
-
- 26
- 26
- 26
-
-
-
-
-
-
- 255
- 255
- 255
-
-
-
-
-
-
-
-
- 118
- 118
- 117
-
-
-
-
-
-
- 255
- 255
- 255
-
-
-
-
-
-
-
- Hz
-
-
-
-
-
- -
-
-
-
-
+
- 50
+ 55
16777215
- Channel decimation
+ Decimation factor
-
@@ -228,18 +116,18 @@
-
-
+
- 80
+ 50
0
- Channel final sample rate
+ Effective channel rate (kS/s)
- 00000.0 kS/s
+ 0000k
Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -247,63 +135,125 @@
-
-
-
- Select input signal
+
+
+
+ 50
+ 0
+
+
+
+ Filter chain stages left to right (L: low, C: center, H: high)
+
+
+ LLLLLL
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 85
+ 0
+
+
+
+ Offset frequency with thousands separator (Hz)
+
+
+ -9,999,999 Hz
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 52
+ 0
+
+
+
+ Channel power
+
+
+ Qt::LeftToRight
+
+
+ -100.0 dB
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- Sig
-
-
- -
-
- Lock
-
-
- -
-
- ACorr
-
-
-
-
-
- Qt::Horizontal
+
+
+ 10
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
- 52
- 0
-
-
-
- Channel power
-
-
- Qt::LeftToRight
-
-
- -100.0 dB
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
+
-
+
+
+ Pos
+
+
+
+ -
+
+
+ Center frequency position
+
+
+ 2
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 24
+ 0
+
+
+
+ Filter chain hash code
+
+
+ 000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
@@ -494,12 +444,6 @@
1
-
- ValueDialZ
- QWidget
-
- 1
-
GLScope
QWidget
diff --git a/plugins/channelmimo/interferometer/interferometersettings.cpp b/plugins/channelmimo/interferometer/interferometersettings.cpp
index da033a1c7..f870673ee 100644
--- a/plugins/channelmimo/interferometer/interferometersettings.cpp
+++ b/plugins/channelmimo/interferometer/interferometersettings.cpp
@@ -33,20 +33,31 @@ InterferometerSettings::InterferometerSettings() :
void InterferometerSettings::resetToDefaults()
{
- m_inputFrequencyOffset = 0;
m_correlationType = CorrelationAdd;
m_rgbColor = QColor(128, 128, 128).rgb();
m_title = "Interferometer";
+ m_log2Decim = 0;
+ m_filterChainHash = 0;
+ m_reverseAPIAddress = "127.0.0.1";
+ m_reverseAPIPort = 8888;
+ m_reverseAPIDeviceIndex = 0;
+ m_reverseAPIChannelIndex = 0;
}
QByteArray InterferometerSettings::serialize() const
{
SimpleSerializer s(1);
- s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, (int) m_correlationType);
s.writeU32(3, m_rgbColor);
s.writeString(4, m_title);
+ s.writeU32(5, m_log2Decim);
+ s.writeU32(6, m_filterChainHash);
+ 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);
if (m_spectrumGUI) {
s.writeBlob(20, m_spectrumGUI->serialize());
@@ -73,12 +84,29 @@ bool InterferometerSettings::deserialize(const QByteArray& data)
{
QByteArray bytetmp;
int tmp;
+ quint32 utmp;
- d.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(2, &tmp, 0);
m_correlationType = (CorrelationType) tmp;
d.readU32(3, &m_rgbColor);
d.readString(4, &m_title, "Interpolator");
+ d.readU32(5, &utmp, 0);
+ m_log2Decim = utmp > 6 ? 6 : utmp;
+ d.readU32(6, &m_filterChainHash, 0);
+ d.readBool(7, &m_useReverseAPI, false);
+ d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
+ d.readU32(9, &utmp, 0);
+
+ if ((utmp > 1023) && (utmp < 65535)) {
+ m_reverseAPIPort = utmp;
+ } else {
+ m_reverseAPIPort = 8888;
+ }
+
+ d.readU32(10, &utmp, 0);
+ m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
+ d.readU32(11, &utmp, 0);
+ m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
if (m_spectrumGUI) {
d.readBlob(20, &bytetmp);
diff --git a/plugins/channelmimo/interferometer/interferometersettings.h b/plugins/channelmimo/interferometer/interferometersettings.h
index d071cb3bb..860d5ced4 100644
--- a/plugins/channelmimo/interferometer/interferometersettings.h
+++ b/plugins/channelmimo/interferometer/interferometersettings.h
@@ -32,10 +32,17 @@ struct InterferometerSettings
CorrelationCorrelation
};
- qint32 m_inputFrequencyOffset;
CorrelationType m_correlationType;
quint32 m_rgbColor;
QString m_title;
+ uint32_t m_log2Decim;
+ uint32_t m_filterChainHash;
+ bool m_useReverseAPI;
+ QString m_reverseAPIAddress;
+ uint16_t m_reverseAPIPort;
+ uint16_t m_reverseAPIDeviceIndex;
+ uint16_t m_reverseAPIChannelIndex;
+
Serializable *m_channelMarker;
Serializable *m_spectrumGUI;
Serializable *m_scopeGUI;
diff --git a/plugins/channelmimo/interferometer/interferometersink.cpp b/plugins/channelmimo/interferometer/interferometersink.cpp
index 3775a8b91..569a6fd91 100644
--- a/plugins/channelmimo/interferometer/interferometersink.cpp
+++ b/plugins/channelmimo/interferometer/interferometersink.cpp
@@ -15,31 +15,133 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
+#include
+
+#include "dsp/downchannelizer.h"
#include "interferometersink.h"
-InterferometerSink::InterferometerSink(InterferometerCorrelator *correlator) :
- m_processingUnit(false),
- m_correlator(correlator)
-{}
+
+MESSAGE_CLASS_DEFINITION(InterferometerSink::MsgConfigureChannelizer, Message)
+
+InterferometerSink::InterferometerSink() :
+ m_correlator(4096),
+ m_spectrumSink(nullptr),
+ m_scopeSink(nullptr)
+{
+ for (int i = 0; i < 2; i++)
+ {
+ m_sinks[i].setStreamIndex(i);
+ m_channelizers[i] = new DownChannelizer(&m_sinks[i]);
+ QObject::connect(
+ &m_sinkBuffers[i],
+ &SampleSinkVector::dataReady,
+ this,
+ [=](){ this->handleSinkBuffer(i); },
+ Qt::QueuedConnection
+ );
+ }
+
+ connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
+}
InterferometerSink::~InterferometerSink()
-{}
-
-void InterferometerSink::start()
-{}
-
-void InterferometerSink::stop()
-{}
-
-void InterferometerSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
{
- (void) positiveOnly;
- (void) begin;
- (void) end;
+ for (int i = 0; i < 2; i++)
+ {
+ delete m_channelizers[i];
+ }
+}
+
+void InterferometerSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex)
+{
+ if (streamIndex > 1) {
+ return;
+ }
+
+ m_sinkBuffers[streamIndex].write(begin, end);
+}
+
+void InterferometerSink::handleSinkBuffer(unsigned int sinkIndex)
+{
+ SampleVector::iterator vbegin;
+ SampleVector::iterator vend;
+ m_sinkBuffers[sinkIndex].read(vbegin, vend);
+ m_channelizers[sinkIndex]->feed(vbegin, vend, false);
+
+ if (sinkIndex == 1) {
+ run();
+ }
+}
+
+void InterferometerSink::run()
+{
+ m_correlator.performCorr(m_sinks[0].getData(), m_sinks[1].getData());
+
+ if (m_scopeSink) {
+ m_scopeSink->feed(m_correlator.m_tcorr.begin(), m_correlator.m_tcorr.begin() + m_correlator.m_processed, false);
+ }
+
+ if (m_spectrumSink)
+ {
+ if (m_correlator.getCorrType() == InterferometerSettings::CorrelationCorrelation) {
+ m_spectrumSink->feed(m_correlator.m_scorr.begin(), m_correlator.m_scorr.begin() + m_correlator.m_processed, false);
+ } else {
+ m_spectrumSink->feed(m_correlator.m_tcorr.begin(), m_correlator.m_tcorr.begin() + m_correlator.m_processed, false);
+ }
+ }
+
+ if (m_correlator.m_remaining != 0)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ std::copy(
+ m_sinks[i].getData().begin() + m_correlator.m_processed,
+ m_sinks[i].getData().begin() + m_correlator.m_processed + m_correlator.m_remaining,
+ m_sinks[i].getData().begin()
+ );
+ }
+ }
+
+ m_sinks[0].setDataStart(m_correlator.m_remaining);
+ m_sinks[1].setDataStart(m_correlator.m_remaining);
+}
+
+void InterferometerSink::handleInputMessages()
+{
+ Message* message;
+
+ while ((message = m_inputMessageQueue.pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
}
bool InterferometerSink::handleMessage(const Message& cmd)
{
- (void) cmd;
- return false;
+ if (MsgConfigureChannelizer::match(cmd))
+ {
+ MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
+ int log2Decim = cfg.getLog2Decim();
+ int filterChainHash = cfg.getFilterChainHash();
+
+ qDebug() << "InterferometerSink::handleMessage: MsgConfigureChannelizer:"
+ << " log2Decim: " << log2Decim
+ << " filterChainHash: " << filterChainHash;
+
+ for (int i = 0; i < 2; i++)
+ {
+ m_channelizers[i]->set(m_channelizers[i]->getInputMessageQueue(),
+ log2Decim,
+ filterChainHash);
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
}
\ No newline at end of file
diff --git a/plugins/channelmimo/interferometer/interferometersink.h b/plugins/channelmimo/interferometer/interferometersink.h
index da929b76d..97745d86f 100644
--- a/plugins/channelmimo/interferometer/interferometersink.h
+++ b/plugins/channelmimo/interferometer/interferometersink.h
@@ -18,27 +18,66 @@
#ifndef INCLUDE_INTERFEROMETERSINK_H
#define INCLUDE_INTERFEROMETERSINK_H
-#include "dsp/basebandsamplesink.h"
+#include
-class InterferometerCorrelator;
+#include "dsp/mimosamplesink.h"
+#include "dsp/samplesinkvector.h"
+#include "interferometerstreamsink.h"
+#include "interferometercorr.h"
-class InterferometerSink : public BasebandSampleSink
+class DownChannelizer;
+class BasebandSampleSink;
+
+class InterferometerSink : public QObject
{
+ Q_OBJECT
public:
- InterferometerSink(InterferometerCorrelator *correlator);
- virtual ~InterferometerSink();
+ class MsgConfigureChannelizer : public Message {
+ MESSAGE_CLASS_DECLARATION
- virtual void start();
- virtual void stop();
- virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
- virtual bool handleMessage(const Message& cmd);
+ public:
+ int getLog2Decim() const { return m_log2Decim; }
+ int getFilterChainHash() const { return m_filterChainHash; }
- void setProcessingUnit(bool) { m_processingUnit = m_processingUnit; }
- bool isProcessingUnit() const { return m_processingUnit; }
+ static MsgConfigureChannelizer* create(unsigned int log2Decim, unsigned int filterChainHash) {
+ return new MsgConfigureChannelizer(log2Decim, filterChainHash);
+ }
+
+ private:
+ unsigned int m_log2Decim;
+ unsigned int m_filterChainHash;
+
+ MsgConfigureChannelizer(unsigned int log2Decim, unsigned int filterChainHash) :
+ Message(),
+ m_log2Decim(log2Decim),
+ m_filterChainHash(filterChainHash)
+ { }
+ };
+
+ InterferometerSink();
+ ~InterferometerSink();
+ MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
+
+ void setSpectrumSink(BasebandSampleSink *spectrumSink) { m_spectrumSink = spectrumSink; }
+ void setScopeSink(BasebandSampleSink *scopeSink) { m_scopeSink = scopeSink; }
+
+ void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex);
private:
- bool m_processingUnit; //!< True if it is responsible of starting the correlation process
- InterferometerCorrelator *m_correlator;
+ void run();
+ bool handleMessage(const Message& cmd);
+
+ InterferometerCorrelator m_correlator;
+ SampleSinkVector m_sinkBuffers[2];
+ InterferometerStreamSink m_sinks[2];
+ DownChannelizer *m_channelizers[2];
+ BasebandSampleSink *m_spectrumSink;
+ BasebandSampleSink *m_scopeSink;
+ MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
+
+private slots:
+ void handleSinkBuffer(unsigned int sinkIndex); //!< Handle data when samples have to be processed
+ void handleInputMessages();
};
#endif // INCLUDE_INTERFEROMETERSINK_H
diff --git a/plugins/channelmimo/interferometer/interferometerstreamsink.cpp b/plugins/channelmimo/interferometer/interferometerstreamsink.cpp
new file mode 100644
index 000000000..7e9a12e26
--- /dev/null
+++ b/plugins/channelmimo/interferometer/interferometerstreamsink.cpp
@@ -0,0 +1,70 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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
+#include "dsp/downchannelizer.h"
+
+#include "interferometerstreamsink.h"
+
+InterferometerStreamSink::InterferometerStreamSink() :
+ m_streamIndex(0),
+ m_dataSize(0),
+ m_bufferSize(0),
+ m_sampleRate(48000),
+ m_settingsMutex(QMutex::Recursive)
+{}
+
+InterferometerStreamSink::~InterferometerStreamSink()
+{}
+
+void InterferometerStreamSink::start()
+{}
+
+void InterferometerStreamSink::stop()
+{}
+
+void InterferometerStreamSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
+{
+ m_settingsMutex.lock();
+ m_dataSize = (end - begin) + m_dataStart;
+
+ if (m_dataSize > m_bufferSize) {
+ m_data.resize(m_dataSize);
+ }
+
+ std::copy(begin, end, m_data.begin() + m_dataStart);
+ m_settingsMutex.unlock();
+}
+
+bool InterferometerStreamSink::handleMessage(const Message& cmd)
+{
+ if (DownChannelizer::MsgChannelizerNotification::match(cmd))
+ {
+ DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
+
+ qDebug() << "InterferometerStreamSink::handleMessage: MsgChannelizerNotification:"
+ << " inputSampleRate: " << notif.getSampleRate()
+ << " inputFrequencyOffset: " << notif.getFrequencyOffset();
+ m_sampleRate = notif.getSampleRate();
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/plugins/channelmimo/interferometer/interferometerstreamsink.h b/plugins/channelmimo/interferometer/interferometerstreamsink.h
new file mode 100644
index 000000000..a3a7031a7
--- /dev/null
+++ b/plugins/channelmimo/interferometer/interferometerstreamsink.h
@@ -0,0 +1,58 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 SDRBASE_INTERFEROMETERSTREAMSINK_H_
+#define SDRBASE_INTERFEROMETERSTREAMSINK_H_
+
+#include
+
+#include "dsp/basebandsamplesink.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+
+
+class InterferometerStreamSink : public BasebandSampleSink
+{
+public:
+ InterferometerStreamSink();
+ virtual ~InterferometerStreamSink();
+
+ virtual void start();
+ virtual void stop();
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
+ virtual bool handleMessage(const Message& cmd); //!< Processing of a message. Returns true if message has actually been processed
+
+ unsigned int getStreamIndex() const { return m_streamIndex; }
+ void setStreamIndex(unsigned int streamIndex) { m_streamIndex = streamIndex; }
+ SampleVector& getData() { return m_data; }
+ void setDataStart(int dataStart) { m_dataStart = dataStart; }
+
+private:
+ unsigned int m_streamIndex;
+ SampleVector m_data;
+ int m_dataSize;
+ int m_bufferSize;
+ int m_dataStart;
+
+ int m_sampleRate;
+ uint32_t m_log2Decim;
+ uint32_t m_filterChainHash;
+ QMutex m_settingsMutex;
+};
+
+
+#endif // SDRBASE_INTERFEROMETERSTREAMSINK_H_
\ No newline at end of file
diff --git a/plugins/channelmimo/interferometer/interferometerwebapiadapter.cpp b/plugins/channelmimo/interferometer/interferometerwebapiadapter.cpp
index 5c0ab8fe4..e16f04c6c 100644
--- a/plugins/channelmimo/interferometer/interferometerwebapiadapter.cpp
+++ b/plugins/channelmimo/interferometer/interferometerwebapiadapter.cpp
@@ -46,7 +46,6 @@ void InterferometerWebAPIAdapter::webapiFormatChannelSettings(
const GLScopeSettings& scopeSettings,
const GLSpectrumSettings& spectrumSettings)
{
- response.getInterferometerSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getInterferometerSettings()->setCorrelationType((int) settings.m_correlationType);
response.getInterferometerSettings()->setRgbColor(settings.m_rgbColor);
response.getInterferometerSettings()->setTitle(new QString(settings.m_title));
@@ -154,9 +153,6 @@ void InterferometerWebAPIAdapter::webapiUpdateChannelSettings(
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
- if (channelSettingsKeys.contains("inputFrequencyOffset")) {
- settings.m_inputFrequencyOffset = response.getInterferometerSettings()->getInputFrequencyOffset();
- }
if (channelSettingsKeys.contains("correlationType")) {
settings.m_correlationType = (InterferometerSettings::CorrelationType) response.getInterferometerSettings()->getCorrelationType();
}
diff --git a/sdrbase/dsp/mimosamplesink.cpp b/sdrbase/dsp/mimosamplesink.cpp
new file mode 100644
index 000000000..eeaa31178
--- /dev/null
+++ b/sdrbase/dsp/mimosamplesink.cpp
@@ -0,0 +1,40 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 "mimosamplesink.h"
+
+MIMOSampleSink::MIMOSampleSink()
+{
+ connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
+}
+
+MIMOSampleSink::~MIMOSampleSink()
+{
+}
+
+void MIMOSampleSink::handleInputMessages()
+{
+ Message* message;
+
+ while ((message = m_inputMessageQueue.pop()) != 0)
+ {
+ if (handleMessage(*message)) {
+ delete message;
+ }
+ }
+}
diff --git a/sdrbase/dsp/mimosamplesink.h b/sdrbase/dsp/mimosamplesink.h
new file mode 100644
index 000000000..dc3f82167
--- /dev/null
+++ b/sdrbase/dsp/mimosamplesink.h
@@ -0,0 +1,50 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2019 F4EXB //
+// written by Edouard Griffiths //
+// //
+// 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 SDRBASE_MIMOSAMPLESINK_H
+#define SDRBASE_MIMOSAMPLESINK_H
+
+#include
+
+#include "export.h"
+#include "dsp/dsptypes.h"
+#include "util/messagequeue.h"
+#include "util/message.h"
+
+
+class SDRBASE_API MIMOSampleSink : public QObject {
+ Q_OBJECT
+public:
+ MIMOSampleSink();
+ virtual ~MIMOSampleSink();
+
+ virtual void start() = 0;
+ virtual void stop() = 0;
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex) = 0;
+ virtual bool handleMessage(const Message& cmd) = 0; //!< Processing of a message. Returns true if message has actually been processed
+
+ MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
+
+protected:
+ MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
+
+protected slots:
+ void handleInputMessages();
+};
+
+#endif // SDRBASE_MIMOSAMPLESINK_H
\ No newline at end of file
diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html
index c73a52c0c..d9cfbd1c3 100644
--- a/sdrbase/resources/webapi/doc/html2/index.html
+++ b/sdrbase/resources/webapi/doc/html2/index.html
@@ -157,7 +157,7 @@ h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify"
});