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