pull/714/head
f4exb 2020-11-10 16:38:12 +01:00
rodzic f8125cbb68
commit 26c7821d68
28 zmienionych plików z 3953 dodań i 25 usunięć

Wyświetl plik

@ -38,6 +38,7 @@ else()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${BUILD_PLUGINSSRV_DIR}")
endif()
add_subdirectory(channelmimo)
add_subdirectory(channelrx)
add_subdirectory(channeltx)
add_subdirectory(feature)

Wyświetl plik

@ -0,0 +1,5 @@
project(channelmimo)
if (NOT SERVER_MODE)
add_subdirectory(interferometer)
endif()

Wyświetl plik

@ -0,0 +1,62 @@
project(interferometer)
set(interferometer_SOURCES
interferometer.cpp
interferometercorr.cpp
interferometersettings.cpp
interferometerbaseband.cpp
interferometerstreamsink.cpp
interferometerplugin.cpp
interferometerwebapiadapter.cpp
)
set(interferometer_HEADERS
interferometer.h
interferometercorr.h
interferometersettings.h
interferometerbaseband.h
interferometerstreamsink.h
interferometerplugin.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})

Wyświetl plik

@ -0,0 +1,479 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QThread>
#include <QDebug>
#include <QBuffer>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "SWGChannelSettings.h"
#include "device/deviceapi.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/dspcommands.h"
#include "feature/feature.h"
#include "maincore.h"
#include "interferometerbaseband.h"
#include "interferometer.h"
MESSAGE_CLASS_DEFINITION(Interferometer::MsgConfigureInterferometer, Message)
MESSAGE_CLASS_DEFINITION(Interferometer::MsgBasebandNotification, Message)
const QString Interferometer::m_channelIdURI = "sdrangel.channel.interferometer";
const QString Interferometer::m_channelId = "Interferometer";
const int Interferometer::m_fftSize = 4096;
Interferometer::Interferometer(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamMIMO),
m_deviceAPI(deviceAPI),
m_spectrumVis(SDR_RX_SCALEF),
m_scopeSink(nullptr),
m_guiMessageQueue(nullptr),
m_frequencyOffset(0),
m_deviceSampleRate(48000)
{
setObjectName(m_channelId);
m_thread = new QThread(this);
m_basebandSink = new InterferometerBaseband(m_fftSize);
m_basebandSink->setSpectrumSink(&m_spectrumVis);
m_basebandSink->moveToThread(m_thread);
m_deviceAPI->addMIMOChannel(this);
m_deviceAPI->addMIMOChannelAPI(this);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
Interferometer::~Interferometer()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeMIMOChannel(this);
delete m_basebandSink;
delete m_thread;
}
void Interferometer::setScopeSink(BasebandSampleSink *scopeSink)
{
m_scopeSink = scopeSink;
m_basebandSink->setScopeSink(scopeSink);
}
void Interferometer::startSinks()
{
if (m_deviceSampleRate != 0) {
m_basebandSink->setBasebandSampleRate(m_deviceSampleRate);
}
m_basebandSink->reset();
m_thread->start();
InterferometerBaseband::MsgConfigureChannelizer *msg = InterferometerBaseband::MsgConfigureChannelizer::create(
m_settings.m_log2Decim, m_settings.m_filterChainHash);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void Interferometer::stopSinks()
{
m_thread->exit();
m_thread->wait();
}
void Interferometer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex)
{
m_basebandSink->feed(begin, end, sinkIndex);
}
void Interferometer::pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int sourceIndex)
{
(void) begin;
(void) nbSamples;
(void) sourceIndex;
}
void Interferometer::applySettings(const InterferometerSettings& settings, bool force)
{
qDebug() << "Interferometer::applySettings: "
<< "m_correlationType: " << settings.m_correlationType
<< "m_filterChainHash: " << settings.m_filterChainHash
<< "m_log2Decim: " << settings.m_log2Decim
<< "m_phase: " << settings.m_phase
<< "m_useReverseAPI: " << settings.m_useReverseAPI
<< "m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< "m_reverseAPIPort: " << settings.m_reverseAPIPort
<< "m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< "m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< "m_title: " << settings.m_title;
QList<QString> reverseAPIKeys;
if ((m_settings.m_correlationType != settings.m_correlationType) || force) {
reverseAPIKeys.append("correlationType");
}
if ((m_settings.m_filterChainHash != settings.m_filterChainHash) || force) {
reverseAPIKeys.append("filterChainHash");
}
if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) {
reverseAPIKeys.append("log2Decim");
}
if ((m_settings.m_phase != settings.m_phase) || force) {
reverseAPIKeys.append("phase");
}
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_log2Decim != settings.m_log2Decim)
|| (m_settings.m_filterChainHash != settings.m_filterChainHash) || force)
{
InterferometerBaseband::MsgConfigureChannelizer *msg = InterferometerBaseband::MsgConfigureChannelizer::create(
settings.m_log2Decim, settings.m_filterChainHash);
m_basebandSink->getInputMessageQueue()->push(msg);
}
if ((m_settings.m_correlationType != settings.m_correlationType) || force)
{
InterferometerBaseband::MsgConfigureCorrelation *msg = InterferometerBaseband::MsgConfigureCorrelation::create(
settings.m_correlationType);
m_basebandSink->getInputMessageQueue()->push(msg);
}
if ((m_settings.m_phase != settings.m_phase) || force) {
m_basebandSink->setPhase(settings.m_phase);
}
if (m_featuresSettingsFeedback.size() > 0) {
featuresSendSettings(reverseAPIKeys, settings, force);
}
m_settings = settings;
}
void Interferometer::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
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 if (DSPMIMOSignalNotification::match(cmd))
{
DSPMIMOSignalNotification& notif = (DSPMIMOSignalNotification&) cmd;
qDebug() << "Interferometer::handleMessage: DSPMIMOSignalNotification:"
<< " inputSampleRate: " << notif.getSampleRate()
<< " centerFrequency: " << notif.getCenterFrequency()
<< " sourceElseSink: " << notif.getSourceOrSink()
<< " streamIndex: " << notif.getIndex();
if (notif.getSourceOrSink()) // deals with source messages only
{
m_deviceSampleRate = notif.getSampleRate();
calculateFrequencyOffset(); // This is when device sample rate changes
// Notify baseband sink of input sample rate change
InterferometerBaseband::MsgSignalNotification *sig = InterferometerBaseband::MsgSignalNotification::create(
m_deviceSampleRate, notif.getCenterFrequency(), notif.getIndex()
);
qDebug() << "Interferometer::handleMessage: DSPMIMOSignalNotification: push to sink";
m_basebandSink->getInputMessageQueue()->push(sig);
if (getMessageQueueToGUI())
{
qDebug() << "Interferometer::handleMessage: DSPMIMOSignalNotification: push to GUI";
MsgBasebandNotification *msg = MsgBasebandNotification::create(
notif.getSampleRate(), notif.getCenterFrequency());
getMessageQueueToGUI()->push(msg);
}
}
return true;
}
else
{
return false;
}
}
QByteArray Interferometer::serialize() const
{
return m_settings.serialize();
}
bool Interferometer::deserialize(const QByteArray& data)
{
(void) data;
if (m_settings.deserialize(data))
{
MsgConfigureInterferometer *msg = MsgConfigureInterferometer::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureInterferometer *msg = MsgConfigureInterferometer::create(m_settings, true);
m_inputMessageQueue.push(msg);
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;
}
void Interferometer::calculateFrequencyOffset()
{
double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Decim, m_settings.m_filterChainHash);
m_frequencyOffset = m_deviceSampleRate * shiftFactor;
}
void Interferometer::applyChannelSettings(uint32_t log2Decim, uint32_t filterChainHash)
{
InterferometerBaseband::MsgConfigureChannelizer *msg = InterferometerBaseband::MsgConfigureChannelizer::create(log2Decim, filterChainHash);
m_basebandSink->getInputMessageQueue()->push(msg);
}
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);
if (getMessageQueueToGUI()) // forward to GUI if any
{
MsgConfigureInterferometer *msgToGUI = MsgConfigureInterferometer::create(settings, force);
getMessageQueueToGUI()->push(msgToGUI);
}
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<QString>& channelSettingsKeys, const InterferometerSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
reply->setParent(buffer);
delete swgChannelSettings;
}
void Interferometer::featuresSendSettings(QList<QString>& channelSettingsKeys, const InterferometerSettings& settings, bool force)
{
QList<Feature*>::iterator it = m_featuresSettingsFeedback.begin();
MainCore *mainCore = MainCore::instance();
for (; it != m_featuresSettingsFeedback.end(); ++it)
{
if (mainCore->existsFeature(*it))
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
Feature::MsgChannelSettings *msg = Feature::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
(*it)->getInputMessageQueue()->push(msg);
}
else
{
m_featuresSettingsFeedback.removeOne(*it);
}
}
}
void Interferometer::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const InterferometerSettings& settings,
bool force
)
{
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);
}
}
void Interferometer::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "Interferometer::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("Interferometer::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

Wyświetl plik

@ -0,0 +1,185 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETER_H
#define INCLUDE_INTERFEROMETER_H
#include <vector>
#include <QNetworkRequest>
#include "dsp/mimochannel.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h"
#include "util/messagequeue.h"
#include "util/message.h"
#include "interferometersettings.h"
class QThread;
class DeviceAPI;
class InterferometerBaseband;
class QNetworkReply;
class QNetworkAccessManager;
class BasebandSampleSink;
class Interferometer: public MIMOChannel, public 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 MsgBasebandNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgBasebandNotification* create(int sampleRate, qint64 centerFrequency) {
return new MsgBasebandNotification(sampleRate, centerFrequency);
}
int getSampleRate() const { return m_sampleRate; }
qint64 getCenterFrequency() const { return m_centerFrequency; }
private:
MsgBasebandNotification(int sampleRate, qint64 centerFrequency) :
Message(),
m_sampleRate(sampleRate),
m_centerFrequency(centerFrequency)
{ }
int m_sampleRate;
qint64 m_centerFrequency;
};
Interferometer(DeviceAPI *deviceAPI);
virtual ~Interferometer();
virtual void destroy() { delete this; }
virtual void startSinks(); //!< thread start()
virtual void stopSinks(); //!< thread exit() and wait()
virtual void startSources() {}
virtual void stopSources() {}
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int sinkIndex);
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int sourceIndex);
virtual bool handleMessage(const Message& cmd); //!< Processing of a message. Returns true if message has actually been processed
virtual void getIdentifier(QString& id) { id = m_channelId; }
virtual const QString& getURI() const { return m_channelIdURI; }
virtual void getTitle(QString& title) { title = "Interferometer"; }
virtual qint64 getCenterFrequency() const { return m_frequencyOffset; }
uint32_t getDeviceSampleRate() const { return m_deviceSampleRate; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 2; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_frequencyOffset;
}
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; }
SpectrumVis *getSpectrumVis() { return &m_spectrumVis; }
void setScopeSink(BasebandSampleSink *scopeSink);
void applyChannelSettings(uint32_t log2Decim, uint32_t filterChainHash);
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;
static const int m_fftSize;
private:
DeviceAPI *m_deviceAPI;
QThread *m_thread;
SpectrumVis m_spectrumVis;
InterferometerBaseband* m_basebandSink;
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;
int64_t m_frequencyOffset;
uint32_t m_deviceSampleRate;
int m_count0, m_count1;
void applySettings(const InterferometerSettings& settings, bool force = false);
static void validateFilterChainHash(InterferometerSettings& settings);
void calculateFrequencyOffset();
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const InterferometerSettings& settings, bool force);
void featuresSendSettings(QList<QString>& channelSettingsKeys, const InterferometerSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const InterferometerSettings& settings,
bool force
);
private slots:
void handleInputMessages();
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_INTERFEROMETER_H

Wyświetl plik

@ -0,0 +1,258 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMutexLocker>
#include <QDebug>
#include "dsp/downchannelizer.h"
#include "dsp/basebandsamplesink.h"
#include "dsp/dspcommands.h"
#include "interferometerbaseband.h"
MESSAGE_CLASS_DEFINITION(InterferometerBaseband::MsgConfigureChannelizer, Message)
MESSAGE_CLASS_DEFINITION(InterferometerBaseband::MsgSignalNotification, Message)
MESSAGE_CLASS_DEFINITION(InterferometerBaseband::MsgConfigureCorrelation, Message)
InterferometerBaseband::InterferometerBaseband(int fftSize) :
m_correlator(fftSize),
m_spectrumSink(nullptr),
m_scopeSink(nullptr),
m_mutex(QMutex::Recursive)
{
m_sampleMIFifo.init(2, 96000 * 8);
m_vbegin.resize(2);
for (int i = 0; i < 2; i++)
{
m_sinks[i].setStreamIndex(i);
m_channelizers[i] = new DownChannelizer(&m_sinks[i]);
m_sizes[i] = 0;
}
QObject::connect(
&m_sampleMIFifo,
&SampleMIFifo::dataSyncReady,
this,
&InterferometerBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_lastStream = 0;
}
InterferometerBaseband::~InterferometerBaseband()
{
for (int i = 0; i < 2; i++)
{
delete m_channelizers[i];
}
}
void InterferometerBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleMIFifo.reset();
for (int i = 0; i < 2; i++) {
m_sinks[i].reset();
}
}
void InterferometerBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex)
{
if (streamIndex > 1) {
return;
}
if (streamIndex == m_lastStream) {
qWarning("InterferometerBaseband::feed: twice same stream in a row: %u", streamIndex);
}
m_lastStream = streamIndex;
m_vbegin[streamIndex] = begin;
m_sizes[streamIndex] = end - begin;
if (streamIndex == 1)
{
if (m_sizes[0] != m_sizes[1])
{
qWarning("InterferometerBaseband::feed: unequal sizes: [0]: %d [1]: %d", m_sizes[0], m_sizes[1]);
m_sampleMIFifo.writeSync(m_vbegin, std::min(m_sizes[0], m_sizes[1]));
}
else
{
m_sampleMIFifo.writeSync(m_vbegin, m_sizes[0]);
}
}
}
void InterferometerBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
const std::vector<SampleVector>& data = m_sampleMIFifo.getData();
unsigned int ipart1begin;
unsigned int ipart1end;
unsigned int ipart2begin;
unsigned int ipart2end;
while ((m_sampleMIFifo.fillSync() > 0) && (m_inputMessageQueue.size() == 0))
{
m_sampleMIFifo.readSync(ipart1begin, ipart1end, ipart2begin, ipart2end);
if (ipart1begin != ipart1end) { // first part of FIFO data
processFifo(data, ipart1begin, ipart1end);
}
if (ipart2begin != ipart2end) { // second part of FIFO data (used when block wraps around)
processFifo(data, ipart2begin, ipart2end);
}
}
}
void InterferometerBaseband::processFifo(const std::vector<SampleVector>& data, unsigned int ibegin, unsigned int iend)
{
for (unsigned int stream = 0; stream < 2; stream++) {
m_channelizers[stream]->feed(data[stream].begin() + ibegin, data[stream].begin() + iend);
}
run();
}
void InterferometerBaseband::run()
{
if (m_correlator.performCorr(m_sinks[0].getData(), m_sinks[0].getSize(), m_sinks[1].getData(), m_sinks[1].getSize()))
{
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::CorrelationFFT)
|| (m_correlator.getCorrType() == InterferometerSettings::CorrelationIFFT)
|| (m_correlator.getCorrType() == InterferometerSettings::CorrelationIFFT2)
|| (m_correlator.getCorrType() == InterferometerSettings::CorrelationIFFTStar))
{
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);
}
}
}
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[i],
m_sinks[i].getData().begin()
);
m_sinks[i].setDataStart(m_correlator.m_remaining[i]);
}
}
void InterferometerBaseband::handleInputMessages()
{
qDebug("InterferometerBaseband::handleInputMessage");
Message* message;
while ((message = m_inputMessageQueue.pop()) != 0)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool InterferometerBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureChannelizer::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
int log2Decim = cfg.getLog2Decim();
int filterChainHash = cfg.getFilterChainHash();
qDebug() << "InterferometerBaseband::handleMessage: MsgConfigureChannelizer:"
<< " log2Decim: " << log2Decim
<< " filterChainHash: " << filterChainHash;
for (int i = 0; i < 2; i++)
{
m_channelizers[i]->setDecimation(log2Decim, filterChainHash);
m_sinks[i].reset();
}
return true;
}
else if (MsgSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgSignalNotification& cfg = (MsgSignalNotification&) cmd;
int inputSampleRate = cfg.getInputSampleRate();
qint64 centerFrequency = cfg.getCenterFrequency();
int streamIndex = cfg.getStreamIndex();
qDebug() << "InterferometerBaseband::handleMessage: MsgSignalNotification:"
<< " inputSampleRate: " << inputSampleRate
<< " centerFrequency: " << centerFrequency
<< " streamIndex: " << streamIndex;
if (streamIndex < 2)
{
m_channelizers[streamIndex]->setBasebandSampleRate(inputSampleRate);
m_sinks[streamIndex].reset();
}
return true;
}
else if (MsgConfigureCorrelation::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureCorrelation& cfg = (MsgConfigureCorrelation&) cmd;
InterferometerSettings::CorrelationType correlationType = cfg.getCorrelationType();
qDebug() << "InterferometerBaseband::handleMessage: MsgConfigureCorrelation:"
<< " correlationType: " << correlationType;
m_correlator.setCorrType(correlationType);
return true;
}
else
{
qDebug("InterferometerBaseband::handleMessage: unhandled: %s", cmd.getIdentifier());
return false;
}
}
void InterferometerBaseband::setBasebandSampleRate(unsigned int sampleRate)
{
for (int istream = 0; istream < 2; istream++)
{
m_channelizers[istream]->setBasebandSampleRate(sampleRate);
m_sinks[istream].reset();
}
}

Wyświetl plik

@ -0,0 +1,136 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETERBASEBAND_H
#define INCLUDE_INTERFEROMETERBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplemififo.h"
#include "util/messagequeue.h"
#include "interferometerstreamsink.h"
#include "interferometercorr.h"
class DownChannelizer;
class BasebandSampleSink;
class InterferometerBaseband : public QObject
{
Q_OBJECT
public:
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 MsgConfigureCorrelation : public Message {
MESSAGE_CLASS_DECLARATION
public:
InterferometerSettings::CorrelationType getCorrelationType() const { return m_correlationType; }
static MsgConfigureCorrelation *create(InterferometerSettings::CorrelationType correlationType) {
return new MsgConfigureCorrelation(correlationType);
}
private:
InterferometerSettings::CorrelationType m_correlationType;
MsgConfigureCorrelation(InterferometerSettings::CorrelationType correlationType) :
Message(),
m_correlationType(correlationType)
{}
};
class MsgSignalNotification : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getInputSampleRate() const { return m_inputSampleRate; }
qint64 getCenterFrequency() const { return m_centerFrequency; }
int getStreamIndex() const { return m_streamIndex; }
static MsgSignalNotification* create(int inputSampleRate, qint64 centerFrequency, int streamIndex) {
return new MsgSignalNotification(inputSampleRate, centerFrequency, streamIndex);
}
private:
int m_inputSampleRate;
qint64 m_centerFrequency;
int m_streamIndex;
MsgSignalNotification(int inputSampleRate, qint64 centerFrequency, int streamIndex) :
Message(),
m_inputSampleRate(inputSampleRate),
m_centerFrequency(centerFrequency),
m_streamIndex(streamIndex)
{ }
};
InterferometerBaseband(int fftSize);
~InterferometerBaseband();
void reset();
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 setPhase(int phase) { m_correlator.setPhase(phase); }
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, unsigned int streamIndex);
void setBasebandSampleRate(unsigned int sampleRate);
private:
void processFifo(const std::vector<SampleVector>& data, unsigned int ibegin, unsigned int iend);
void run();
bool handleMessage(const Message& cmd);
InterferometerCorrelator m_correlator;
SampleMIFifo m_sampleMIFifo;
std::vector<SampleVector::const_iterator> m_vbegin;
int m_sizes[2];
InterferometerStreamSink m_sinks[2];
DownChannelizer *m_channelizers[2];
BasebandSampleSink *m_spectrumSink;
BasebandSampleSink *m_scopeSink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
QMutex m_mutex;
unsigned int m_lastStream;
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_INTERFEROMETERBASEBAND_H

Wyświetl plik

@ -0,0 +1,722 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <functional>
#include "dsp/dspengine.h"
#include "dsp/fftfactory.h"
#include "dsp/fftengine.h"
#include "interferometercorr.h"
std::complex<float> s2c(const Sample& s)
{
return std::complex<float>{s.real() / SDR_RX_SCALEF, s.imag() / SDR_RX_SCALEF};
}
std::complex<float> s2cNorm(const Sample& s)
{
float x = s.real() / SDR_RX_SCALEF;
float y = s.imag() / SDR_RX_SCALEF;
float m = sqrt(x*x + y*y);
return std::complex<float>{x/m, y/m};
}
Sample sFirst(const Sample& a, const Sample& b) {
return a;
}
Sample sSecond(const Sample& a, const Sample& b) {
return b;
}
Sample sSecondInv(const Sample& a, const Sample& b) {
return Sample{-b.real(), -b.imag()};
}
Sample sAdd(const Sample& a, const Sample& b) { //!< Sample addition
return Sample{(a.real()+b.real())/2, (a.imag()+b.imag())/2};
}
Sample sAddInv(const Sample& a, const Sample& b) { //!< Sample addition
return Sample{(a.real()-b.real())/2, (a.imag()+b.imag())/2};
}
Sample sMulConj(const Sample& a, const Sample& b) { //!< Sample multiply with conjugate
Sample s;
// Integer processing
int64_t ax = a.real();
int64_t ay = a.imag();
int64_t bx = b.real();
int64_t by = b.imag();
int64_t x = ax*bx + ay*by;
int64_t y = ay*bx - ax*by;
s.setReal(x>>(SDR_RX_SAMP_SZ-1));
s.setImag(y>>(SDR_RX_SAMP_SZ-1));
// Floating point processing (in practice there is no significant performance difference)
// float ax = a.real() / SDR_RX_SCALEF;
// float ay = a.imag() / SDR_RX_SCALEF;
// float bx = b.real() / SDR_RX_SCALEF;
// float by = b.imag() / SDR_RX_SCALEF;
// float x = ax*bx + ay*by;
// float y = ay*bx - ax*by;
// s.setReal(x*SDR_RX_SCALEF);
// s.setImag(y*SDR_RX_SCALEF);
return s;
}
Sample sMulConjInv(const Sample& a, const Sample& b) { //!< Sample multiply with conjugate
Sample s;
// Integer processing
int64_t ax = a.real();
int64_t ay = a.imag();
int64_t bx = -b.real();
int64_t by = -b.imag();
int64_t x = ax*bx + ay*by;
int64_t y = ay*bx - ax*by;
s.setReal(x>>(SDR_RX_SAMP_SZ-1));
s.setImag(y>>(SDR_RX_SAMP_SZ-1));
return s;
}
Sample invfft2s(const std::complex<float>& a) { //!< Complex float to Sample for 1 side time correlation
Sample s;
s.setReal(a.real()/2.0f);
s.setImag(a.imag()/2.0f);
return s;
}
Sample invfft2s2(const std::complex<float>& a) { //!< Complex float to Sample for 2 sides time correlation
Sample s;
s.setReal(a.real());
s.setImag(a.imag());
return s;
}
Sample invfft2star(const std::complex<float>& a) { //!< Complex float to Sample for 1 side time correlation
Sample s;
s.setReal(a.real()/2.82842712475f); // 2*sqrt(2)
s.setImag(a.imag()/2.82842712475f);
return s;
}
InterferometerCorrelator::InterferometerCorrelator(int fftSize) :
m_corrType(InterferometerSettings::CorrelationAdd),
m_fftSize(fftSize)
{
setPhase(0);
FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory();
m_window.create(FFTWindow::Function::Hanning, fftSize);
m_data0w.resize(m_fftSize);
m_data1w.resize(m_fftSize);
for (int i = 0; i < 2; i++)
{
m_fftSequences[i] = fftFactory->getEngine(2*fftSize, false, &m_fft[i]); // internally twice the data FFT size
m_fft2Sequences[i] = fftFactory->getEngine(fftSize, false, &m_fft2[i]);
}
m_invFFTSequence = fftFactory->getEngine(2*fftSize, true, &m_invFFT);
m_invFFT2Sequence = fftFactory->getEngine(fftSize, true, &m_invFFT2);
m_dataj = new std::complex<float>[2*fftSize]; // receives actual FFT result hence twice the data FFT size
m_scorr.resize(fftSize);
m_tcorr.resize(fftSize);
m_scorrSize = fftSize;
m_tcorrSize = fftSize;
}
InterferometerCorrelator::~InterferometerCorrelator()
{
FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory();
fftFactory->releaseEngine(2*m_fftSize, true, m_invFFTSequence);
fftFactory->releaseEngine(m_fftSize, true, m_invFFT2Sequence);
delete[] m_dataj;
for (int i = 0; i < 2; i++)
{
fftFactory->releaseEngine(2*m_fftSize, false, m_fftSequences[i]);
fftFactory->releaseEngine(m_fftSize, false, m_fft2Sequences[i]);
}
}
bool InterferometerCorrelator::performCorr(
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
)
{
bool results = false;
if (m_phase == 0)
{
switch (m_corrType)
{
case InterferometerSettings::Correlation0:
results = performOpCorr(data0, size0, data1, size1, sFirst);
break;
case InterferometerSettings::Correlation1:
results = performOpCorr(data0, size0, data1, size1, sSecond);
break;
case InterferometerSettings::CorrelationAdd:
results = performOpCorr(data0, size0, data1, size1, sAdd);
break;
case InterferometerSettings::CorrelationMultiply:
results = performOpCorr(data0, size0, data1, size1, sMulConj);
break;
case InterferometerSettings::CorrelationIFFT:
results = performIFFTCorr(data0, size0, data1, size1);
break;
case InterferometerSettings::CorrelationIFFTStar:
results = performIFFTCorr(data0, size0, data1, size1, true);
break;
case InterferometerSettings::CorrelationFFT:
results = performFFTProd(data0, size0, data1, size1);
break;
case InterferometerSettings::CorrelationIFFT2:
results = performIFFT2Corr(data0, size0, data1, size1);
break;
default:
break;
}
}
else if ((m_phase == -180) || (m_phase == 180))
{
if ((m_corrType == InterferometerSettings::CorrelationIFFT)
|| (m_corrType == InterferometerSettings::CorrelationIFFT2)
|| (m_corrType == InterferometerSettings::CorrelationIFFTStar)
|| (m_corrType == InterferometerSettings::CorrelationFFT))
{
if (size1 > m_data1p.size()) {
m_data1p.resize(size1);
}
std::transform(
data1.begin(),
data1.begin() + size1,
m_data1p.begin(),
[](const Sample& s) -> Sample {
return Sample{-s.real(), -s.imag()};
}
);
}
switch (m_corrType)
{
case InterferometerSettings::Correlation0:
results = performOpCorr(data0, size0, data1, size1, sFirst);
break;
case InterferometerSettings::Correlation1:
results = performOpCorr(data0, size0, data1, size1, sSecondInv);
break;
case InterferometerSettings::CorrelationAdd:
results = performOpCorr(data0, size0, data1, size1, sAddInv);
break;
case InterferometerSettings::CorrelationMultiply:
results = performOpCorr(data0, size0, data1, size1, sMulConjInv);
break;
case InterferometerSettings::CorrelationIFFT:
results = performIFFTCorr(data0, size0, m_data1p, size1);
break;
case InterferometerSettings::CorrelationIFFTStar:
results = performIFFTCorr(data0, size0, m_data1p, size1, true);
break;
case InterferometerSettings::CorrelationFFT:
results = performFFTProd(data0, size0, m_data1p, size1);
break;
case InterferometerSettings::CorrelationIFFT2:
results = performIFFT2Corr(data0, size0, m_data1p, size1);
break;
default:
break;
}
}
else
{
if (size1 > m_data1p.size()) {
m_data1p.resize(size1);
}
std::transform(
data1.begin(),
data1.begin() + size1,
m_data1p.begin(),
[this](const Sample& s) -> Sample {
Sample t;
int64_t sx = s.real();
int64_t sy = s.imag();
int64_t x = sx*m_cos + sy*m_sin;
int64_t y = sy*m_cos - sx*m_sin;
t.setReal(x>>(SDR_RX_SAMP_SZ-1));
t.setImag(y>>(SDR_RX_SAMP_SZ-1));
return t;
}
);
switch (m_corrType)
{
case InterferometerSettings::Correlation0:
results = performOpCorr(data0, size0, m_data1p, size1, sFirst);
break;
case InterferometerSettings::Correlation1:
results = performOpCorr(data0, size0, m_data1p, size1, sSecond);
break;
case InterferometerSettings::CorrelationAdd:
results = performOpCorr(data0, size0, m_data1p, size1, sAdd);
break;
case InterferometerSettings::CorrelationMultiply:
results = performOpCorr(data0, size0, m_data1p, size1, sMulConj);
break;
case InterferometerSettings::CorrelationIFFT:
results = performIFFTCorr(data0, size0, m_data1p, size1);
break;
case InterferometerSettings::CorrelationIFFTStar:
results = performIFFTCorr(data0, size0, m_data1p, size1, true);
break;
case InterferometerSettings::CorrelationFFT:
results = performFFTProd(data0, size0, m_data1p, size1);
break;
case InterferometerSettings::CorrelationIFFT2:
results = performIFFT2Corr(data0, size0, m_data1p, size1);
break;
default:
break;
}
}
return results;
}
bool InterferometerCorrelator::performOpCorr(
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1,
Sample sampleOp(const Sample& a, const Sample& b)
)
{
unsigned int size = std::min(size0, size1);
adjustTCorrSize(size);
std::transform(
data0.begin(),
data0.begin() + size,
data1.begin(),
m_tcorr.begin(),
sampleOp
);
m_processed = size;
m_remaining[0] = size0 - size;
m_remaining[1] = size1 - size;
return true;
}
bool InterferometerCorrelator::performIFFTCorr(
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1,
bool star
)
{
unsigned int size = std::min(size0, size1);
int nfft = 0;
SampleVector::const_iterator begin0 = data0.begin();
SampleVector::const_iterator begin1 = data1.begin();
adjustSCorrSize(size);
adjustTCorrSize(size);
while (size >= m_fftSize)
{
// FFT[0]
std::transform(
begin0,
begin0 + m_fftSize,
m_fft[0]->in(),
s2c
);
m_window.apply(m_fft[0]->in());
std::fill(m_fft[0]->in() + m_fftSize, m_fft[0]->in() + 2*m_fftSize, std::complex<float>{0, 0});
m_fft[0]->transform();
// FFT[1]
std::transform(
begin1,
begin1 + m_fftSize,
m_fft[1]->in(),
s2c
);
m_window.apply(m_fft[1]->in());
std::fill(m_fft[1]->in() + m_fftSize, m_fft[1]->in() + 2*m_fftSize, std::complex<float>{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<float>& c) -> std::complex<float> {
return std::conj(c);
}
);
// 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<float>& a, const std::complex<float>& b) -> std::complex<float> {
return (a*b);
}
);
// copy product to correlation spectrum - convert and scale to FFT size and Hanning window
std::transform(
m_invFFT->in(),
m_invFFT->in() + m_fftSize,
m_scorr.begin() + nfft*m_fftSize,
[this](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()*(SDR_RX_SCALEF/m_fftSize));
s.setImag(a.imag()*(SDR_RX_SCALEF/m_fftSize));
return s;
}
);
// do the inverse FFT to get time correlation
m_invFFT->transform();
if (star)
{
// sum first half with the reversed second half as one is the conjugate of the other this should yield constant phase
*m_tcorr.begin() = invfft2star(m_invFFT->out()[0]); // t = 0
std::reverse(m_invFFT->out() + m_fftSize, m_invFFT->out() + 2*m_fftSize);
std::transform(
m_invFFT->out() + 1,
m_invFFT->out() + m_fftSize,
m_invFFT->out() + m_fftSize,
m_tcorr.begin() + nfft*m_fftSize,
[](const std::complex<float>& a, const std::complex<float>& b) -> Sample {
Sample s;
std::complex<float> sum = a + b;
s.setReal(sum.real()/12.0f);
s.setImag(sum.imag()/12.0f);
return s;
}
);
}
else
{
std::transform(
m_invFFT->out(),
m_invFFT->out() + m_fftSize,
m_tcorr.begin() + nfft*m_fftSize,
[](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()/6.0f);
s.setImag(a.imag()/6.0f);
return s;
}
);
}
size -= m_fftSize;
begin0 += m_fftSize;
begin1 += m_fftSize;
nfft++;
}
// update the samples counters
m_processed = nfft*m_fftSize;
m_remaining[0] = size0 - nfft*m_fftSize;
m_remaining[1] = size1 - nfft*m_fftSize;
return nfft > 0;
}
bool InterferometerCorrelator::performIFFT2Corr(
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
)
{
unsigned int size = std::min(size0, size1);
int nfft = 0;
SampleVector::const_iterator begin0 = data0.begin();
SampleVector::const_iterator begin1 = data1.begin();
adjustSCorrSize(size);
adjustTCorrSize(size);
while (size >= m_fftSize)
{
// FFT[0]
std::transform(
begin0,
begin0 + m_fftSize,
m_fft2[0]->in(),
s2c
);
m_window.apply(m_fft2[0]->in());
m_fft2[0]->transform();
// FFT[1]
std::transform(
begin1,
begin1 + m_fftSize,
m_fft2[1]->in(),
s2c
);
m_window.apply(m_fft2[1]->in());
m_fft2[1]->transform();
// conjugate FFT[1]
std::transform(
m_fft2[1]->out(),
m_fft2[1]->out() + m_fftSize,
m_dataj,
[](const std::complex<float>& c) -> std::complex<float> {
return std::conj(c);
}
);
// product of FFT[1]* with FFT[0] and store in inverse FFT input
std::transform(
m_fft2[0]->out(),
m_fft2[0]->out() + m_fftSize,
m_dataj,
m_invFFT2->in(),
[](std::complex<float>& a, const std::complex<float>& b) -> std::complex<float> {
return (a*b);
}
);
// copy product to correlation spectrum - convert and scale to FFT size
std::transform(
m_invFFT2->in(),
m_invFFT2->in() + m_fftSize,
m_scorr.begin() + nfft*m_fftSize,
[this](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()*(SDR_RX_SCALEF/m_fftSize));
s.setImag(a.imag()*(SDR_RX_SCALEF/m_fftSize));
return s;
}
);
// do the inverse FFT to get time correlation
m_invFFT2->transform();
std::transform(
m_invFFT2->out() + m_fftSize/2,
m_invFFT2->out() + m_fftSize,
m_tcorr.begin() + nfft*m_fftSize,
[](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()/3.0f);
s.setImag(a.imag()/3.0f);
return s;
}
);
std::transform(
m_invFFT2->out(),
m_invFFT2->out() + m_fftSize/2,
m_tcorr.begin() + nfft*m_fftSize + m_fftSize/2,
[](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()/3.0f);
s.setImag(a.imag()/3.0f);
return s;
}
);
size -= m_fftSize;
begin0 += m_fftSize;
begin1 += m_fftSize;
nfft++;
}
// update the samples counters
m_processed = nfft*m_fftSize;
m_remaining[0] = size0 - nfft*m_fftSize;
m_remaining[1] = size1 - nfft*m_fftSize;
return nfft > 0;
}
bool InterferometerCorrelator::performFFTProd(
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
)
{
unsigned int size = std::min(size0, size1);
int nfft = 0;
SampleVector::const_iterator begin0 = data0.begin();
SampleVector::const_iterator begin1 = data1.begin();
adjustSCorrSize(size);
adjustTCorrSize(size);
while (size >= m_fftSize)
{
// FFT[0]
std::transform(
begin0,
begin0 + m_fftSize,
m_fft2[0]->in(),
s2cNorm
);
m_window.apply(m_fft2[0]->in());
m_fft2[0]->transform();
// FFT[1]
std::transform(
begin1,
begin1 + m_fftSize,
m_fft2[1]->in(),
s2cNorm
);
m_window.apply(m_fft2[1]->in());
m_fft2[1]->transform();
// conjugate FFT[1]
std::transform(
m_fft2[1]->out(),
m_fft2[1]->out() + m_fftSize,
m_dataj,
[](const std::complex<float>& c) -> std::complex<float> {
return std::conj(c);
}
);
// product of FFT[1]* with FFT[0] and store in both results
std::transform(
m_fft2[0]->out(),
m_fft2[0]->out() + m_fftSize,
m_dataj,
m_invFFT2->in(),
[this](std::complex<float>& a, const std::complex<float>& b) -> std::complex<float> {
return (a*b);
}
);
// copy product to time domain - re-order, convert and scale to FFT size
std::transform(
m_invFFT2->in(),
m_invFFT2->in() + m_fftSize/2,
m_tcorr.begin() + nfft*m_fftSize + m_fftSize/2,
[](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()/2.0f);
s.setImag(a.imag()/2.0f);
return s;
}
);
std::transform(
m_invFFT2->in() + m_fftSize/2,
m_invFFT2->in() + m_fftSize,
m_tcorr.begin() + nfft*m_fftSize,
[](const std::complex<float>& a) -> Sample {
Sample s;
s.setReal(a.real()/2.0f);
s.setImag(a.imag()/2.0f);
return s;
}
);
// feed spectrum with the sum
std::transform(
begin0,
begin0 + m_fftSize,
begin1,
m_scorr.begin() + nfft*m_fftSize,
sAdd
);
size -= m_fftSize;
begin0 += m_fftSize;
begin1 += m_fftSize;
nfft++;
}
// update the samples counters
m_processed = nfft*m_fftSize;
m_remaining[0] = size0 - nfft*m_fftSize;
m_remaining[1] = size1 - nfft*m_fftSize;
return nfft > 0;
}
void InterferometerCorrelator::adjustSCorrSize(int size)
{
int nFFTSize = (size/m_fftSize)*m_fftSize;
if (nFFTSize > m_scorrSize)
{
m_scorr.resize(nFFTSize);
m_scorrSize = nFFTSize;
}
}
void InterferometerCorrelator::adjustTCorrSize(int size)
{
int nFFTSize = (size/m_fftSize)*m_fftSize;
if (nFFTSize > m_tcorrSize)
{
m_tcorr.resize(nFFTSize);
m_tcorrSize = nFFTSize;
}
}
void InterferometerCorrelator::setPhase(int phase)
{
m_phase = phase;
if (phase == 0)
{
m_sin = 0;
m_cos = 1<<(SDR_RX_SAMP_SZ-1);
}
else if (phase == 90)
{
m_sin = 1<<(SDR_RX_SAMP_SZ-1);
m_cos = 0;
}
else if (phase == -90)
{
m_sin = -(1<<(SDR_RX_SAMP_SZ-1));
m_cos = 0;
}
else if ((phase == -180) || (phase == 180))
{
m_sin = 0;
m_cos = -(1<<(SDR_RX_SAMP_SZ-1));
}
else
{
m_phase = phase % 180;
double d_sin = sin(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1));
double d_cos = cos(M_PI*(m_phase/180.0)) * (1<<(SDR_RX_SAMP_SZ-1));
m_sin = d_sin;
m_cos = d_cos;
}
}

Wyświetl plik

@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETERCORR_H
#define INCLUDE_INTERFEROMETERCORR_H
#include <complex>
#include <QObject>
#include "dsp/dsptypes.h"
#include "dsp/fftwindow.h"
#include "util/message.h"
#include "interferometersettings.h"
class FFTEngine;
class InterferometerCorrelator : public QObject {
Q_OBJECT
public:
InterferometerCorrelator(int fftSize);
~InterferometerCorrelator();
void setCorrType(InterferometerSettings::CorrelationType corrType) { m_corrType = corrType; }
InterferometerSettings::CorrelationType getCorrType() const { return m_corrType; }
bool performCorr( //!< Returns true if results were produced
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
);
int getFullFFTSize() const { return 2*m_fftSize; }
void setPhase(int phase);
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[2]; //!< number of samples remaining per member at the end of correlation
signals:
void dataReady(int start, int stop);
private:
bool performOpCorr( //!< Returns true if results were produced
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1,
Sample sampleOp(const Sample& a, const Sample& b)
);
bool performIFFTCorr( //!< Returns true if results were produced
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1,
bool star = false
);
bool performIFFT2Corr( //!< Returns true if results were produced
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
);
bool performFFTProd( //!< Returns true if results were produced
const SampleVector& data0,
int size0,
const SampleVector& data1,
int size1
);
void adjustSCorrSize(int size);
void adjustTCorrSize(int size);
InterferometerSettings::CorrelationType m_corrType;
int m_fftSize; //!< FFT length
FFTEngine *m_fft[2]; //!< FFT engines (double FFT)
FFTEngine *m_invFFT; //!< Inverse FFT engine (double FFT)
FFTEngine *m_fft2[2]; //!< FFT engines
FFTEngine *m_invFFT2; //!< Inverse FFT engine
unsigned int m_fftSequences[2]; //!< FFT factory engine sequences
unsigned int m_invFFTSequence; //!< Inverse FFT engine sequence
unsigned int m_fft2Sequences[2]; //!< FFT engines sequences
unsigned int m_invFFT2Sequence; //!< Inverse FFT engine sequence
FFTWindow m_window; //!< FFT window
std::complex<float> *m_dataj; //!< conjuate of FFT transform
SampleVector m_data0w; //!< windowed data 0
SampleVector m_data1w; //!< windowed data 1
SampleVector m_data1p; //!< data1 with phase correction
int m_scorrSize; //!< spectrum correlations vector size
int m_tcorrSize; //!< time correlations vector size
int m_phase; //!< phase correction
int64_t m_sin; //!< scaled sine of phase correction
int64_t m_cos; //!< scaled cosine of phase correction
};
#endif // INCLUDE_INTERFEROMETERCORR_H

Wyświetl plik

@ -0,0 +1,334 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QLocale>
#include "device/deviceuiset.h"
#include "gui/basicchannelsettingsdialog.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/scopevis.h"
#include "dsp/spectrumvis.h"
#include "maincore.h"
#include "interferometergui.h"
#include "interferometer.h"
#include "ui_interferometergui.h"
InterferometerGUI* InterferometerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *channelMIMO)
{
InterferometerGUI* gui = new InterferometerGUI(pluginAPI, deviceUISet, channelMIMO);
return gui;
}
void InterferometerGUI::destroy()
{
delete this;
}
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;
}
}
MessageQueue* InterferometerGUI::getInputMessageQueue()
{
return &m_inputMessageQueue;
}
bool InterferometerGUI::handleMessage(const Message& message)
{
if (Interferometer::MsgBasebandNotification::match(message))
{
Interferometer::MsgBasebandNotification& notif = (Interferometer::MsgBasebandNotification&) message;
m_sampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
displayRateAndShift();
return true;
}
else if (Interferometer::MsgConfigureInterferometer::match(message))
{
const Interferometer::MsgConfigureInterferometer& notif = (const Interferometer::MsgConfigureInterferometer&) message;
m_settings = notif.getSettings();
displaySettings();
return true;
}
else
{
return false;
}
}
InterferometerGUI::InterferometerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *channelMIMO, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::InterferometerGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_sampleRate(48000),
m_centerFrequency(435000000),
m_tickCount(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
setStreamIndicator("M");
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_scopeVis = new ScopeVis(ui->glScope);
m_interferometer = (Interferometer*) channelMIMO;
m_spectrumVis = m_interferometer->getSpectrumVis();
m_interferometer->setScopeSink(m_scopeVis);
m_interferometer->setMessageQueueToGUI(getInputMessageQueue());
m_sampleRate = m_interferometer->getDeviceSampleRate();
ui->glSpectrum->setDisplayWaterfall(true);
ui->glSpectrum->setDisplayMaxHold(true);
ui->glSpectrum->setCenterFrequency(0);
ui->glSpectrum->setSampleRate(m_sampleRate);
ui->glSpectrum->setSsbSpectrum(false);
ui->glSpectrum->setLsbDisplay(false);
ui->glScope->setTraceModulo(Interferometer::m_fftSize);
ui->glSpectrum->connectTimer(MainCore::instance()->getMasterTimer());
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
m_channelMarker.blockSignals(true);
m_channelMarker.addStreamIndex(1);
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_settings.setScopeGUI(ui->scopeGUI);
m_settings.setSpectrumGUI(ui->spectrumGUI);
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum);
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
m_scopeVis->setTraceChunkSize(Interferometer::m_fftSize); // Set scope trace length unit to FFT size
ui->scopeGUI->traceLengthChange();
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
displaySettings();
displayRateAndShift();
applySettings(true);
}
InterferometerGUI::~InterferometerGUI()
{
delete m_scopeVis;
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::displaySettings()
{
ui->correlationType->setCurrentIndex((int) m_settings.m_correlationType);
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();
ui->phaseCorrection->setValue(m_settings.m_phase);
ui->phaseCorrectionText->setText(tr("%1").arg(m_settings.m_phase));
blockApplySettings(false);
}
void InterferometerGUI::displayRateAndShift()
{
int shift = m_shiftFrequencyFactor * m_sampleRate;
double channelSampleRate = ((double) m_sampleRate) / (1<<m_settings.m_log2Decim);
QLocale loc;
ui->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);
ui->glSpectrum->setSampleRate(channelSampleRate);
m_scopeVis->setLiveRate(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::on_phaseCorrection_valueChanged(int value)
{
m_settings.m_phase = value;
ui->phaseCorrectionText->setText(tr("%1").arg(value));
applySettings();
}
void InterferometerGUI::on_correlationType_currentIndexChanged(int index)
{
m_settings.m_correlationType = (InterferometerSettings::CorrelationType) index;
applySettings();
}
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();
applySettings();
}
void InterferometerGUI::tick()
{
if (++m_tickCount == 20) { // once per second
m_tickCount = 0;
}
}

Wyświetl plik

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETERGUI_H
#define INCLUDE_INTERFEROMETERGUI_H
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "interferometersettings.h"
class PluginAPI;
class DeviceUISet;
class MIMOChannel;
class Interferometer;
class SpectrumVis;
class ScopeVis;
namespace Ui {
class InterferometerGUI;
}
class InterferometerGUI : public ChannelGUI {
Q_OBJECT
public:
static InterferometerGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *mimoChannel);
virtual void destroy();
virtual void resetToDefaults();
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual MessageQueue* getInputMessageQueue();
private:
Ui::InterferometerGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
InterferometerSettings m_settings;
int m_sampleRate;
qint64 m_centerFrequency;
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
bool m_doApplySettings;
MovingAverageUtil<double, double, 40> m_channelPowerAvg;
Interferometer *m_interferometer;
SpectrumVis* m_spectrumVis;
ScopeVis* m_scopeVis;
MessageQueue m_inputMessageQueue;
uint32_t m_tickCount;
explicit InterferometerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, MIMOChannel *rxChannel, QWidget* parent = nullptr);
virtual ~InterferometerGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void applyDecimation();
void applyPosition();
void displaySettings();
void displayRateAndShift();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void handleSourceMessages();
void on_decimationFactor_currentIndexChanged(int index);
void on_position_valueChanged(int value);
void on_phaseCorrection_valueChanged(int value);
void on_correlationType_currentIndexChanged(int index);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void tick();
};
#endif // INCLUDE_INTERFEROMETERGUI_H

Wyświetl plik

@ -0,0 +1,493 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>InterferometerGUI</class>
<widget class="RollupWidget" name="InterferometerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>739</width>
<height>778</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>720</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Interferometer</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>10</y>
<width>631</width>
<height>81</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QVBoxLayout" name="decimationLayer">
<property name="spacing">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="decimationStageLayer">
<item>
<widget class="QLabel" name="decimationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="decimationFactor">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Decimation factor</string>
</property>
<item>
<property name="text">
<string>1</string>
</property>
</item>
<item>
<property name="text">
<string>2</string>
</property>
</item>
<item>
<property name="text">
<string>4</string>
</property>
</item>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
<item>
<property name="text">
<string>64</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="channelRateText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Effective channel rate (kS/s)</string>
</property>
<property name="text">
<string>0000k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainText">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain stages left to right (L: low, C: center, H: high) </string>
</property>
<property name="text">
<string>LLLLLL</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Corr</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="correlationType">
<property name="toolTip">
<string>Correlation type</string>
</property>
<item>
<property name="text">
<string>A</string>
</property>
</item>
<item>
<property name="text">
<string>B</string>
</property>
</item>
<item>
<property name="text">
<string>A+B</string>
</property>
</item>
<item>
<property name="text">
<string>A.B*</string>
</property>
</item>
<item>
<property name="text">
<string>IFFT</string>
</property>
</item>
<item>
<property name="text">
<string>IFFT*</string>
</property>
</item>
<item>
<property name="text">
<string>FFT</string>
</property>
</item>
<item>
<property name="text">
<string>IFFT2</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="phaseCorrectionLabel">
<property name="text">
<string>Ph</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="phaseCorrection">
<property name="toolTip">
<string>Phase correction on stream 1</string>
</property>
<property name="minimum">
<number>-180</number>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="phaseCorrectionText">
<property name="minimumSize">
<size>
<width>28</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Phase correction on stream 1 in degrees</string>
</property>
<property name="text">
<string>-180</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="offsetFrequencyText">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Offset frequency with thousands separator (Hz)</string>
</property>
<property name="text">
<string>-9,999,999 Hz</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="decimationShiftLayer">
<property name="rightMargin">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="positionLabel">
<property name="text">
<string>Pos</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="position">
<property name="toolTip">
<string>Center frequency position</string>
</property>
<property name="maximum">
<number>2</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="filterChainIndex">
<property name="minimumSize">
<size>
<width>24</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Filter chain hash code</string>
</property>
<property name="text">
<string>000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="spectrumContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>98</y>
<width>720</width>
<height>284</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>716</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Frequency domain</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutSpectrum">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="GLSpectrum" name="glSpectrum" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLSpectrumGUI" name="spectrumGUI" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="scopeContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>390</y>
<width>720</width>
<height>334</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>716</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Time domain</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutScope">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="GLScope" name="glScope" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>300</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLScopeGUI" name="scopeGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrum</class>
<extends>QWidget</extends>
<header>gui/glspectrum.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLSpectrumGUI</class>
<extends>QWidget</extends>
<header>gui/glspectrumgui.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScope</class>
<extends>QWidget</extends>
<header>gui/glscope.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeGUI</class>
<extends>QWidget</extends>
<header>gui/glscopegui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

Wyświetl plik

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "interferometerplugin.h"
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "interferometergui.h"
#endif
#include "interferometer.h"
#include "interferometerwebapiadapter.h"
#include "interferometerplugin.h"
const PluginDescriptor InterferometerPlugin::m_pluginDescriptor = {
QString(Interferometer::m_channelId),
QString("Interferometer"),
QString("6.0.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
QString("https://github.com/f4exb/sdrangel")
};
InterferometerPlugin::InterferometerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& InterferometerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void InterferometerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register channel MIMO
m_pluginAPI->registerMIMOChannel(Interferometer::m_channelIdURI, Interferometer::m_channelId, this);
}
void InterferometerPlugin::createMIMOChannel(DeviceAPI *deviceAPI, MIMOChannel **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
Interferometer *instance = new Interferometer(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* InterferometerPlugin::createMIMOChannelGUI(
DeviceUISet *deviceUISet,
MIMOChannel *mimoChannel) const
{
return 0;
}
#else
ChannelGUI* InterferometerPlugin::createMIMOChannelGUI(DeviceUISet *deviceUISet, MIMOChannel *mimoChannel) const
{
return InterferometerGUI::create(m_pluginAPI, deviceUISet, mimoChannel);
}
#endif
ChannelWebAPIAdapter* InterferometerPlugin::createChannelWebAPIAdapter() const
{
return new InterferometerWebAPIAdapter();
}

Wyświetl plik

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELMIMO_INTERFEROMETER_INTERFEROMETERPLUGIN_H_
#define PLUGINS_CHANNELMIMO_INTERFEROMETER_INTERFEROMETERPLUGIN_H_
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class MIMOChannel;
class InterferometerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channelmimo.interferometer")
public:
explicit InterferometerPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createMIMOChannel(DeviceAPI *deviceAPI, MIMOChannel **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createMIMOChannelGUI(DeviceUISet *deviceUISet, MIMOChannel *mimoChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif /* PLUGINS_CHANNELMIMO_INTERFEROMETER_INTERFEROMETERPLUGIN_H_ */

Wyświetl plik

@ -0,0 +1,139 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "interferometersettings.h"
InterferometerSettings::InterferometerSettings() :
m_channelMarker(nullptr),
m_spectrumGUI(nullptr),
m_scopeGUI(nullptr)
{
resetToDefaults();
}
void InterferometerSettings::resetToDefaults()
{
m_correlationType = CorrelationAdd;
m_rgbColor = QColor(128, 128, 128).rgb();
m_title = "Interferometer";
m_log2Decim = 0;
m_filterChainHash = 0;
m_phase = 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(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);
s.writeS32(12, m_phase);
if (m_spectrumGUI) {
s.writeBlob(20, m_spectrumGUI->serialize());
}
if (m_scopeGUI) {
s.writeBlob(21, m_scopeGUI->serialize());
}
if (m_channelMarker) {
s.writeBlob(22, m_channelMarker->serialize());
}
return s.final();
}
bool InterferometerSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
int tmp;
quint32 utmp;
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;
d.readS32(12, &tmp, 0);
m_phase = tmp < -180 ? -180 : tmp > 180 ? 180 : tmp;
if (m_spectrumGUI) {
d.readBlob(20, &bytetmp);
m_spectrumGUI->deserialize(bytetmp);
}
if (m_scopeGUI) {
d.readBlob(21, &bytetmp);
m_scopeGUI->deserialize(bytetmp);
}
if (m_channelMarker) {
d.readBlob(21, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

Wyświetl plik

@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETERSETTINGS_H
#define INCLUDE_INTERFEROMETERSETTINGS_H
#include <QByteArray>
#include <QString>
class Serializable;
struct InterferometerSettings
{
enum CorrelationType
{
Correlation0,
Correlation1,
CorrelationAdd,
CorrelationMultiply,
CorrelationIFFT,
CorrelationIFFTStar,
CorrelationFFT,
CorrelationIFFT2
};
CorrelationType m_correlationType;
quint32 m_rgbColor;
QString m_title;
uint32_t m_log2Decim;
uint32_t m_filterChainHash;
int m_phase;
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;
InterferometerSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; }
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif // INCLUDE_INTERFEROMETERSETTINGS_H

Wyświetl plik

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QMutexLocker>
#include <QDebug>
#include "interferometerstreamsink.h"
InterferometerStreamSink::InterferometerStreamSink() :
m_streamIndex(0),
m_dataSize(0),
m_bufferSize(0),
m_dataStart(0)
{}
InterferometerStreamSink::~InterferometerStreamSink()
{}
void InterferometerStreamSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_dataSize = (end - begin) + m_dataStart;
if (m_dataSize > m_bufferSize)
{
m_data.resize(m_dataSize);
m_bufferSize = m_dataSize;
}
std::copy(begin, end, m_data.begin() + m_dataStart);
}
void InterferometerStreamSink::reset()
{
m_dataStart = 0;
}

Wyświetl plik

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRBASE_INTERFEROMETERSTREAMSINK_H_
#define SDRBASE_INTERFEROMETERSTREAMSINK_H_
#include "dsp/channelsamplesink.h"
class InterferometerStreamSink : public ChannelSampleSink
{
public:
InterferometerStreamSink();
virtual ~InterferometerStreamSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void reset();
unsigned int getStreamIndex() const { return m_streamIndex; }
void setStreamIndex(unsigned int streamIndex) { m_streamIndex = streamIndex; }
SampleVector& getData() { return m_data; }
int getSize() const { return m_dataSize; }
void setDataStart(int dataStart) { m_dataStart = dataStart; }
private:
unsigned int m_streamIndex;
SampleVector m_data;
int m_dataSize;
int m_bufferSize;
int m_dataStart;
uint32_t m_log2Decim;
uint32_t m_filterChainHash;
};
#endif // SDRBASE_INTERFEROMETERSTREAMSINK_H_

Wyświetl plik

@ -0,0 +1,387 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QString>
#include "SWGChannelSettings.h"
#include "interferometerwebapiadapter.h"
InterferometerWebAPIAdapter::InterferometerWebAPIAdapter()
{
m_settings.setScopeGUI(&m_glScopeSettings);
m_settings.setSpectrumGUI(&m_glSpectrumSettings);
}
InterferometerWebAPIAdapter::~InterferometerWebAPIAdapter()
{}
int InterferometerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setInterferometerSettings(new SWGSDRangel::SWGInterferometerSettings());
response.getInterferometerSettings()->init();
webapiFormatChannelSettings(response, m_settings, m_glScopeSettings, m_glSpectrumSettings);
return 200;
}
void InterferometerWebAPIAdapter::webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const InterferometerSettings& settings,
const GLScopeSettings& scopeSettings,
const GLSpectrumSettings& spectrumSettings)
{
response.getInterferometerSettings()->setCorrelationType((int) settings.m_correlationType);
response.getInterferometerSettings()->setRgbColor(settings.m_rgbColor);
response.getInterferometerSettings()->setTitle(new QString(settings.m_title));
// scope
SWGSDRangel::SWGGLScope *swgScope = new SWGSDRangel::SWGGLScope();
swgScope->init();
response.getInterferometerSettings()->setScopeConfig(swgScope);
swgScope->setDisplayMode(scopeSettings.m_displayMode);
swgScope->setGridIntensity(scopeSettings.m_gridIntensity);
swgScope->setTime(scopeSettings.m_time);
swgScope->setTimeOfs(scopeSettings.m_timeOfs);
swgScope->setTraceIntensity(scopeSettings.m_traceIntensity);
swgScope->setTraceLen(scopeSettings.m_traceLen);
swgScope->setTrigPre(scopeSettings.m_trigPre);
// array of traces
swgScope->setTracesData(new QList<SWGSDRangel::SWGTraceData *>);
std::vector<GLScopeSettings::TraceData>::const_iterator traceIt = scopeSettings.m_tracesData.begin();
for (; traceIt != scopeSettings.m_tracesData.end(); ++traceIt)
{
swgScope->getTracesData()->append(new SWGSDRangel::SWGTraceData);
swgScope->getTracesData()->back()->setAmp(traceIt->m_amp);
swgScope->getTracesData()->back()->setAmpIndex(traceIt->m_ampIndex);
swgScope->getTracesData()->back()->setHasTextOverlay(traceIt->m_hasTextOverlay ? 1 : 0);
swgScope->getTracesData()->back()->setInputIndex(traceIt->m_inputIndex);
swgScope->getTracesData()->back()->setOfs(traceIt->m_ofs);
swgScope->getTracesData()->back()->setOfsCoarse(traceIt->m_ofsCoarse);
swgScope->getTracesData()->back()->setOfsFine(traceIt->m_ofsFine);
swgScope->getTracesData()->back()->setProjectionType((int) traceIt->m_projectionType);
swgScope->getTracesData()->back()->setTextOverlay(new QString(traceIt->m_textOverlay));
swgScope->getTracesData()->back()->setTraceColor(qColorToInt(traceIt->m_traceColor));
swgScope->getTracesData()->back()->setTraceColorB(traceIt->m_traceColorB);
swgScope->getTracesData()->back()->setTraceColorG(traceIt->m_traceColorG);
swgScope->getTracesData()->back()->setTraceColorR(traceIt->m_traceColorR);
swgScope->getTracesData()->back()->setTraceDelay(traceIt->m_traceDelay);
swgScope->getTracesData()->back()->setTraceDelayCoarse(traceIt->m_traceDelayCoarse);
swgScope->getTracesData()->back()->setTraceDelayFine(traceIt->m_traceDelayFine);
swgScope->getTracesData()->back()->setTriggerDisplayLevel(traceIt->m_triggerDisplayLevel);
swgScope->getTracesData()->back()->setViewTrace(traceIt->m_viewTrace ? 1 : 0);
}
// array of triggers
swgScope->setTriggersData(new QList<SWGSDRangel::SWGTriggerData *>);
std::vector<GLScopeSettings::TriggerData>::const_iterator triggerIt = scopeSettings.m_triggersData.begin();
for (; triggerIt != scopeSettings.m_triggersData.end(); ++triggerIt)
{
swgScope->getTriggersData()->append(new SWGSDRangel::SWGTriggerData);
swgScope->getTriggersData()->back()->setInputIndex(triggerIt->m_inputIndex);
swgScope->getTriggersData()->back()->setProjectionType((int) triggerIt->m_projectionType);
swgScope->getTriggersData()->back()->setTriggerBothEdges(triggerIt->m_triggerBothEdges ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerColor(qColorToInt(triggerIt->m_triggerColor));
swgScope->getTriggersData()->back()->setTriggerColorB(triggerIt->m_triggerColorB);
swgScope->getTriggersData()->back()->setTriggerColorG(triggerIt->m_triggerColorG);
swgScope->getTriggersData()->back()->setTriggerColorR(triggerIt->m_triggerColorR);
swgScope->getTriggersData()->back()->setTriggerDelay(triggerIt->m_triggerDelay);
swgScope->getTriggersData()->back()->setTriggerDelayCoarse(triggerIt->m_triggerDelayCoarse);
swgScope->getTriggersData()->back()->setTriggerDelayFine(triggerIt->m_triggerDelayFine);
swgScope->getTriggersData()->back()->setTriggerDelayMult(triggerIt->m_triggerDelayMult);
swgScope->getTriggersData()->back()->setTriggerHoldoff(triggerIt->m_triggerHoldoff ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerLevel(triggerIt->m_triggerLevel);
swgScope->getTriggersData()->back()->setTriggerLevelCoarse(triggerIt->m_triggerLevelCoarse);
swgScope->getTriggersData()->back()->setTriggerLevelFine(triggerIt->m_triggerLevelFine);
swgScope->getTriggersData()->back()->setTriggerPositiveEdge(triggerIt->m_triggerPositiveEdge ? 1 : 0);
swgScope->getTriggersData()->back()->setTriggerRepeat(triggerIt->m_triggerRepeat);
}
// spectrum
SWGSDRangel::SWGGLSpectrum *swgSpectrum = new SWGSDRangel::SWGGLSpectrum();
swgSpectrum->init();
response.getInterferometerSettings()->setSpectrumConfig(swgSpectrum);
swgSpectrum->setAveragingMode((int) spectrumSettings.m_averagingMode);
swgSpectrum->setAveragingValue(GLSpectrumSettings::getAveragingValue(spectrumSettings.m_averagingIndex, spectrumSettings.m_averagingMode));
swgSpectrum->setDecay(spectrumSettings.m_decay);
swgSpectrum->setDecayDivisor(spectrumSettings.m_decayDivisor);
swgSpectrum->setDisplayCurrent(spectrumSettings.m_displayCurrent ? 1 : 0);
swgSpectrum->setDisplayGrid(spectrumSettings.m_displayGrid ? 1 : 0);
swgSpectrum->setDisplayGridIntensity(spectrumSettings.m_displayGridIntensity);
swgSpectrum->setDisplayHistogram(spectrumSettings.m_displayHistogram ? 1 : 0);
swgSpectrum->setDisplayMaxHold(spectrumSettings.m_displayMaxHold ? 1 : 0);
swgSpectrum->setDisplayTraceIntensity(spectrumSettings.m_displayTraceIntensity);
swgSpectrum->setDisplayWaterfall(spectrumSettings.m_displayWaterfall ? 1 : 0);
swgSpectrum->setFftOverlap(spectrumSettings.m_fftOverlap);
swgSpectrum->setFftSize(spectrumSettings.m_fftSize);
}
int InterferometerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
webapiUpdateChannelSettings(m_settings, m_glScopeSettings, m_glSpectrumSettings, channelSettingsKeys, response);
return 200;
}
void InterferometerWebAPIAdapter::webapiUpdateChannelSettings(
InterferometerSettings& settings,
GLScopeSettings& scopeSettings,
GLSpectrumSettings& spectrumSettings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("correlationType")) {
settings.m_correlationType = (InterferometerSettings::CorrelationType) response.getInterferometerSettings()->getCorrelationType();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getInterferometerSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getInterferometerSettings()->getTitle();
}
// scope
if (channelSettingsKeys.contains("scopeConfig"))
{
if (channelSettingsKeys.contains("scopeConfig.displayMode")) {
scopeSettings.m_displayMode = (GLScopeSettings::DisplayMode) response.getInterferometerSettings()->getScopeConfig()->getDisplayMode();
}
if (channelSettingsKeys.contains("scopeConfig.gridIntensity")) {
scopeSettings.m_gridIntensity = response.getInterferometerSettings()->getScopeConfig()->getGridIntensity();
}
if (channelSettingsKeys.contains("scopeConfig.time")) {
scopeSettings.m_time = response.getInterferometerSettings()->getScopeConfig()->getTime();
}
if (channelSettingsKeys.contains("scopeConfig.timeOfs")) {
scopeSettings.m_timeOfs = response.getInterferometerSettings()->getScopeConfig()->getTimeOfs();
}
if (channelSettingsKeys.contains("scopeConfig.traceIntensity")) {
scopeSettings.m_traceIntensity = response.getInterferometerSettings()->getScopeConfig()->getTraceIntensity();
}
if (channelSettingsKeys.contains("scopeConfig.traceLen")) {
scopeSettings.m_traceLen = response.getInterferometerSettings()->getScopeConfig()->getTraceLen();
}
if (channelSettingsKeys.contains("scopeConfig.trigPre")) {
scopeSettings.m_trigPre = response.getInterferometerSettings()->getScopeConfig()->getTrigPre();
}
// traces
if (channelSettingsKeys.contains("scopeConfig.tracesData"))
{
QList<SWGSDRangel::SWGTraceData *> *tracesData = response.getInterferometerSettings()->getScopeConfig()->getTracesData();
scopeSettings.m_tracesData.clear();
for (int i = 0; i < 10; i++) // no more than 10 traces anyway
{
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1]").arg(i)))
{
SWGSDRangel::SWGTraceData *traceData = tracesData->at(i);
scopeSettings.m_tracesData.push_back(GLScopeSettings::TraceData());
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].amp").arg(i))) {
scopeSettings.m_tracesData.back().m_amp = traceData->getAmp();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].ampIndex").arg(i))) {
scopeSettings.m_tracesData.back().m_ampIndex = traceData->getAmpIndex();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].hasTextOverlay").arg(i))) {
scopeSettings.m_tracesData.back().m_hasTextOverlay = traceData->getHasTextOverlay() != 0;
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].inputIndex").arg(i))) {
scopeSettings.m_tracesData.back().m_inputIndex = traceData->getInputIndex();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].ofs").arg(i))) {
scopeSettings.m_tracesData.back().m_ofs = traceData->getOfs();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].ofsCoarse").arg(i))) {
scopeSettings.m_tracesData.back().m_ofsCoarse = traceData->getOfsCoarse();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].ofsFine").arg(i))) {
scopeSettings.m_tracesData.back().m_ofsFine = traceData->getOfsFine();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].projectionType").arg(i))) {
scopeSettings.m_tracesData.back().m_projectionType = (Projector::ProjectionType) traceData->getProjectionType();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColor").arg(i))) {
scopeSettings.m_tracesData.back().m_traceColor = intToQColor(traceData->getTraceColor());
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorB").arg(i))) {
scopeSettings.m_tracesData.back().m_traceColorB = traceData->getTraceColorB();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorG").arg(i))) {
scopeSettings.m_tracesData.back().m_traceColorG = traceData->getTraceColorG();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceColorR").arg(i))) {
scopeSettings.m_tracesData.back().m_traceColorR = traceData->getTraceColorR();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelay").arg(i))) {
scopeSettings.m_tracesData.back().m_traceDelay = traceData->getTraceDelay();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelayCoarse").arg(i))) {
scopeSettings.m_tracesData.back().m_traceDelayCoarse = traceData->getTraceDelayCoarse();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].traceDelayFine").arg(i))) {
scopeSettings.m_tracesData.back().m_traceDelayFine = traceData->getTraceDelayFine();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].triggerDisplayLevel").arg(i))) {
scopeSettings.m_tracesData.back().m_triggerDisplayLevel = traceData->getTriggerDisplayLevel();
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].viewTrace").arg(i))) {
scopeSettings.m_tracesData.back().m_viewTrace = traceData->getViewTrace() != 0;
}
}
else
{
break;
}
}
}
// triggers
if (channelSettingsKeys.contains("scopeConfig.triggersData"))
{
QList<SWGSDRangel::SWGTriggerData *> *triggersData = response.getInterferometerSettings()->getScopeConfig()->getTriggersData();
scopeSettings.m_triggersData.clear();
for (int i = 0; i < 10; i++) // no more than 10 triggers anyway
{
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1]").arg(i)))
{
SWGSDRangel::SWGTriggerData *triggerData = triggersData->at(i);
scopeSettings.m_triggersData.push_back(GLScopeSettings::TriggerData());
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].inputIndex").arg(i))) {
scopeSettings.m_triggersData.back().m_inputIndex = triggerData->getInputIndex();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].projectionType").arg(i))) {
scopeSettings.m_triggersData.back().m_projectionType = (Projector::ProjectionType) triggerData->getProjectionType();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerBothEdges").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerBothEdges = triggerData->getTriggerBothEdges() != 0;
}
if (channelSettingsKeys.contains(QString("scopeConfig.tracesData[%1].triggerColor").arg(i))) {
scopeSettings.m_tracesData.back().m_traceColor = intToQColor(triggerData->getTriggerColor());
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorB").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerColorB = triggerData->getTriggerColorB();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorG").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerColorG = triggerData->getTriggerColorG();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerColorR").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerColorR = triggerData->getTriggerColorR();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelay").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerDelay = triggerData->getTriggerDelay();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayCoarse").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerDelayCoarse = triggerData->getTriggerDelayCoarse();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayFine").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerDelayFine = triggerData->getTriggerDelayFine();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerDelayMult").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerDelayMult = triggerData->getTriggerDelayMult();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerHoldoff").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerHoldoff = triggerData->getTriggerHoldoff();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevel").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerLevel = triggerData->getTriggerLevel();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevelCoarse").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerLevelCoarse = triggerData->getTriggerLevelCoarse();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerLevelFine").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerLevelFine = triggerData->getTriggerLevelFine();
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerPositiveEdge").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerPositiveEdge = triggerData->getTriggerPositiveEdge() != 0;
}
if (channelSettingsKeys.contains(QString("scopeConfig.triggersData[%1].triggerRepeat").arg(i))) {
scopeSettings.m_triggersData.back().m_triggerRepeat = triggerData->getTriggerRepeat() != 0;
}
}
}
}
}
// spectrum
if (channelSettingsKeys.contains("spectrumConfig"))
{
if (channelSettingsKeys.contains("spectrumConfig.averagingMode")) {
spectrumSettings.m_averagingMode = (GLSpectrumSettings::AveragingMode) response.getInterferometerSettings()->getSpectrumConfig()->getAveragingMode();
}
if (channelSettingsKeys.contains("spectrumConfig.averagingValue"))
{
spectrumSettings.m_averagingValue = response.getInterferometerSettings()->getSpectrumConfig()->getAveragingValue();
spectrumSettings.m_averagingIndex = GLSpectrumSettings::getAveragingIndex(spectrumSettings.m_averagingValue, spectrumSettings.m_averagingMode);
}
if (channelSettingsKeys.contains("spectrumConfig.decay")) {
spectrumSettings.m_decay = response.getInterferometerSettings()->getSpectrumConfig()->getDecay();
}
if (channelSettingsKeys.contains("spectrumConfig.decayDivisor")) {
spectrumSettings.m_decayDivisor = response.getInterferometerSettings()->getSpectrumConfig()->getDecayDivisor();
}
if (channelSettingsKeys.contains("spectrumConfig.displayCurrent")) {
spectrumSettings.m_displayCurrent = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayCurrent() != 0;
}
if (channelSettingsKeys.contains("spectrumConfig.displayGrid")) {
spectrumSettings.m_displayGrid = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayGrid() != 0;
}
if (channelSettingsKeys.contains("spectrumConfig.displayGridIntensity")) {
spectrumSettings.m_displayGridIntensity = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayGridIntensity();
}
if (channelSettingsKeys.contains("spectrumConfig.displayHistogram")) {
spectrumSettings.m_displayHistogram = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayHistogram() != 0;
}
if (channelSettingsKeys.contains("spectrumConfig.displayMaxHold")) {
spectrumSettings.m_displayMaxHold = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayMaxHold() != 0;
}
if (channelSettingsKeys.contains("spectrumConfig.displayTraceIntensity")) {
spectrumSettings.m_displayTraceIntensity = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayTraceIntensity();
}
if (channelSettingsKeys.contains("spectrumConfig.displayWaterfall")) {
spectrumSettings.m_displayWaterfall = response.getInterferometerSettings()->getSpectrumConfig()->getDisplayWaterfall() != 0;
}
if (channelSettingsKeys.contains("spectrumConfig.fftOverlap")) {
spectrumSettings.m_fftOverlap = response.getInterferometerSettings()->getSpectrumConfig()->getFftOverlap();
}
if (channelSettingsKeys.contains("spectrumConfig.fftSize")) {
spectrumSettings.m_fftSize = response.getInterferometerSettings()->getSpectrumConfig()->getFftSize();
}
}
}
int InterferometerWebAPIAdapter::qColorToInt(const QColor& color)
{
return 256*256*color.blue() + 256*color.green() + color.red();
}
QColor InterferometerWebAPIAdapter::intToQColor(int intColor)
{
int r = intColor % 256;
int bg = intColor / 256;
int g = bg % 256;
int b = bg / 256;
return QColor(r, g, b);
}

Wyświetl plik

@ -0,0 +1,69 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_INTERFEROMETER_WEBAPIADAPTER_H
#define INCLUDE_INTERFEROMETER_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "dsp/glscopesettings.h"
#include "dsp/glspectrumsettings.h"
#include "interferometersettings.h"
/**
* Standalone API adapter only for the settings
*/
class InterferometerWebAPIAdapter : public ChannelWebAPIAdapter {
public:
InterferometerWebAPIAdapter();
virtual ~InterferometerWebAPIAdapter();
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);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const InterferometerSettings& settings,
const GLScopeSettings& scopeSettings,
const GLSpectrumSettings& spectrumSettings);
static void webapiUpdateChannelSettings(
InterferometerSettings& settings,
GLScopeSettings& scopeSettings,
GLSpectrumSettings& spectrumSettings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
private:
InterferometerSettings m_settings;
GLScopeSettings m_glScopeSettings;
GLSpectrumSettings m_glSpectrumSettings;
static int qColorToInt(const QColor& color);
static QColor intToQColor(int intColor);
};
#endif // INCLUDE_INTERFEROMETER_WEBAPIADAPTER_H

Wyświetl plik

@ -0,0 +1,158 @@
<h1>Interferometer plugin</h1>
<h2>Introduction</h2>
This MIMO reception only (MI) plugin can be used to study phase difference between two coherent streams. It must be connected to a device that exposes at least two input streams and will connect to streams 0 and 1 as channels A and B respectively.
<h2>Interface</h2>
![Interferometer plugin GUI](../../../doc/img/Interferometer_plugin.png)
The interface is divided in 3 sections that will be detailed next:
- A: settings. These are the plugin controls
- B: spectrum (frequency domain). This is a spectrum display analogous to other spectrum displays. Its input varies depending on the correlation function selected
- C: scope (time domain). This is a scope display analogous to other scope displays. Its input varies depending on the correlation function selected. For FFT type correlation this is not a time domain but a frequency domain display transposed to time analogous to a frequency sweep.
<h2>A. Settings section</h2>
![Interferometer plugin setings GUI](../../../doc/img/Interferometer_settings.png)
<h3>A.1. Decimation</h3>
Input streams frome baseband are decimated by a power of two. Use this combo to select from 0 (no decimation) to 64 (2^6). The resulting channel sample rate is displayed next (A.2)
<h3>A.2. Channel sample rate</h3>
This is the channel sample rate in kilo or mega samples per second indicated by the `k` or `M` letter.
<h3>A.3. Half-band filter chain sequence display</h3>
This string represents the sequence of half-band filters used in the decimation from device baseband to channel stream and controlled by (A.7). Each character represents a filter type:
- **L**: lower half-band
- **H**: higher half-band
- **C**: centered
<h3>A.4. Correlation function</h3>
This combo selects which function is applied to A and B channel inputs to obtain resulting correlation signal.
- **A**: A channel only (no correlation)
- **B**: B channel only (no correlation)
- **A+B**: This is the complex sum of A and B
- **A.B***: This is the complex multiplication of A with the conjugate of B
- **IFFT**: This is a time correlation obtained by FFT. The FFT of A is multiplied by the conjugate of the FFT of B and the result is passed to an IFFT to obtain the final result. This can be used for passive radar.
- **IFFT***: Same FFT operations as above but the IFFT is handled differently: sums the first half of IFFT with the reversed second half as one is the conjugate of the other so this should yield constant phase
- **FFT**: The FFT of A is multiplied by the conjugate of the FFT of B (no IFFT) thus the result is in the frequency domain. This is sometimes called "interspectrum". It can be used in practice to evaluate the direction of arrival (DOA) of a plane wave (see next)
- **IFFT2**: Same as IFFT but looks at the entire time domain including "negative" times.
<h4>Spectrum and scope inputs</h4>
The spectrum and scope displays inputs vary depending on the correlation types and are summarized in the table below:
<table>
<tr>
<th>Correlation type</th>
<th>Spectrum</th>
<th>Scope</th>
</tr>
<tr>
<td>A</td>
<td>FFT(A)</td>
<td>A(t)</td>
</tr>
<tr>
<td>B</td>
<td>FFT(B)</td>
<td>B(t)</td>
</tr>
<tr>
<td>A+B</td>
<td>FFT(A+B)</td>
<td>(A+B)(t)</td>
</tr>
<tr>
<td>A.B*</td>
<td>FFT(A.B*)</td>
<td>(A.B*)(t)</td>
</tr>
<tr>
<td>IFFT</td>
<td>FFT(A)*FFT(B)*</td>
<td>IFFT(FFT(A).FFT(B)*)</td>
</tr>
<tr>
<td>IFFT</td>
<td>FFT(A)*FFT(B)*</td>
<td>IFFT(FFT(A).FFT(B)*) with special manipulation (see above)</td>
</tr>
<tr>
<td>FFT</td>
<td>FFT(A)*FFT(B)* over 1 FFT length</td>
<td>FFT(A)*FFT(B)* possibly over several FFT lengths depending on trace length</td>
</tr>
<tr>
<td>IFFT2</td>
<td>FFT(A)*FFT(B)*</td>
<td>IFFT(FFT(A).FFT(B)*) with positive and negative times</td>
</tr>
</table>
<h4>Direction Of Arrival (DOA) estimation with FFT correlation</h4>
The "FFT" correlation function can be selected for DOA estimation. "A.B*" should yield similar results but is less precise because FFT analysis helps in removing non essential contributions. The FFT analysis can also show different DOAs for signals at different frequencies.
It assumes that channel A is connected to the antenna of reference (device stream 0) and channel B is connected to the second antenna (device stream 1) in the following configuration:
![Interferometer antennas](../../../doc/img/interferometer_antennas.png)
The scope is configured to have X and Y displays with Y1 set to a magnitude display projection and X to a phase related projection. See scope controls in C section for setup. Here are the different possibilities:
- **X**: Phi, DOAP, DOAN
- **Y1**: Mag, MagSq, MagDB (this one is usually the most convenient)
You will select the XY display on the scope and you can use the polar grid display to show phase or direction angles directly.
Angles are counted from -&pi; to &pi; and normalized to &pi; for display thus displayed from -1.0 to 1.0
The phase difference on X input (`Phi` selected) is then the actual phase difference between the reference signal A and the second signal B. Thus for a given phase difference the wave may come from the positive side of angles (0 to &pi;) or the negative side (-&pi; to 0). Angles of arrival are referenced to the axis perpendicular to the axis passing by the two antennas.
![Interferometer DOA](../../../doc/img/interferometer_doa.png)
Thus when antennas are separated by half the wavelength the relation between the angle of arrival &theta; and the phase difference &phi; can be expressed as:
&phi; = &pi; cos(&theta;) thus &theta; = acos(&phi; / &pi;)
This angle can be displayed directly when `DOAP` (positive angles) or `DOAN` (negative angles) is selected for X input.
Thus a possible procedure to determine DOA could be the following:
1. Arrange antennas axis so that the phase difference &phi; or DOA angle &theta; is roughly &pi;/2 (zero phase difference)
2. Make an assumption for the wave to come from the positive or negative angles side
3. Rotate the antennas axis slightly and if the DOA angle moves in the direction corresponding to your assumption (2) then the assumption is correct and the wave is coming from the side corresponding to your assumption. You can then refine the antenna axis direction to obtain a &pi;/2 or -&pi;/2 angle depending from which side the wave is coming. The scope `DOAP` projection is for waves coming from the positive angles side and `DOAN` for the negative angles side
4. If when performing previous step (3) the DOA angle moves in the opposite direction to the one corresponding to your assumption then the wave is coming from the opposite side w.r to your assumption. You can then refine the antenna axis direction to obtain a &plusmn;&pi;/2 DOA as in (3).
5. Once the &plusmn;&pi;/2 DOA angle (zero phase difference) is obtained at &lambda;/2 distance betweeen antennas you can move your antennas further apart to refine the &plusmn;&pi;/2 DOA angle.
<h3>A.5. Phase difference correction</h3>
This is the phase correction in degrees applied to signal in channel B.
<h3>A.6 Center frequency shift</h3>
This is the shift of the channel center frequency from the device center frequency. Its value is driven by the baseband sample rate, the decimation factor (A.1) and the filter chain sequence (A.7).
<h3>A.7. Half-band filter chain sequence adjust</h3>
The slider moves the channel center frequency roughly from the lower to the higher frequency in the device baseband. The number on the right represents the filter sequence as the decimal value of a base 3 number. Each base 3 digit represents the filter type and its sequence from MSB to LSB in the filter chain:
- **0**: lower half-band
- **1**: centered
- **2**: higher half-band
The resulting filter chain sequence is represented in (A.3)
<h2>B. Spectrum display</h2>
This is the spectrum (frequency domain) display. This display and controls are identical to all spectrum displays in the software. Further details on spectrum controls can be found [here](https://github.com/f4exb/sdrangel/tree/master/sdrgui#4-spectrum-display-control)
<h2>C. Scope display</h2>
This is the scope (mainly time domain) display. This display and controls are identical to all scope displays in the software. Further details can be found in [Channel Analyzer documentation](../../channelrx/chanalyzer/readme.md)

Wyświetl plik

@ -132,7 +132,7 @@ void ChannelAnalyzerWebAPIAdapter::webapiFormatChannelSettings(
swgSpectrum->init();
response.getChannelAnalyzerSettings()->setSpectrumConfig(swgSpectrum);
swgSpectrum->setAveragingMode((int) spectrumSettings.m_averagingMode);
swgSpectrum->setAveragingValue(spectrumSettings.m_averagingNb);
swgSpectrum->setAveragingValue(spectrumSettings.m_averagingValue);
swgSpectrum->setDecay(spectrumSettings.m_decay);
swgSpectrum->setDecayDivisor(spectrumSettings.m_decayDivisor);
swgSpectrum->setDisplayCurrent(spectrumSettings.m_displayCurrent ? 1 : 0);
@ -380,7 +380,7 @@ void ChannelAnalyzerWebAPIAdapter::webapiUpdateChannelSettings(
spectrumSettings.m_averagingMode = (GLSpectrumSettings::AveragingMode) response.getChannelAnalyzerSettings()->getSpectrumConfig()->getAveragingMode();
}
if (channelSettingsKeys.contains("spectrumConfig.averagingValue")) {
spectrumSettings.m_averagingNb = response.getChannelAnalyzerSettings()->getSpectrumConfig()->getAveragingValue();
spectrumSettings.m_averagingValue = response.getChannelAnalyzerSettings()->getSpectrumConfig()->getAveragingValue();
}
if (channelSettingsKeys.contains("spectrumConfig.decay")) {
spectrumSettings.m_decay = response.getChannelAnalyzerSettings()->getSpectrumConfig()->getDecay();
@ -430,4 +430,4 @@ QColor ChannelAnalyzerWebAPIAdapter::intToQColor(int intColor)
int g = bg % 256;
int b = bg / 256;
return QColor(r, g, b);
}
}

Wyświetl plik

@ -39,16 +39,16 @@ void GLSpectrumSettings::resetToDefaults()
m_histogramStroke = 30;
m_displayGridIntensity = 5;
m_displayTraceIntensity = 50;
m_waterfallShare = 0.66;
m_displayCurrent = true;
m_displayWaterfall = true;
m_invertedWaterfall = true;
m_waterfallShare = 0.66;
m_displayMaxHold = false;
m_displayHistogram = false;
m_displayGrid = false;
m_invert = true;
m_averagingMode = AvgModeNone;
m_averagingIndex = 0;
m_averagingValue = 1;
m_linear = false;
m_ssb = false;
m_usb = true;
@ -69,7 +69,6 @@ QByteArray GLSpectrumSettings::serialize() const
s.writeBool(9, m_displayHistogram);
s.writeS32(10, m_decay);
s.writeBool(11, m_displayGrid);
s.writeBool(12, m_invert);
s.writeS32(13, m_displayGridIntensity);
s.writeS32(14, m_decayDivisor);
s.writeS32(15, m_histogramStroke);
@ -110,7 +109,6 @@ bool GLSpectrumSettings::deserialize(const QByteArray& data)
d.readBool(9, &m_displayHistogram, false);
d.readS32(10, &m_decay, 1);
d.readBool(11, &m_displayGrid, false);
d.readBool(12, &m_invert, true);
d.readS32(13, &m_displayGridIntensity, 5);
d.readS32(14, &m_decayDivisor, 1);
d.readS32(15, &m_histogramStroke, 30);
@ -121,7 +119,7 @@ bool GLSpectrumSettings::deserialize(const QByteArray& data)
m_averagingMode = tmp < 0 ? AvgModeNone : tmp > 3 ? AvgModeMax : (AveragingMode) tmp;
d.readS32(20, &tmp, 0);
m_averagingIndex = getAveragingIndex(tmp, m_averagingMode);
m_averagingNb = getAveragingValue(m_averagingIndex, m_averagingMode);
m_averagingValue = getAveragingValue(m_averagingIndex, m_averagingMode);
d.readBool(21, &m_linear, false);
d.readBool(24, &m_ssb, false);
d.readBool(25, &m_usb, true);

Wyświetl plik

@ -53,10 +53,9 @@ public:
bool m_displayCurrent;
bool m_displayHistogram;
bool m_displayGrid;
bool m_invert;
AveragingMode m_averagingMode;
int m_averagingIndex;
unsigned int m_averagingNb;
unsigned int m_averagingValue;
bool m_linear; //!< linear else logarithmic scale
bool m_ssb; //!< SSB display with spectrum center at start of array or display - else spectrum center is on center
bool m_usb; //!< USB display with increasing frequencies towads the right - else decreasing frequencies

Wyświetl plik

@ -155,7 +155,6 @@ void WebAPIAdapterBase::webapiFormatPreset(
swgSpectrumConfig->setDisplayHistogram(m_spectrumSettings.m_displayHistogram ? 1 : 0);
swgSpectrumConfig->setDecay(m_spectrumSettings.m_decay);
swgSpectrumConfig->setDisplayGrid(m_spectrumSettings.m_displayGrid ? 1 : 0);
swgSpectrumConfig->setInvert(m_spectrumSettings.m_invert ? 1 : 0);
swgSpectrumConfig->setDisplayGridIntensity(m_spectrumSettings.m_displayGridIntensity);
swgSpectrumConfig->setDecayDivisor(m_spectrumSettings.m_decayDivisor);
swgSpectrumConfig->setHistogramStroke(m_spectrumSettings.m_histogramStroke);
@ -316,8 +315,8 @@ void WebAPIAdapterBase::webapiUpdatePreset(
}
if (spectrumIt->contains("averagingValue"))
{
spectrumSettings.m_averagingNb = apiPreset->getSpectrumConfig()->getAveragingValue();
spectrumSettings.m_averagingIndex = GLSpectrumSettings::getAveragingIndex(spectrumSettings.m_averagingNb, spectrumSettings.m_averagingMode);
spectrumSettings.m_averagingValue = apiPreset->getSpectrumConfig()->getAveragingValue();
spectrumSettings.m_averagingIndex = GLSpectrumSettings::getAveragingIndex(spectrumSettings.m_averagingValue, spectrumSettings.m_averagingMode);
}
if (spectrumIt->contains("decay")) {
spectrumSettings.m_decay = apiPreset->getSpectrumConfig()->getDecay();
@ -358,9 +357,6 @@ void WebAPIAdapterBase::webapiUpdatePreset(
if (spectrumIt->contains("histogramStroke")) {
spectrumSettings.m_histogramStroke = apiPreset->getSpectrumConfig()->getHistogramStroke();
}
if (spectrumIt->contains("invert")) {
spectrumSettings.m_invert = apiPreset->getSpectrumConfig()->getInvert() != 0;
}
if (spectrumIt->contains("invertedWaterfall")) {
spectrumSettings.m_invertedWaterfall = apiPreset->getSpectrumConfig()->getInvertedWaterfall() != 0;
}

Wyświetl plik

@ -110,7 +110,7 @@ void GLSpectrumGUI::displaySettings()
ui->maxHold->setChecked(m_settings.m_displayMaxHold);
ui->current->setChecked(m_settings.m_displayCurrent);
ui->histogram->setChecked(m_settings.m_displayHistogram);
ui->invert->setChecked(m_settings.m_invert);
ui->invertWaterfall->setChecked(m_settings.m_invertedWaterfall);
ui->grid->setChecked(m_settings.m_displayGrid);
ui->gridIntensity->setSliderPosition(m_settings.m_displayGridIntensity);
@ -169,7 +169,7 @@ void GLSpectrumGUI::applySettings()
m_glSpectrum->setDecay(m_settings.m_decay);
m_glSpectrum->setDecayDivisor(m_settings.m_decayDivisor);
m_glSpectrum->setHistoStroke(m_settings.m_histogramStroke);
m_glSpectrum->setInvertedWaterfall(m_settings.m_invert);
m_glSpectrum->setInvertedWaterfall(m_settings.m_invertedWaterfall);
m_glSpectrum->setDisplayGrid(m_settings.m_displayGrid);
m_glSpectrum->setDisplayGridIntensity(m_settings.m_displayGridIntensity);
m_glSpectrum->setDisplayTraceIntensity(m_settings.m_displayTraceIntensity);
@ -306,9 +306,9 @@ void GLSpectrumGUI::on_current_toggled(bool checked)
applySettings();
}
void GLSpectrumGUI::on_invert_toggled(bool checked)
void GLSpectrumGUI::on_invertWaterfall_toggled(bool checked)
{
m_settings.m_invert = checked;
m_settings.m_invertedWaterfall = checked;
applySettings();
}

Wyświetl plik

@ -98,7 +98,7 @@ private slots:
void on_histogram_toggled(bool checked);
void on_maxHold_toggled(bool checked);
void on_current_toggled(bool checked);
void on_invert_toggled(bool checked);
void on_invertWaterfall_toggled(bool checked);
void on_grid_toggled(bool checked);
void on_clearSpectrum_clicked(bool checked);
void on_freeze_toggled(bool checked);

Wyświetl plik

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>378</width>
<height>59</height>
<width>420</width>
<height>75</height>
</rect>
</property>
<property name="font">
@ -215,7 +215,7 @@
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="invert">
<widget class="ButtonSwitch" name="invertWaterfall">
<property name="toolTip">
<string>Exchange waterfall and histogram</string>
</property>
@ -721,7 +721,7 @@
<tabstops>
<tabstop>histogram</tabstop>
<tabstop>maxHold</tabstop>
<tabstop>invert</tabstop>
<tabstop>invertWaterfall</tabstop>
<tabstop>grid</tabstop>
</tabstops>
<resources>