diff --git a/plugins/samplemimo/CMakeLists.txt b/plugins/samplemimo/CMakeLists.txt index 9a5923418..2eca65258 100644 --- a/plugins/samplemimo/CMakeLists.txt +++ b/plugins/samplemimo/CMakeLists.txt @@ -16,6 +16,7 @@ if(ENABLE_IIO AND LIBIIO_FOUND) add_subdirectory(plutosdrmimo) endif() +add_subdirectory(audiocatsiso) add_subdirectory(metismiso) add_subdirectory(testmi) add_subdirectory(testmosync) diff --git a/plugins/samplemimo/audiocatsiso/CMakeLists.txt b/plugins/samplemimo/audiocatsiso/CMakeLists.txt new file mode 100644 index 000000000..26c48bc94 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/CMakeLists.txt @@ -0,0 +1,64 @@ +project(audiocatsiso) + +set(audiocatsiso_SOURCES + audiocatinputworker.cpp + audiocatoutputworker.cpp + audiocatsiso.cpp + audiocatsisoplugin.cpp + audiocatsisosettings.cpp + audiocatsisowebapiadapter.cpp +) + +set(audiocatsiso_HEADERS + audiocatinputworker.h + audiocatoutputworker.h + audiocatsiso.h + audiocatsisoplugin.h + audiocatsisosettings.h + audiocatsisowebapiadapter.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices +) + +if (NOT SERVER_MODE) + set (audiocatsiso_SOURCES + ${audiocatsiso_SOURCES} + audiocatsisogui.cpp + audiocatsisogui.ui + ) + set(audiocatsiso_HEADERS + ${audiocatsiso_HEADERS} + audiocatsisogui.h + ) + set(TARGET_NAME mimoaudiocatsiso) + set(TARGET_LIB "Qt::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME mimoaudiocatsisosrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${audiocatsiso_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) + +# Install debug symbols +if (WIN32) + install(FILES $ CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} ) +endif() diff --git a/plugins/samplemimo/audiocatsiso/audiocatinputworker.cpp b/plugins/samplemimo/audiocatsiso/audiocatinputworker.cpp new file mode 100644 index 000000000..9d45dc08e --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatinputworker.cpp @@ -0,0 +1,205 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "dsp/samplemififo.h" +#include "audio/audiofifo.h" + +#include "audiocatinputworker.h" + +AudioCATInputWorker::AudioCATInputWorker(SampleMIFifo* sampleFifo, AudioFifo *fifo, QObject* parent) : + QObject(parent), + m_fifo(fifo), + m_running(false), + m_log2Decim(0), + m_iqMapping(AudioCATSISOSettings::IQMapping::L), + m_convertBuffer(m_convBufSamples), + m_sampleFifo(sampleFifo), + m_quNCOPhase(0) +{ +} + +AudioCATInputWorker::~AudioCATInputWorker() +{ +} + +void AudioCATInputWorker::startWork() +{ + connect(m_fifo, SIGNAL(dataReady()), this, SLOT(handleAudio())); + m_running = true; +} + +void AudioCATInputWorker::stopWork() +{ + disconnect(m_fifo, SIGNAL(dataReady()), this, SLOT(handleAudio())); + m_running = false; +} + +void AudioCATInputWorker::workIQ(unsigned int nbRead) +{ + // Map between left and right audio channels and IQ channels + if ((m_iqMapping == AudioCATSISOSettings::IQMapping::L) || // mono + (m_iqMapping == AudioCATSISOSettings::IQMapping::R)) + { + for (uint32_t i = 0; i < nbRead; i++) + { + qint16 r = m_buf[i*2 + (m_iqMapping == AudioCATSISOSettings::IQMapping::R ? 1 : 0)]; // real sample + + if (m_quNCOPhase == 0) // 0 + { + m_buf[i*2] = r; // 1 + m_buf[i*2+1] = 0; // 0 + m_quNCOPhase = 1; // next phase + } + else if (m_quNCOPhase == 1) // -pi/2 + { + m_buf[i*2] = 0; // 0 + m_buf[i*2+1] = -r; // -1 + m_quNCOPhase = 2; // next phase + } + else if (m_quNCOPhase == 2) // pi or -pi + { + m_buf[i*2] = -r; // -1 + m_buf[i*2+1] = 0; // 0 + m_quNCOPhase = 3; // next phase + } + else if (m_quNCOPhase == 3) // pi/2 + { + m_buf[i*2] = 0; // 0 + m_buf[i*2+1] = r; // 1 + m_quNCOPhase = 0; // next phase + } + } + } + else if (m_iqMapping == AudioCATSISOSettings::IQMapping::LR) // stereo - reverse + { + for (uint32_t i = 0; i < nbRead; i++) + { + qint16 t = m_buf[i*2]; + m_buf[i*2] = m_buf[i*2+1]; + m_buf[i*2+1] = t; + } + } + + decimate(m_buf, nbRead); +} + +void AudioCATInputWorker::decimate(qint16 *buf, unsigned int nbRead) +{ + SampleVector::iterator it = m_convertBuffer.begin(); + + if (m_log2Decim == 0) + { + m_decimatorsIQ.decimate1(&it, buf, 2*nbRead); + } + else + { + if (m_fcPos == 0) // Infradyne + { + switch (m_log2Decim) + { + case 1: + m_decimatorsIQ.decimate2_inf(&it, buf, 2*nbRead); + break; + case 2: + m_decimatorsIQ.decimate4_inf(&it, buf, 2*nbRead); + break; + case 3: + m_decimatorsIQ.decimate8_inf(&it, buf, 2*nbRead); + break; + case 4: + m_decimatorsIQ.decimate16_inf(&it, buf, 2*nbRead); + break; + case 5: + m_decimatorsIQ.decimate32_inf(&it, buf, 2*nbRead); + break; + case 6: + m_decimatorsIQ.decimate64_inf(&it, buf, 2*nbRead); + break; + default: + break; + } + } + else if (m_fcPos == 1) // Supradyne + { + switch (m_log2Decim) + { + case 1: + m_decimatorsIQ.decimate2_sup(&it, buf, 2*nbRead); + break; + case 2: + m_decimatorsIQ.decimate4_sup(&it, buf, 2*nbRead); + break; + case 3: + m_decimatorsIQ.decimate8_sup(&it, buf, 2*nbRead); + break; + case 4: + m_decimatorsIQ.decimate16_sup(&it, buf, 2*nbRead); + break; + case 5: + m_decimatorsIQ.decimate32_sup(&it, buf, 2*nbRead); + break; + case 6: + m_decimatorsIQ.decimate64_sup(&it, buf, 2*nbRead); + break; + default: + break; + } + } + else // centered + { + switch (m_log2Decim) + { + case 1: + m_decimatorsIQ.decimate2_cen(&it, buf, 2*nbRead); + break; + case 2: + m_decimatorsIQ.decimate4_cen(&it, buf, 2*nbRead); + break; + case 3: + m_decimatorsIQ.decimate8_cen(&it, buf, 2*nbRead); + break; + case 4: + m_decimatorsIQ.decimate16_cen(&it, buf, 2*nbRead); + break; + case 5: + m_decimatorsIQ.decimate32_cen(&it, buf, 2*nbRead); + break; + case 6: + m_decimatorsIQ.decimate64_cen(&it, buf, 2*nbRead); + break; + default: + break; + } + } + } + + // qDebug("AudioCATInputWorker::decimate: %ld to write - in: %u", it - m_convertBuffer.begin(), nbRead ); + m_sampleFifo->writeAsync(m_convertBuffer.begin(), it - m_convertBuffer.begin(), 0); +} + +void AudioCATInputWorker::handleAudio() +{ + uint32_t nbRead; + + while ((nbRead = m_fifo->read((unsigned char *) m_buf, m_convBufSamples)) != 0) { + workIQ(nbRead); + } +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatinputworker.h b/plugins/samplemimo/audiocatsiso/audiocatinputworker.h new file mode 100644 index 000000000..ce47f7416 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatinputworker.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 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_AUDIOCATINPUTWORKER_H +#define INCLUDE_AUDIOCATINPUTWORKER_H + +#include + +#include "dsp/decimators.h" +#include "audiocatsisosettings.h" + +class AudioFifo; +class SampleMIFifo; + +class AudioCATInputWorker : public QObject { + Q_OBJECT + +public: + AudioCATInputWorker(SampleMIFifo* sampleFifo, AudioFifo *fifo, QObject* parent = nullptr); + ~AudioCATInputWorker(); + + void startWork(); + void stopWork(); + void setLog2Decimation(unsigned int log2_decim) {m_log2Decim = log2_decim;} + void setFcPos(int fcPos) { m_fcPos = fcPos; } + void setIQMapping(AudioCATSISOSettings::IQMapping iqMapping) {m_iqMapping = iqMapping;} + + static const int m_convBufSamples = 4096; +private: + AudioFifo* m_fifo; + + bool m_running; + unsigned int m_log2Decim; + int m_fcPos; + AudioCATSISOSettings::IQMapping m_iqMapping; + + qint16 m_buf[m_convBufSamples*2]; // stereo (I, Q) + SampleVector m_convertBuffer; + SampleMIFifo* m_sampleFifo; + Decimators m_decimatorsIQ; + int m_quNCOPhase; //!< Quarter sample rate pseudo NCO phase index (0, 90, 180, 270) + + void workIQ(unsigned int nbRead); + void decimate(qint16 *buf, unsigned int nbRead); + +private slots: + void handleAudio(); +}; + +#endif // INCLUDE_AUDIOCATINPUTWORKER_H diff --git a/plugins/samplemimo/audiocatsiso/audiocatoutputworker.cpp b/plugins/samplemimo/audiocatsiso/audiocatoutputworker.cpp new file mode 100644 index 000000000..afbc192c6 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatoutputworker.cpp @@ -0,0 +1,158 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/samplemofifo.h" +#include "dsp/samplesourcefifo.h" +#include "audio/audiofifo.h" + +#include "audiocatoutputworker.h" + +#define AUDIOOUTPUT_THROTTLE_MS 50 + +AudioCATOutputWorker::AudioCATOutputWorker(SampleMOFifo* sampleFifo, AudioFifo *fifo, QObject* parent) : + QObject(parent), + m_running(false), + m_samplerate(0), + m_throttlems(AUDIOOUTPUT_THROTTLE_MS), + m_maxThrottlems(50), + m_throttleToggle(false), + m_iqMapping(AudioCATSISOSettings::IQMapping::LR), + m_buf(nullptr), + m_samplesChunkSize(0), + m_sampleFifo(sampleFifo), + m_audioFifo(fifo) +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; +} + +AudioCATOutputWorker::~AudioCATOutputWorker() +{ +} + +void AudioCATOutputWorker::startWork() +{ + qDebug("AudioCATOutputWorker::startWork"); + m_running = true; +} + +void AudioCATOutputWorker::stopWork() +{ + qDebug("AudioCATOutputWorker::stopWork"); + m_running = false; +} + +void AudioCATOutputWorker::connectTimer(const QTimer& timer) +{ + qDebug() << "AudioCATOutputWorker::connectTimer"; + connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); +} + +void AudioCATOutputWorker::setSamplerate(int samplerate) +{ + if (samplerate != m_samplerate) + { + bool wasRunning = false; + + if (m_running) + { + stopWork(); + wasRunning = true; + } + + // resize sample FIFO + if (m_sampleFifo) { + m_sampleFifo->init(1, SampleSourceFifo::getSizePolicy(samplerate)); // 1s buffer + } + + qDebug() << "AudioCATOutputWorker::setSamplerate:" + << " new:" << samplerate + << " old:" << m_samplerate + << " m_sampleFifo size:" << SampleSourceFifo::getSizePolicy(samplerate) + << " m_audioFifo size:" << m_audioFifo->size() + << " sample i/q size" << sizeof(FixReal); + + // resize output buffer + if (m_buf) { + delete[] m_buf; + } + + m_buf = new int16_t[samplerate*2]; + + m_samplerate = samplerate; + m_samplesChunkSize = (m_samplerate * m_throttlems) / 1000; + + if (wasRunning) { + startWork(); + } + } +} + +void AudioCATOutputWorker::tick() +{ + if (m_running) + { + qint64 throttlems = m_elapsedTimer.restart(); + + if (throttlems != m_throttlems) + { + m_throttlems = throttlems; + m_samplesChunkSize = (m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000; + m_throttleToggle = !m_throttleToggle; + } + + unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End; + SampleVector& data = m_sampleFifo->getData(0); + m_sampleFifo->readAsync(m_samplesChunkSize, iPart1Begin, iPart1End, iPart2Begin, iPart2End, 0); + + if (iPart1Begin != iPart1End) { + callbackPart(data, iPart1Begin, iPart1End); + } + + if (iPart2Begin != iPart2End) { + callbackPart(data, iPart2Begin, iPart2End); + } + + // qDebug("AudioCATOutputWorker::tick: %d samples fill: %u", m_samplesChunkSize, m_audioFifo->fill()); + } +} + +void AudioCATOutputWorker::callbackPart(SampleVector& data, unsigned int iBegin, unsigned int iEnd) +{ + for (unsigned int i = iBegin; i < iEnd; i++) + { + m_audioBuffer[m_audioBufferFill].l = m_iqMapping == AudioCATSISOSettings::LR ? data[i].m_real : data[i].m_imag; + m_audioBuffer[m_audioBufferFill].r = m_iqMapping == AudioCATSISOSettings::LR ? data[i].m_imag : data[i].m_real; + m_audioBufferFill++; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("AudioCATOutputWorker::callbackPart: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo->clear(); + } + + m_audioBufferFill = 0; + } + } +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatoutputworker.h b/plugins/samplemimo/audiocatsiso/audiocatoutputworker.h new file mode 100644 index 000000000..401f212ac --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatoutputworker.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AUDIOCATOUTPUTWORKER_H +#define INCLUDE_AUDIOCATOUTPUTWORKER_H + +#include +#include + +#include "dsp/interpolators.h" +#include "audiocatsisosettings.h" + +class QTimer; +class SampleMOFifo; +class AudioFifo; + +class AudioCATOutputWorker : public QObject { + Q_OBJECT + +public: + AudioCATOutputWorker(SampleMOFifo* sampleFifo, AudioFifo *fifo, QObject* parent = nullptr); + ~AudioCATOutputWorker(); + + void startWork(); + void stopWork(); + void setSamplerate(int samplerate); + void setIQMapping(AudioCATSISOSettings::IQMapping iqMapping) {m_iqMapping = iqMapping;} + void connectTimer(const QTimer& timer); + +private: + bool m_running; + int m_samplerate; + int m_throttlems; + int m_maxThrottlems; + QElapsedTimer m_elapsedTimer; + bool m_throttleToggle; + AudioCATSISOSettings::IQMapping m_iqMapping; + AudioVector m_audioBuffer; + uint32_t m_audioBufferFill; + qint16 *m_buf; // stereo (I, Q) + unsigned int m_samplesChunkSize; + SampleMOFifo* m_sampleFifo; + AudioFifo* m_audioFifo; + Interpolators m_interpolators; + + void callbackPart(SampleVector& data, unsigned int iBegin, unsigned int iEnd); + +private slots: + void tick(); +}; + +#endif // INCLUDE_AUDIOCATOUTPUTWORKER_H diff --git a/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp b/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp new file mode 100644 index 000000000..cda2f16f6 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsiso.cpp @@ -0,0 +1,838 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB // +// Copyright (C) 2020 Jon Beniston, M7RCE // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include +#include + +#include "SWGDeviceSettings.h" +#include "SWGDeviceState.h" + +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "dsp/samplesourcefifo.h" +#include "dsp/samplesinkfifo.h" +#include "dsp/devicesamplesource.h" +#include "dsp/devicesamplesink.h" +#include "device/deviceapi.h" + +#include "audiocatsiso.h" +#include "audiocatinputworker.h" +#include "audiocatoutputworker.h" + +MESSAGE_CLASS_DEFINITION(AudioCATSISO::MsgConfigureAudioCATSISO, Message) +MESSAGE_CLASS_DEFINITION(AudioCATSISO::MsgStartStop, Message) + +AudioCATSISO::AudioCATSISO(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_inputFifo(48000), + m_outputFifo(48000), + m_settings(), + m_inputWorker(nullptr), + m_outputWorker(nullptr), + m_inputWorkerThread(nullptr), + m_outputWorkerThread(nullptr), + m_deviceDescription("AudioCATSISO"), + m_rxRunning(false), + m_rxAudioDeviceIndex(-1), + m_txRunning(false), + m_txAudioDeviceIndex(-1), + m_masterTimer(deviceAPI->getMasterTimer()) +{ + m_mimoType = MIMOAsynchronous; + m_deviceAPI->setNbSourceStreams(1); + m_deviceAPI->setNbSinkStreams(1); + m_inputFifo.setLabel("Input"); + m_outputFifo.setLabel("Output"); + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + + m_rxSampleRate = audioDeviceManager->getInputSampleRate(m_rxAudioDeviceIndex); + m_settings.m_rxDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_sampleMIFifo.init(1, SampleSinkFifo::getSizePolicy(m_rxSampleRate)); + + m_txSampleRate = audioDeviceManager->getOutputSampleRate(m_txAudioDeviceIndex); + m_settings.m_txDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_sampleMOFifo.init(1, SampleSourceFifo::getSizePolicy(m_txSampleRate)); + + m_networkManager = new QNetworkAccessManager(); + QObject::connect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AudioCATSISO::networkManagerFinished + ); +} + +AudioCATSISO::~AudioCATSISO() +{ + QObject::disconnect( + m_networkManager, + &QNetworkAccessManager::finished, + this, + &AudioCATSISO::networkManagerFinished + ); + delete m_networkManager; + + if (m_rxRunning) { + stopRx(); + } + + if (m_txRunning) { + stopTx(); + } +} + +void AudioCATSISO::destroy() +{ + delete this; +} + +void AudioCATSISO::init() +{ + applySettings(m_settings, QList(), true); +} + +bool AudioCATSISO::startRx() +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_rxRunning) { + return true; + } + + qDebug() << "AudioCATSISO::startRx"; + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->addAudioSource(&m_inputFifo, getInputMessageQueue(), m_rxAudioDeviceIndex); + + m_inputWorkerThread = new QThread(); + m_inputWorker = new AudioCATInputWorker(&m_sampleMIFifo, &m_inputFifo); + m_inputWorker->moveToThread(m_inputWorkerThread); + + QObject::connect(m_inputWorkerThread, &QThread::started, m_inputWorker, &AudioCATInputWorker::startWork); + QObject::connect(m_inputWorkerThread, &QThread::finished, m_inputWorker, &QObject::deleteLater); + QObject::connect(m_inputWorkerThread, &QThread::finished, m_inputWorkerThread, &QThread::deleteLater); + + m_inputWorker->setLog2Decimation(m_settings.m_log2Decim); + m_inputWorker->setFcPos(m_settings.m_fcPosRx); + m_inputWorker->setIQMapping(m_settings.m_rxIQMapping); + m_inputWorker->startWork(); + m_inputWorkerThread->start(); + + qDebug("AudioCATSISO::startRx: started"); + m_rxRunning = true; + + return true; +} + +bool AudioCATSISO::startTx() +{ + QMutexLocker mutexLocker(&m_mutex); + + if (m_txRunning) { + return true; + } + + qDebug("AudioCATSISO::startTx"); + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->addAudioSink(&m_outputFifo, getInputMessageQueue(), m_txAudioDeviceIndex); + + m_outputWorkerThread = new QThread(); + m_outputWorker = new AudioCATOutputWorker(&m_sampleMOFifo, &m_outputFifo); + m_outputWorker->moveToThread(m_outputWorkerThread); + + QObject::connect(m_outputWorkerThread, &QThread::started, m_outputWorker, &AudioCATOutputWorker::startWork); + QObject::connect(m_outputWorkerThread, &QThread::finished, m_outputWorker, &QObject::deleteLater); + QObject::connect(m_outputWorkerThread, &QThread::finished, m_outputWorkerThread, &QThread::deleteLater); + + m_outputWorker->setSamplerate(m_txSampleRate); + m_outputWorker->setIQMapping(m_settings.m_txIQMapping); + m_outputWorker->connectTimer(m_deviceAPI->getMasterTimer()); + m_outputWorkerThread->start(); + m_txRunning = true; + + mutexLocker.unlock(); + + qDebug("AudioCATSISO::startTx: started"); + + return true; +} + +void AudioCATSISO::stopRx() +{ + QMutexLocker mutexLocker(&m_mutex); + + if (!m_rxRunning) { + return; + } + + qDebug("AudioCATSISO::stopRx"); + m_rxRunning = false; + + if (m_inputWorkerThread) + { + m_inputWorkerThread->quit(); + m_inputWorkerThread->wait(); + m_inputWorkerThread = nullptr; + m_inputWorker = nullptr; + } + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->removeAudioSource(&m_inputFifo); + + qDebug("AudioCATSISO::stopRx: stopped"); +} + +void AudioCATSISO::stopTx() +{ + if (!m_txRunning) { + return; + } + + qDebug("AudioCATSISO::stopTx"); + m_txRunning = false; + + if (m_outputWorkerThread) + { + m_outputWorker->stopWork(); + m_outputWorkerThread->quit(); + m_outputWorkerThread->wait(); + m_outputWorker = nullptr; + m_outputWorkerThread = nullptr; + } + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->removeAudioSink(&m_outputFifo); + + qDebug("AudioCATSISO::stopTx: stopped"); +} + +QByteArray AudioCATSISO::serialize() const +{ + return m_settings.serialize(); +} + +bool AudioCATSISO::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureAudioCATSISO* message = MsgConfigureAudioCATSISO::create(m_settings, QList(), true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureAudioCATSISO* messageToGUI = MsgConfigureAudioCATSISO::create(m_settings, QList(), true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& AudioCATSISO::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int AudioCATSISO::getSourceSampleRate(int) const +{ + return m_rxSampleRate / (1<{"rxCenterFrequency"}, false); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureAudioCATSISO* messageToGUI = MsgConfigureAudioCATSISO::create(settings, QList{"rxCenterFrequency"}, false); + m_guiMessageQueue->push(messageToGUI); + } +} + +int AudioCATSISO::getSinkSampleRate(int) const +{ + return m_txSampleRate; +} + +quint64 AudioCATSISO::getSinkCenterFrequency(int) const +{ + return m_settings.m_txCenterFrequency; +} + +void AudioCATSISO::setSinkCenterFrequency(qint64 centerFrequency, int) +{ + AudioCATSISOSettings settings = m_settings; + settings.m_txCenterFrequency = centerFrequency; + + MsgConfigureAudioCATSISO* message = MsgConfigureAudioCATSISO::create(settings, QList{"txCenterFrequency"}, false); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureAudioCATSISO* messageToGUI = MsgConfigureAudioCATSISO::create(settings, QList{"txCenterFrequency"}, false); + m_guiMessageQueue->push(messageToGUI); + } +} + +bool AudioCATSISO::handleMessage(const Message& message) +{ + if (MsgConfigureAudioCATSISO::match(message)) + { + qDebug() << "AudioCATSISO::handleMessage: MsgConfigureAudioCATSISO"; + MsgConfigureAudioCATSISO& conf = (MsgConfigureAudioCATSISO&) message; + applySettings(conf.getSettings(), conf.getSettingsKeys(), conf.getForce()); + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "AudioCATSISO::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initDeviceEngine(0)) { + m_deviceAPI->startDeviceEngine(0); + } + + if (m_settings.m_txEnable) + { + if (m_deviceAPI->initDeviceEngine(1)) { + m_deviceAPI->startDeviceEngine(1); + } + } + } + else + { + m_deviceAPI->stopDeviceEngine(0); + m_deviceAPI->stopDeviceEngine(1); + } + + if (m_settings.m_useReverseAPI) { + webapiReverseSendStartStop(cmd.getStartStop()); + } + + return true; + } + else + { + return false; + } +} + +void AudioCATSISO::applySettings(const AudioCATSISOSettings& settings, const QList& settingsKeys, bool force) +{ + bool forwardRxChange = false; + bool forwardTxChange = false; + + qDebug() << "AudioCATSISO::applySettings: " + << " force:" << force + << settings.getDebugString(settingsKeys, force); + + if (settingsKeys.contains("rxDeviceName") || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + m_rxAudioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_rxDeviceName); + m_rxSampleRate = audioDeviceManager->getInputSampleRate(m_rxAudioDeviceIndex); + forwardRxChange = true; + + if (m_rxRunning) + { + audioDeviceManager->removeAudioSource(&m_inputFifo); + audioDeviceManager->addAudioSource(&m_inputFifo, getInputMessageQueue(), m_rxAudioDeviceIndex); + } + } + + if (settingsKeys.contains("txDeviceName") || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + m_txAudioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_txDeviceName); + m_txSampleRate = audioDeviceManager->getOutputSampleRate(m_txAudioDeviceIndex); + forwardTxChange = true; + + if (m_txRunning) + { + audioDeviceManager->removeAudioSink(&m_outputFifo); + audioDeviceManager->addAudioSink(&m_outputFifo, getInputMessageQueue(), m_txAudioDeviceIndex); + } + } + + if (settingsKeys.contains("rxVolume") || force) + { + m_audioInput.setVolume(settings.m_rxVolume); + qDebug() << "AudioCATSISO::applySettings: set Rx volume to " << settings.m_rxVolume; + } + + if (settingsKeys.contains("txVolume") || force) + { + m_audioOutput.setVolume(settings.m_txVolume); + qDebug() << "AudioCATSISO::applySettings: set Tx volume to " << settings.m_txVolume; + } + + if (settingsKeys.contains("log2Decim") || force) + { + forwardRxChange = true; + + if (m_rxRunning) + { + m_inputWorker->setLog2Decimation(settings.m_log2Decim); + qDebug() << "AudioCATSISO::applySettings: set decimation to " << (1<setFcPos((int) settings.m_fcPosRx); + } + + qDebug() << "AudioCATSISO::applySettings: set Rx fc pos (enum) to " << (int) settings.m_fcPosRx; + } + + if (settingsKeys.contains("rxIQMapping") || force) + { + if (m_rxRunning) { + m_inputWorker->setIQMapping(settings.m_rxIQMapping); + } + } + + if (settingsKeys.contains("txIQMapping") || force) + { + if (m_txRunning) { + m_outputWorker->setIQMapping(settings.m_txIQMapping); + } + } + + if (settingsKeys.contains("dcBlock") || settingsKeys.contains("iqCorrection") || force) + { + m_deviceAPI->configureCorrections(settings.m_dcBlock, settings.m_iqCorrection); + qDebug("AudioInput::applySettings: corrections: DC block: %s IQ imbalance: %s", + settings.m_dcBlock ? "true" : "false", + settings.m_iqCorrection ? "true" : "false"); + } + + if (settingsKeys.contains("rxCenterFrequency") || force) + { + // TBD with CAT + forwardRxChange = true; + } + + if (settingsKeys.contains("txCenterFrequency") || force) + { + // TBD with CAT + forwardTxChange = true; + } + + if (settingsKeys.contains("useReverseAPI")) + { + bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) || + settingsKeys.contains("reverseAPIAddress") || + settingsKeys.contains("reverseAPIPort") || + settingsKeys.contains("reverseAPIDeviceIndex"); + webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force); + } + + if (force) { + m_settings = settings; + } else { + m_settings.applySettings(settingsKeys, settings); + } + + if (forwardRxChange) + { + int sampleRate = m_rxSampleRate / (1<getDeviceEngineInputMessageQueue()->push(notif); + } + + if (forwardTxChange) + { + if (m_txRunning) { + m_outputWorker->setSamplerate(m_txSampleRate); + } + + DSPMIMOSignalNotification *notif = new DSPMIMOSignalNotification(m_txSampleRate, settings.m_txCenterFrequency, false, 0); + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } +} + +int AudioCATSISO::webapiRunGet( + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage +) +{ + (void) subsystemIndex; + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int AudioCATSISO::webapiRun( + bool run, + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage +) +{ + (void) subsystemIndex; + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} + +int AudioCATSISO::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage +) +{ + (void) errorMessage; + response.setAudioInputSettings(new SWGSDRangel::SWGAudioInputSettings()); + response.getAudioInputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + + return 200; +} + +int AudioCATSISO::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage +) +{ + (void) errorMessage; + AudioCATSISOSettings settings = m_settings; + webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response); + + MsgConfigureAudioCATSISO *msg = MsgConfigureAudioCATSISO::create(settings, deviceSettingsKeys, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAudioCATSISO *msgToGUI = MsgConfigureAudioCATSISO::create(settings, deviceSettingsKeys, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void AudioCATSISO::webapiUpdateDeviceSettings( + AudioCATSISOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response) +{ + if (deviceSettingsKeys.contains("rxCenterFrequency")) { + settings.m_rxCenterFrequency = response.getAudioCatsisoSettings()->getRxCenterFrequency(); + } + if (deviceSettingsKeys.contains("txCenterFrequency")) { + settings.m_txCenterFrequency = response.getAudioCatsisoSettings()->getTxCenterFrequency(); + } + if (deviceSettingsKeys.contains("transverterMode")) { + settings.m_transverterMode = response.getAudioCatsisoSettings()->getTransverterMode() != 0; + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + settings.m_transverterDeltaFrequency = response.getAudioCatsisoSettings()->getTransverterDeltaFrequency(); + } + if (deviceSettingsKeys.contains("iqOrder")) { + settings.m_iqOrder = response.getAudioCatsisoSettings()->getIqOrder() != 0; + } + if (deviceSettingsKeys.contains("spectrumStreamIndex")) { + settings.m_spectrumStreamIndex = response.getAudioCatsisoSettings()->getSpectrumStreamIndex(); + } + if (deviceSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getAudioCatsisoSettings()->getStreamIndex(); + } + if (deviceSettingsKeys.contains("rxDeviceName")) { + settings.m_rxDeviceName = *response.getAudioCatsisoSettings()->getRxDeviceName(); + } + if (deviceSettingsKeys.contains("rxIQMapping")) { + settings.m_rxIQMapping = (AudioCATSISOSettings::IQMapping)response.getAudioCatsisoSettings()->getRxIqMapping(); + } + if (deviceSettingsKeys.contains("log2Decim")) { + settings.m_log2Decim = response.getAudioCatsisoSettings()->getLog2Decim(); + } + if (deviceSettingsKeys.contains("fcPosRx")) { + settings.m_fcPosRx = (AudioCATSISOSettings::fcPos_t) response.getAudioCatsisoSettings()->getFcPosRx(); + } + if (deviceSettingsKeys.contains("dcBlock")) { + settings.m_dcBlock = response.getAudioCatsisoSettings()->getDcBlock() != 0; + } + if (deviceSettingsKeys.contains("iqCorrection")) { + settings.m_iqCorrection = response.getAudioCatsisoSettings()->getIqCorrection() != 0; + } + if (deviceSettingsKeys.contains("rxVolume")) { + settings.m_rxVolume = response.getAudioCatsisoSettings()->getRxVolume(); + } + + if (deviceSettingsKeys.contains("txDeviceName")) { + settings.m_txDeviceName = *response.getAudioCatsisoSettings()->getTxDeviceName(); + } + if (deviceSettingsKeys.contains("txIQMapping")) { + settings.m_txIQMapping = (AudioCATSISOSettings::IQMapping)response.getAudioCatsisoSettings()->getTxIqMapping(); + } + if (deviceSettingsKeys.contains("log2Interp")) { + settings.m_log2Interp = response.getAudioCatsisoSettings()->getLog2Interp(); + } + if (deviceSettingsKeys.contains("fcPosTx")) { + settings.m_fcPosTx = (AudioCATSISOSettings::fcPos_t) response.getAudioCatsisoSettings()->getFcPosTx(); + } + if (deviceSettingsKeys.contains("txVolume")) { + settings.m_txVolume = response.getAudioCatsisoSettings()->getTxVolume(); + } + + if (deviceSettingsKeys.contains("streamIndex")) { + settings.m_streamIndex = response.getAudioCatsisoSettings()->getStreamIndex(); + } + if (deviceSettingsKeys.contains("spectrumStreamIndex")) { + settings.m_spectrumStreamIndex = response.getAudioCatsisoSettings()->getSpectrumStreamIndex(); + } + if (deviceSettingsKeys.contains("txEnable")) { + settings.m_txEnable = response.getAudioCatsisoSettings()->getTxEnable() != 0; + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getAudioCatsisoSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getAudioCatsisoSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getAudioCatsisoSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getAudioCatsisoSettings()->getReverseApiDeviceIndex(); + } +} + +void AudioCATSISO::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AudioCATSISOSettings& settings) +{ + response.getAudioCatsisoSettings()->setRxCenterFrequency(settings.m_rxCenterFrequency); + response.getAudioCatsisoSettings()->setTxCenterFrequency(settings.m_txCenterFrequency); + response.getAudioCatsisoSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getAudioCatsisoSettings()->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + response.getAudioCatsisoSettings()->setTransverterMode(settings.m_transverterMode ? 1 : 0); + response.getAudioCatsisoSettings()->setSpectrumStreamIndex(settings.m_spectrumStreamIndex); + response.getAudioCatsisoSettings()->setStreamIndex(settings.m_streamIndex); + response.getAudioCatsisoSettings()->setRxDeviceName(new QString(settings.m_rxDeviceName)); + response.getAudioCatsisoSettings()->setRxIqMapping((int)settings.m_rxIQMapping); + response.getAudioCatsisoSettings()->setLog2Decim(settings.m_log2Decim); + response.getAudioCatsisoSettings()->setFcPosRx((int) settings.m_fcPosRx); + response.getAudioCatsisoSettings()->setDcBlock(settings.m_dcBlock ? 1 : 0); + response.getAudioCatsisoSettings()->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + response.getAudioCatsisoSettings()->setRxVolume(settings.m_rxVolume); + + response.getAudioCatsisoSettings()->setTxDeviceName(new QString(settings.m_txDeviceName)); + response.getAudioCatsisoSettings()->setTxIqMapping((int)settings.m_txIQMapping); + response.getAudioCatsisoSettings()->setLog2Interp(settings.m_log2Interp); + response.getAudioCatsisoSettings()->setFcPosTx((int) settings.m_fcPosTx); + response.getAudioCatsisoSettings()->setTxVolume(settings.m_txVolume); + + response.getAudioCatsisoSettings()->setStreamIndex(settings.m_streamIndex); + response.getAudioCatsisoSettings()->setSpectrumStreamIndex(settings.m_spectrumStreamIndex); + response.getAudioCatsisoSettings()->setTxEnable(settings.m_txEnable ? 1 : 0); + response.getAudioCatsisoSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getAudioCatsisoSettings()->getReverseApiAddress()) { + *response.getAudioCatsisoSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getAudioCatsisoSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getAudioCatsisoSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getAudioCatsisoSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +void AudioCATSISO::webapiReverseSendSettings(const QList& deviceSettingsKeys, const AudioCATSISOSettings& settings, bool force) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(0); // single Rx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("AudioCATSISO")); + swgDeviceSettings->setAudioCatsisoSettings(new SWGSDRangel::SWGAudioCATSISOSettings()); + SWGSDRangel::SWGAudioCATSISOSettings *swgAudioCATSISOSettings = swgDeviceSettings->getAudioCatsisoSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (deviceSettingsKeys.contains("rxCenterFrequency")) { + swgAudioCATSISOSettings->setRxCenterFrequency(settings.m_rxCenterFrequency); + } + if (deviceSettingsKeys.contains("txCenterFrequency")) { + swgAudioCATSISOSettings->setTxCenterFrequency(settings.m_txCenterFrequency); + } + if (deviceSettingsKeys.contains("transverterMode")) { + swgAudioCATSISOSettings->setTransverterMode(settings.m_transverterMode ? 1 : 0); + } + if (deviceSettingsKeys.contains("transverterDeltaFrequency")) { + swgAudioCATSISOSettings->setTransverterDeltaFrequency(settings.m_transverterDeltaFrequency); + } + if (deviceSettingsKeys.contains("iqOrder")) { + swgAudioCATSISOSettings->setIqOrder(settings.m_iqOrder ? 1 : 0); + } + if (deviceSettingsKeys.contains("spectrumStreamIndex")) { + swgAudioCATSISOSettings->setSpectrumStreamIndex(settings.m_spectrumStreamIndex); + } + if (deviceSettingsKeys.contains("streamIndex")) { + swgAudioCATSISOSettings->setStreamIndex(settings.m_streamIndex); + } + if (deviceSettingsKeys.contains("rxDeviceName") || force) { + swgAudioCATSISOSettings->setRxDeviceName(new QString(settings.m_rxDeviceName)); + } + if (deviceSettingsKeys.contains("rxIQMapping")) { + swgAudioCATSISOSettings->setRxIqMapping((int) settings.m_rxIQMapping); + } + if (deviceSettingsKeys.contains("log2Decim")) { + swgAudioCATSISOSettings->setLog2Decim(settings.m_log2Decim); + } + if (deviceSettingsKeys.contains("fcPosRx")) { + swgAudioCATSISOSettings->setFcPosRx((int) settings.m_fcPosRx); + } + if (deviceSettingsKeys.contains("dcBlock")) { + swgAudioCATSISOSettings->setDcBlock(settings.m_dcBlock ? 1 : 0); + } + if (deviceSettingsKeys.contains("iqCorrection")) { + swgAudioCATSISOSettings->setIqCorrection(settings.m_iqCorrection ? 1 : 0); + } + if (deviceSettingsKeys.contains("rxVolume")) { + swgAudioCATSISOSettings->setRxVolume(settings.m_rxVolume); + } + + if (deviceSettingsKeys.contains("txDeviceName")) { + swgAudioCATSISOSettings->setTxDeviceName(new QString(settings.m_txDeviceName)); + } + if (deviceSettingsKeys.contains("txIQMapping")) { + swgAudioCATSISOSettings->setTxIqMapping((int) settings.m_txIQMapping); + } + if (deviceSettingsKeys.contains("log2Interp")) { + swgAudioCATSISOSettings->setLog2Interp(settings.m_log2Interp); + } + if (deviceSettingsKeys.contains("fcPosTx")) { + swgAudioCATSISOSettings->setFcPosTx((int) settings.m_fcPosTx); + } + if (deviceSettingsKeys.contains("txVolume")) { + swgAudioCATSISOSettings->setTxVolume(settings.m_txVolume); + } + + if (deviceSettingsKeys.contains("streamIndex")) { + swgAudioCATSISOSettings->setStreamIndex(settings.m_streamIndex); + } + if (deviceSettingsKeys.contains("spectrumStreamIndex")) { + swgAudioCATSISOSettings->setSpectrumStreamIndex(settings.m_spectrumStreamIndex); + } + if (deviceSettingsKeys.contains("txEnable")) { + swgAudioCATSISOSettings->setTxEnable(settings.m_txEnable ? 1 : 0); + } + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgDeviceSettings; +} + +void AudioCATSISO::webapiReverseSendStartStop(bool start) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(0); // single Rx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("AudioCATSISO")); + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + QNetworkReply *reply; + + if (start) { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer); + } else { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer); + } + + buffer->setParent(reply); + delete swgDeviceSettings; +} + +void AudioCATSISO::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "AudioCATSISO::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("AudioCATSISO::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatsiso.h b/plugins/samplemimo/audiocatsiso/audiocatsiso.h new file mode 100644 index 000000000..9a3ff5ac0 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsiso.h @@ -0,0 +1,187 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 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 _AUDIOCATSISO_AUDIOCATSISO_H_ +#define _AUDIOCATSISO_AUDIOCATSISO_H_ + +#include +#include +#include +#include +#include + +#include "dsp/devicesamplemimo.h" +#include "audio/audioinputdevice.h" +#include "audio/audiooutputdevice.h" +#include "audio/audiofifo.h" +#include "audiocatsisosettings.h" + +class DeviceAPI; +class QNetworkAccessManager; +class QNetworkReply; +class QThread; +class AudioCATInputWorker; +class AudioCATOutputWorker; + +class AudioCATSISO : public DeviceSampleMIMO { + Q_OBJECT +public: + class MsgConfigureAudioCATSISO : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AudioCATSISOSettings& getSettings() const { return m_settings; } + const QList& getSettingsKeys() const { return m_settingsKeys; } + bool getForce() const { return m_force; } + + static MsgConfigureAudioCATSISO* create(const AudioCATSISOSettings& settings, const QList& settingsKeys, bool force) { + return new MsgConfigureAudioCATSISO(settings, settingsKeys, force); + } + + private: + AudioCATSISOSettings m_settings; + QList m_settingsKeys; + bool m_force; + + MsgConfigureAudioCATSISO(const AudioCATSISOSettings& settings, const QList& settingsKeys, bool force) : + Message(), + m_settings(settings), + m_settingsKeys(settingsKeys), + m_force(force) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + AudioCATSISO(DeviceAPI *deviceAPI); + virtual ~AudioCATSISO(); + virtual void destroy(); + + virtual void init(); + virtual bool startRx(); + virtual void stopRx(); + virtual bool startTx(); + virtual void stopTx(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + + const QString& getInputDeviceName() { return m_settings.m_rxDeviceName; } + virtual int getSourceSampleRate(int index) const; + virtual void setSourceSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; } + virtual quint64 getSourceCenterFrequency(int index) const; + virtual void setSourceCenterFrequency(qint64 centerFrequency, int index); + + const QString& getOutputDeviceName() { return m_settings.m_txDeviceName; } + virtual int getSinkSampleRate(int index) const; + virtual void setSinkSampleRate(int sampleRate, int index) { (void) sampleRate; (void) index; } + virtual quint64 getSinkCenterFrequency(int index) const; + virtual void setSinkCenterFrequency(qint64 centerFrequency, int index); + + virtual quint64 getMIMOCenterFrequency() const { return getSourceCenterFrequency(0); } + virtual unsigned int getMIMOSampleRate() const { return getSourceSampleRate(0); } + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiRunGet( + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + int subsystemIndex, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + static void webapiFormatDeviceSettings( + SWGSDRangel::SWGDeviceSettings& response, + const AudioCATSISOSettings& settings); + + static void webapiUpdateDeviceSettings( + AudioCATSISOSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response); + +private: + struct DeviceSettingsKeys + { + QList m_commonSettingsKeys; + QList> m_streamsSettingsKeys; + }; + + DeviceAPI *m_deviceAPI; + AudioInputDevice m_audioInput; + AudioFifo m_inputFifo; + AudioOutputDevice m_audioOutput; + AudioFifo m_outputFifo; + QMutex m_mutex; + AudioCATSISOSettings m_settings; + AudioCATInputWorker* m_inputWorker; + AudioCATOutputWorker* m_outputWorker; + QThread *m_inputWorkerThread; + QThread *m_outputWorkerThread; + QString m_deviceDescription; + bool m_rxRunning; + int m_rxAudioDeviceIndex; + int m_rxSampleRate; + bool m_txRunning; + int m_txAudioDeviceIndex; + int m_txSampleRate; + const QTimer& m_masterTimer; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const AudioCATSISOSettings& settings, const QList& settingsKeys, bool force); + void webapiReverseSendSettings(const QList& deviceSettingsKeys, const AudioCATSISOSettings& settings, bool force); + void webapiReverseSendStartStop(bool start); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // _AUDIOCATSISO_AUDIOCATSISO_H_ diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisogui.cpp b/plugins/samplemimo/audiocatsiso/audiocatsisogui.cpp new file mode 100644 index 000000000..99c76389c --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisogui.cpp @@ -0,0 +1,622 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include + +#include "plugin/pluginapi.h" +#include "device/deviceapi.h" +#include "device/deviceuiset.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "gui/dialpopup.h" +#include "gui/dialogpositioner.h" +#include "gui/basicdevicesettingsdialog.h" +#include "gui/audioselectdialog.h" +#include "dsp/dspengine.h" +#include "dsp/dspdevicemimoengine.h" +#include "dsp/dspcommands.h" +#include "dsp/devicesamplesource.h" +#include "util/db.h" + +#include "mainwindow.h" + +#include "ui_audiocatsisogui.h" +#include "audiocatsisogui.h" + +AudioCATSISOGUI::AudioCATSISOGUI(DeviceUISet *deviceUISet, QWidget* parent) : + DeviceGUI(parent), + ui(new Ui::AudioCATSISOGUI), + m_settings(), + m_doApplySettings(true), + m_forceSettings(true), + m_sampleMIMO(nullptr), + m_tickCount(0), + m_lastEngineState(DeviceAPI::StNotStarted) +{ + qDebug("AudioCATSISOGUI::AudioCATSISOGUI"); + m_deviceUISet = deviceUISet; + setAttribute(Qt::WA_DeleteOnClose, true); + m_sampleMIMO = (AudioCATSISO*) m_deviceUISet->m_deviceAPI->getSampleMIMO(); + + m_rxSampleRate = m_sampleMIMO->getSourceSampleRate(0); + m_settings.m_rxCenterFrequency = m_sampleMIMO->getSourceCenterFrequency(0); + m_settings.m_rxDeviceName = m_sampleMIMO->getInputDeviceName(); + m_txSampleRate = m_sampleMIMO->getSinkSampleRate(0); + m_settings.m_txCenterFrequency = m_sampleMIMO->getSinkCenterFrequency(0); + m_settings.m_txDeviceName = m_sampleMIMO->getOutputDeviceName(); + + ui->setupUi(getContents()); + sizeToContents(); + getContents()->setStyleSheet("#AudioCATSISOGUI { background-color: rgb(64, 64, 64); }"); + m_helpURL = "plugins/samplemimo/metismiso/readme.md"; + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->centerFrequency->setValueRange(9, 0, m_absMaxFreq); + + displaySettings(); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); + m_statusTimer.start(500); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_sampleMIMO->setMessageQueueToGUI(&m_inputMessageQueue); + + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &))); + + makeUIConnections(); + DialPopup::addPopupsToChildDials(this); +} + +AudioCATSISOGUI::~AudioCATSISOGUI() +{ + delete ui; +} + +void AudioCATSISOGUI::destroy() +{ + delete this; +} + +void AudioCATSISOGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + m_forceSettings = true; + sendSettings(); +} + +void AudioCATSISOGUI::setCenterFrequency(qint64 centerFrequency) +{ + if (m_settings.m_streamIndex == 0) + { + m_settings.m_rxCenterFrequency = centerFrequency; + m_settingsKeys.append("rxCenterFrequency"); + } + else if (m_settings.m_streamIndex == 1) + { + m_settings.m_txCenterFrequency = centerFrequency; + m_settingsKeys.append("txCenterFrequency"); + } + + displaySettings(); + sendSettings(); +} + +QByteArray AudioCATSISOGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool AudioCATSISOGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +void AudioCATSISOGUI::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + AudioCATSISO::MsgStartStop *message = AudioCATSISO::MsgStartStop::create(checked); + m_sampleMIMO->getInputMessageQueue()->push(message); + } +} + +void AudioCATSISOGUI::on_streamIndex_currentIndexChanged(int index) +{ + if (ui->streamLock->isChecked()) + { + m_settings.m_spectrumStreamIndex = index; + m_settingsKeys.append("spectrumStreamIndex"); + + if (m_settings.m_spectrumStreamIndex == 0) + { + m_deviceUISet->m_spectrum->setDisplayedStream(true, index); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF); + } + else + { + m_deviceUISet->m_spectrum->setDisplayedStream(false, 0); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF); + } + + updateSpectrum(); + + ui->spectrumSource->blockSignals(true); + ui->spectrumSource->setCurrentIndex(index); + ui->spectrumSource->blockSignals(false); + } + + m_settings.m_streamIndex = index; + m_settingsKeys.append("streamIndex"); + sendSettings(); + + displayFrequency(); + displaySampleRate(); +} + +void AudioCATSISOGUI::on_spectrumSource_currentIndexChanged(int index) +{ + m_settings.m_spectrumStreamIndex = index; + m_settingsKeys.append("spectrumStreamIndex"); + + if (m_settings.m_spectrumStreamIndex == 0) + { + m_deviceUISet->m_spectrum->setDisplayedStream(true, index); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(true, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_RX_SCALEF); + } + else + { + m_deviceUISet->m_spectrum->setDisplayedStream(false, 0); + m_deviceUISet->m_deviceAPI->setSpectrumSinkInput(false, 0); + m_deviceUISet->setSpectrumScalingFactor(SDR_TX_SCALEF); + } + + updateSpectrum(); + + if (ui->streamLock->isChecked()) + { + ui->streamIndex->blockSignals(true); + ui->streamIndex->setCurrentIndex(index); + ui->streamIndex->blockSignals(false); + m_settings.m_streamIndex = index; + m_settingsKeys.append("streamIndex"); + displayFrequency(); + displaySampleRate(); + } + + sendSettings(); +} + +void AudioCATSISOGUI::on_streamLock_toggled(bool checked) +{ + if (checked && (ui->streamIndex->currentIndex() != ui->spectrumSource->currentIndex())) { + ui->spectrumSource->setCurrentIndex(ui->streamIndex->currentIndex()); + } +} + +void AudioCATSISOGUI::on_centerFrequency_changed(quint64 value) +{ + if (m_settings.m_streamIndex == 0) + { + m_settings.m_rxCenterFrequency = value * 1000; + m_settingsKeys.append("rxCenterFrequency"); + } + else if (m_settings.m_streamIndex == 1) + { + m_settings.m_txCenterFrequency = value * 1000; + m_settingsKeys.append("txCenterFrequency"); + } + + sendSettings(); +} + +void AudioCATSISOGUI::on_log2Decim_currentIndexChanged(int index) +{ + m_settings.m_log2Decim = index < 0 ? 0 : index > 3 ? 3 : index; + // displaySampleRate(); + displayFcRxTooltip(); + m_settingsKeys.append("log2Decim"); + sendSettings(); +} + +void AudioCATSISOGUI::on_dcBlock_toggled(bool checked) +{ + m_settings.m_dcBlock = checked; + m_settingsKeys.append("dcBlock"); + sendSettings(); +} + +void AudioCATSISOGUI::on_iqCorrection_toggled(bool checked) +{ + m_settings.m_iqCorrection = checked; + m_settingsKeys.append("iqCorrection"); + sendSettings(); +} + +void AudioCATSISOGUI::on_txEnable_toggled(bool checked) +{ + m_settings.m_txEnable = checked; + m_settingsKeys.append("txEnable"); + sendSettings(); +} + +void AudioCATSISOGUI::on_transverter_clicked() +{ + m_settings.m_transverterMode = ui->transverter->getDeltaFrequencyAcive(); + m_settings.m_transverterDeltaFrequency = ui->transverter->getDeltaFrequency(); + m_settings.m_iqOrder = ui->transverter->getIQOrder(); + m_settingsKeys.append("transverterMode"); + m_settingsKeys.append("transverterDeltaFrequency"); + m_settingsKeys.append("iqOrder"); + qDebug("AudioCATSISOGUI::on_transverter_clicked: Rx: %lld Hz %s", + m_settings.m_transverterDeltaFrequency, m_settings.m_transverterMode ? "on" : "off"); + displayFrequency(); + setCenterFrequency(ui->centerFrequency->getValueNew() * 1000); + sendSettings(); +} + +void AudioCATSISOGUI::on_rxDeviceSelect_clicked() +{ + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_txDeviceName, true, this); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_rxDeviceName = audioSelect.m_audioDeviceName; + m_settingsKeys.append("rxDeviceName"); + ui->rxDeviceLabel->setText(m_settings.m_rxDeviceName); + sendSettings(); + } +} + +void AudioCATSISOGUI::on_txDeviceSelect_clicked() +{ + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_txDeviceName, false, this); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_txDeviceName = audioSelect.m_audioDeviceName; + m_settingsKeys.append("txDeviceName"); + ui->txDeviceLabel->setText(m_settings.m_txDeviceName); + sendSettings(); + } +} + +void AudioCATSISOGUI::on_rxChannels_currentIndexChanged(int index) +{ + m_settings.m_rxIQMapping = (AudioCATSISOSettings::IQMapping)index; + // updateSampleRateAndFrequency(); + m_settingsKeys.append("rxIQMapping"); + sendSettings(); +} + +void AudioCATSISOGUI::on_rxVolume_valueChanged(int value) +{ + m_settings.m_rxVolume = value/10.0f; + ui->rxVolumeText->setText(QString("%1").arg(m_settings.m_rxVolume, 3, 'f', 1)); + m_settingsKeys.append("rxVolume"); + sendSettings(); +} + +void AudioCATSISOGUI::on_txChannels_currentIndexChanged(int index) +{ + m_settings.m_txIQMapping = (AudioCATSISOSettings::IQMapping) index; + m_settingsKeys.append("txIQMapping"); + // updateSampleRateAndFrequency(); + sendSettings(); +} + +void AudioCATSISOGUI::on_txVolume_valueChanged(int value) +{ + m_settings.m_txVolume = value/10.0f; + ui->txVolumeText->setText(QString("%1").arg(m_settings.m_txVolume, 3, 'f', 1)); + m_settingsKeys.append("txVolume"); + sendSettings(); +} + +void AudioCATSISOGUI::on_fcPosRx_currentIndexChanged(int index) +{ + m_settings.m_fcPosRx = (AudioCATSISOSettings::fcPos_t) (index < 0 ? 0 : index > 2 ? 2 : index); + displayFcRxTooltip(); + m_settingsKeys.append("fcPosRx"); + sendSettings(); + +} + +void AudioCATSISOGUI::displaySettings() +{ + blockApplySettings(true); + + ui->rxDeviceLabel->setText(m_settings.m_rxDeviceName); + ui->txDeviceLabel->setText(m_settings.m_txDeviceName); + ui->streamIndex->setCurrentIndex(m_settings.m_streamIndex); + ui->spectrumSource->setCurrentIndex(m_settings.m_spectrumStreamIndex); + ui->log2Decim->setCurrentIndex(m_settings.m_log2Decim); + ui->dcBlock->setChecked(m_settings.m_dcBlock); + ui->iqCorrection->setChecked(m_settings.m_iqCorrection); + ui->txEnable->setChecked(m_settings.m_txEnable); + ui->rxVolume->setValue((int)(m_settings.m_rxVolume*10.0f)); + ui->rxVolumeText->setText(QString("%1").arg(m_settings.m_rxVolume, 3, 'f', 1)); + ui->rxChannels->setCurrentIndex((int)m_settings.m_rxIQMapping); + ui->txVolume->setValue((int)(m_settings.m_txVolume*10.0f)); + ui->txVolumeText->setText(QString("%1").arg(m_settings.m_txVolume, 3, 'f', 1)); + ui->txChannels->setCurrentIndex((int)m_settings.m_txIQMapping); + ui->fcPosRx->setCurrentIndex(m_settings.m_fcPosRx); + displayFrequency(); + displaySampleRate(); + updateSpectrum(); + displayFcRxTooltip(); + + blockApplySettings(false); +} + +void AudioCATSISOGUI::displayFcRxTooltip() +{ + int32_t fShift = DeviceSampleSource::calculateFrequencyShift( + m_settings.m_log2Decim, + (DeviceSampleSource::fcPos_t) m_settings.m_fcPosRx, + m_rxSampleRate, + DeviceSampleSource::FrequencyShiftScheme::FSHIFT_STD + ); + ui->fcPosRx->setToolTip(tr("Relative position of device center frequency: %1 kHz").arg(QString::number(fShift / 1000.0f, 'g', 5))); +} + +void AudioCATSISOGUI::sendSettings() +{ + if (!m_updateTimer.isActive()) { + m_updateTimer.start(100); + } +} + +void AudioCATSISOGUI::updateHardware() +{ + if (m_doApplySettings) + { + AudioCATSISO::MsgConfigureAudioCATSISO* message = AudioCATSISO::MsgConfigureAudioCATSISO::create(m_settings, m_settingsKeys, m_forceSettings); + m_sampleMIMO->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_settingsKeys.clear(); + m_updateTimer.stop(); + } +} + +void AudioCATSISOGUI::updateStatus() +{ + int state = m_deviceUISet->m_deviceAPI->state(); + + if (m_lastEngineState != state) + { + switch(state) + { + case DeviceAPI::StNotStarted: + ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + break; + case DeviceAPI::StIdle: + ui->startStop->setStyleSheet("QToolButton { background-color : blue; }"); + break; + case DeviceAPI::StRunning: + ui->startStop->setStyleSheet("QToolButton { background-color : green; }"); + break; + case DeviceAPI::StError: + ui->startStop->setStyleSheet("QToolButton { background-color : red; }"); + QMessageBox::information(this, tr("Message"), m_deviceUISet->m_deviceAPI->errorMessage()); + break; + default: + break; + } + + m_lastEngineState = state; + } +} + +bool AudioCATSISOGUI::handleMessage(const Message& message) +{ + if (AudioCATSISO::MsgConfigureAudioCATSISO::match(message)) + { + qDebug("AudioCATSISOGUI::handleMessage: MsgConfigureAudioCATSISO"); + const AudioCATSISO::MsgConfigureAudioCATSISO& cfg = (AudioCATSISO::MsgConfigureAudioCATSISO&) message; + + if (cfg.getForce()) { + m_settings = cfg.getSettings(); + } else { + m_settings.applySettings(cfg.getSettingsKeys(), cfg.getSettings()); + } + + if ((m_settings.m_spectrumStreamIndex != m_settings.m_streamIndex) && (ui->streamLock->isChecked())) + { + m_settings.m_spectrumStreamIndex = m_settings.m_streamIndex; + m_settingsKeys.append("spectrumStreamIndex"); + sendSettings(); + } + + displaySettings(); + return true; + } + else if (AudioCATSISO::MsgStartStop::match(message)) + { + qDebug("AudioCATSISOGUI::handleMessage: MsgStartStop"); + AudioCATSISO::MsgStartStop& notif = (AudioCATSISO::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + + return true; + } + else + { + return false; + } +} + +void AudioCATSISOGUI::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + if (DSPMIMOSignalNotification::match(*message)) + { + DSPMIMOSignalNotification* notif = (DSPMIMOSignalNotification*) message; + int istream = notif->getIndex(); + bool sourceOrSink = notif->getSourceOrSink(); + qint64 frequency = notif->getCenterFrequency(); + + if (sourceOrSink) + { + m_rxSampleRate = notif->getSampleRate(); + m_settings.m_rxCenterFrequency = frequency; + } + else + { + m_txSampleRate = notif->getSampleRate(); + m_settings.m_txCenterFrequency = frequency; + } + + qDebug() << "AudioCATSISOGUI::handleInputMessages: DSPMIMOSignalNotification: " + << "sourceOrSink:" << sourceOrSink + << "istream:" << istream + << "m_rxSampleRate:" << m_rxSampleRate + << "m_txSampleRate:" << m_txSampleRate + << "frequency:" << frequency; + + displayFrequency(); + displaySampleRate(); + updateSpectrum(); + + delete message; + } + else + { + if (handleMessage(*message)) + { + delete message; + } + } + } +} + +void AudioCATSISOGUI::displayFrequency() +{ + qint64 centerFrequency; + + if (m_settings.m_streamIndex == 0) { + centerFrequency = m_settings.m_rxCenterFrequency; + } else if (m_settings.m_streamIndex == 1) { + centerFrequency = m_settings.m_txCenterFrequency; + } + + ui->centerFrequency->setValueRange(9, 0, 9999999999); + ui->centerFrequency->setValue(centerFrequency / 1000); +} + +void AudioCATSISOGUI::displaySampleRate() +{ + if (m_settings.m_streamIndex == 0) { + ui->deviceRateText->setText(tr("%1k").arg((float) m_rxSampleRate / 1000)); + } else { + ui->deviceRateText->setText(tr("%1k").arg((float) m_txSampleRate / 1000)); + } +} + +void AudioCATSISOGUI::updateSpectrum() +{ + qint64 centerFrequency; + + if (m_settings.m_spectrumStreamIndex == 0) { + centerFrequency = m_settings.m_rxCenterFrequency; + } else if (m_settings.m_spectrumStreamIndex == 1) { + centerFrequency = m_settings.m_txCenterFrequency; + } else { + centerFrequency = 0; + } + + m_deviceUISet->getSpectrum()->setCenterFrequency(centerFrequency); + + if (m_settings.m_spectrumStreamIndex == 0) { + m_deviceUISet->getSpectrum()->setSampleRate(m_rxSampleRate); + } else { + m_deviceUISet->getSpectrum()->setSampleRate(m_txSampleRate); + } +} + +void AudioCATSISOGUI::openDeviceSettingsDialog(const QPoint& p) +{ + if (m_contextMenuType == ContextMenuDeviceSettings) + { + BasicDeviceSettingsDialog dialog(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.move(p); + new DialogPositioner(&dialog, false); + dialog.exec(); + + 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_settingsKeys.append("useReverseAPI"); + m_settingsKeys.append("reverseAPIAddress"); + m_settingsKeys.append("reverseAPIPort"); + m_settingsKeys.append("reverseAPIDeviceIndex"); + + sendSettings(); + } + + resetContextMenuType(); +} + +void AudioCATSISOGUI::makeUIConnections() +{ + QObject::connect(ui->streamIndex, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_streamIndex_currentIndexChanged); + QObject::connect(ui->spectrumSource, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_spectrumSource_currentIndexChanged); + QObject::connect(ui->streamLock, &QToolButton::toggled, this, &AudioCATSISOGUI::on_streamLock_toggled); + QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &AudioCATSISOGUI::on_startStop_toggled); + QObject::connect(ui->centerFrequency, &ValueDial::changed, this, &AudioCATSISOGUI::on_centerFrequency_changed); + QObject::connect(ui->log2Decim, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_log2Decim_currentIndexChanged); + QObject::connect(ui->dcBlock, &ButtonSwitch::toggled, this, &AudioCATSISOGUI::on_dcBlock_toggled); + QObject::connect(ui->iqCorrection, &ButtonSwitch::toggled, this, &AudioCATSISOGUI::on_iqCorrection_toggled); + QObject::connect(ui->txEnable, &ButtonSwitch::toggled, this, &AudioCATSISOGUI::on_txEnable_toggled); + QObject::connect(ui->transverter, &TransverterButton::clicked, this, &AudioCATSISOGUI::on_transverter_clicked); + QObject::connect(ui->rxDeviceSelect, &QPushButton::clicked, this, &AudioCATSISOGUI::on_rxDeviceSelect_clicked); + QObject::connect(ui->txDeviceSelect, &QPushButton::clicked, this, &AudioCATSISOGUI::on_txDeviceSelect_clicked); + QObject::connect(ui->rxChannels, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_rxChannels_currentIndexChanged); + QObject::connect(ui->rxVolume, &QDial::valueChanged, this, &AudioCATSISOGUI::on_rxVolume_valueChanged); + QObject::connect(ui->txChannels, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_txChannels_currentIndexChanged); + QObject::connect(ui->txVolume, &QDial::valueChanged, this, &AudioCATSISOGUI::on_txVolume_valueChanged); + QObject::connect(ui->fcPosRx, QOverload::of(&QComboBox::currentIndexChanged), this, &AudioCATSISOGUI::on_fcPosRx_currentIndexChanged); +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisogui.h b/plugins/samplemimo/audiocatsiso/audiocatsisogui.h new file mode 100644 index 000000000..6d5680ffa --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisogui.h @@ -0,0 +1,103 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 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 _AUDIOCATSISO_AUDIOCATSISOGUI_H_ +#define _AUDIOCATSISO_AUDIOCATSISOGUI_H_ + +#include +#include +#include + +#include "util/messagequeue.h" + +#include "audiocatsisosettings.h" +#include "audiocatsiso.h" + +class DeviceUISet; + +namespace Ui { + class AudioCATSISOGUI; +} + +class AudioCATSISOGUI : public DeviceGUI { + Q_OBJECT + +public: + explicit AudioCATSISOGUI(DeviceUISet *deviceUISet, QWidget* parent = 0); + virtual ~AudioCATSISOGUI(); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +private: + Ui::AudioCATSISOGUI* ui; + + AudioCATSISOSettings m_settings; + QList m_settingsKeys; + int m_rxSampleRate; + int m_txSampleRate; + QTimer m_updateTimer; + QTimer m_statusTimer; + bool m_doApplySettings; + bool m_forceSettings; + AudioCATSISO* m_sampleMIMO; + std::size_t m_tickCount; + std::vector m_deviceSampleRates; + std::vector m_deviceCenterFrequencies; //!< Center frequency in device + int m_lastEngineState; + MessageQueue m_inputMessageQueue; + static const int m_absMaxFreq = 61440; // kHz + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void displayFrequency(); + void displaySampleRate(); + void displayFcRxTooltip(); + void updateSpectrum(); + void sendSettings(); + void setCenterFrequency(qint64 centerFrequency); + bool handleMessage(const Message& message); + void makeUIConnections(); + +private slots: + void handleInputMessages(); + void on_streamIndex_currentIndexChanged(int index); + void on_spectrumSource_currentIndexChanged(int index); + void on_streamLock_toggled(bool checked); + void on_startStop_toggled(bool checked); + void on_centerFrequency_changed(quint64 value); + void on_log2Decim_currentIndexChanged(int index); + void on_dcBlock_toggled(bool checked); + void on_iqCorrection_toggled(bool checked); + void on_txEnable_toggled(bool checked); + void on_transverter_clicked(); + void on_rxDeviceSelect_clicked(); + void on_txDeviceSelect_clicked(); + void on_rxChannels_currentIndexChanged(int index); + void on_rxVolume_valueChanged(int value); + void on_txChannels_currentIndexChanged(int index); + void on_txVolume_valueChanged(int value); + void on_fcPosRx_currentIndexChanged(int index); + void openDeviceSettingsDialog(const QPoint& p); + void updateStatus(); + void updateHardware(); +}; + +#endif // _AUDIOCATSISO_AUDIOCATSISOGUI_H_ diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisogui.ui b/plugins/samplemimo/audiocatsiso/audiocatsisogui.ui new file mode 100644 index 000000000..66e4d96ed --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisogui.ui @@ -0,0 +1,842 @@ + + + AudioCATSISOGUI + + + + 0 + 0 + 360 + 270 + + + + + 0 + 0 + + + + + 360 + 270 + + + + + 360 + 303 + + + + + Liberation Sans + 9 + 50 + false + false + + + + Audio CAT SISO + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + + 16777215 + 22 + + + + + + + :/antenna.png + + + + + + + + 60 + 0 + + + + Select stream to which settings apply (frequency only) + + + + Rx + + + + + Tx + + + + + + + + + 16777215 + 22 + + + + + + + :/dsb.png + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + Select stream for main spectrum source + + + + Rx + + + + + Tx + + + + + + + + Lock spectrum display to stream selection + + + + + + + :/unlocked.png + :/locked.png:/unlocked.png + + + true + + + + + + + Enable/disable Tx + + + Tx + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + Transverter frequency translation dialog + + + X + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + + 58 + 0 + + + + I/Q sample rate kS/s + + + 0000.00k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Liberation Mono + 16 + 50 + false + false + + + + PointingHandCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Refresh list of audio devices + + + ... + + + + :/recycle.png:/recycle.png + + + + + + + + + + + + 24 + 24 + + + + Open dialog to select input device (Rx) + + + + + + + :/microphone.png:/microphone.png + + + + + + + + 0 + 0 + + + + Output device selected + + + ... + + + + + + + + + + + Dec + + + + + + + + 50 + 16777215 + + + + Decimation factor (Rx) + + + + 1 + + + + + 2 + + + + + 4 + + + + + 8 + + + + + + + + Fp + + + + + + + + 55 + 0 + + + + + 50 + 16777215 + + + + Relative position of device center frequency (Rx) + + + 2 + + + + Inf + + + + + Sup + + + + + Cen + + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + DC + + + + + + + + 45 + 0 + + + + + 45 + 16777215 + + + + IQ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Volume + + + + + + + + 24 + 0 + + + + + 24 + 24 + + + + Rx audio volume. Not supported by all devices + + + 10 + + + 1 + + + 10 + + + + + + + 1.0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Channel Map + + + + + + + + 80 + 0 + + + + How Rx audio channels map to IQ data + + + 0 + + + + I=L, Q=R + + + + + I=R, Q=L + + + + + Mono L + + + + + Mono R + + + + + + + + + + + + + 24 + 24 + + + + Open dialog to select output device (Tx) + + + + + + + :/sound_on.png:/sound_on.png + + + + + + + + 0 + 0 + + + + Output device selected + + + ... + + + + + + + + + + + Volume + + + + + + + + 24 + 0 + + + + + 24 + 24 + + + + Tx audio volume. Not supported by all devices + + + 10 + + + 1 + + + 10 + + + + + + + 1.0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Channel Map + + + + + + + + 80 + 0 + + + + How Tx audio channels map to IQ data + + + 0 + + + + I=L, Q=R + + + + + I=R, Q=L + + + + + + + + + + + + + 0 + 0 + + + + CAT + + + + + + + + 0 + 0 + + + + CAT Device + + + + + + + + 0 + 0 + + + + Hamlib device type + + + + + + + + 24 + 24 + + + + CAT settings + + + + + + + :/tool.png:/tool.png + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TransverterButton + QPushButton +
gui/transverterbutton.h
+
+
+ + + + +
diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.cpp b/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.cpp new file mode 100644 index 000000000..c7f506b45 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.cpp @@ -0,0 +1,146 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" + +#ifdef SERVER_MODE +#include "audiocatsiso.h" +#else +#include "audiocatsisogui.h" +#endif +#include "audiocatsisoplugin.h" +#include "audiocatsisowebapiadapter.h" + +const PluginDescriptor AudioCATSISOPlugin::m_pluginDescriptor = { + QStringLiteral("AudioCATSISO"), + QStringLiteral("Audio CAT SISO"), + QStringLiteral("7.15.0"), + QStringLiteral("(c) Edouard Griffiths, F4EXB"), + QStringLiteral("https://github.com/f4exb/sdrangel"), + true, + QStringLiteral("https://github.com/f4exb/sdrangel") +}; + +static constexpr const char* const m_hardwareID = "AudioCATSISO"; +static constexpr const char* const m_deviceTypeID = AUDIOCATSISO_DEVICE_TYPE_ID; + +AudioCATSISOPlugin::AudioCATSISOPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& AudioCATSISOPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void AudioCATSISOPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleMIMO(m_deviceTypeID, this); +} + +void AudioCATSISOPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + originDevices.append(OriginDevice( + "AudioCATSISO", // Displayable name + m_hardwareID, // Hardware ID + QString(), // Serial + 0, // Sequence + 1, // Number of Rx streams + 1 // Number of Tx streams + )); + + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices AudioCATSISOPlugin::enumSampleMIMO(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + result.append(SamplingDevice( + it->displayableName, + it->hardwareId, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::PhysicalDevice, + PluginInterface::SamplingDevice::StreamMIMO, + 1, // MIMO is always considered as a single device + 0) + ); + qDebug("MetisMISOPlugin::enumSampleMIMO: enumerated Metis device #%d", it->sequence); + } + } + + return result; +} + +#ifdef SERVER_MODE +DeviceGUI* AudioCATSISOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sourceId; + (void) widget; + (void) deviceUISet; + return 0; +} +#else +DeviceGUI* AudioCATSISOPlugin::createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if (sourceId == m_deviceTypeID) { + AudioCATSISOGUI* gui = new AudioCATSISOGUI(deviceUISet); + *widget = gui; + return gui; + } else { + return nullptr; + } +} +#endif + +DeviceSampleMIMO *AudioCATSISOPlugin::createSampleMIMOPluginInstance(const QString& mimoId, DeviceAPI *deviceAPI) +{ + if (mimoId == m_deviceTypeID) + { + AudioCATSISO* input = new AudioCATSISO(deviceAPI); + return input; + } + else + { + return nullptr; + } +} + +DeviceWebAPIAdapter *AudioCATSISOPlugin::createDeviceWebAPIAdapter() const +{ + return new AudioCATSISOWebAPIAdapter(); +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.h b/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.h new file mode 100644 index 000000000..6c0231127 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisoplugin.h @@ -0,0 +1,53 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 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 _AUDIOCATSISO_AUDIOCATSISOPLUGIN_H +#define _AUDIOCATSISO_AUDIOCATSISOPLUGIN_H + +#include + +#include "plugin/plugininterface.h" + +class PluginAPI; + +#define AUDIOCATSISO_DEVICE_TYPE_ID "sdrangel.samplemimo.audiocatsiso" + +class AudioCATSISOPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID AUDIOCATSISO_DEVICE_TYPE_ID) + +public: + explicit AudioCATSISOPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleMIMO(const OriginDevices& originDevices); + virtual DeviceGUI* createSampleMIMOPluginInstanceGUI( + const QString& sourceId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleMIMO* createSampleMIMOPluginInstance(const QString& sourceId, DeviceAPI *deviceAPI); + virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // _AUDIOCATSISO_AUDIOCATSISOPLUGIN_H diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisosettings.cpp b/plugins/samplemimo/audiocatsiso/audiocatsisosettings.cpp new file mode 100644 index 000000000..d03c3be93 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisosettings.cpp @@ -0,0 +1,304 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include "util/simpleserializer.h" +#include "audiocatsisosettings.h" + +AudioCATSISOSettings::AudioCATSISOSettings() +{ + resetToDefaults(); +} + +void AudioCATSISOSettings::resetToDefaults() +{ + m_rxCenterFrequency = 14200000; + m_txCenterFrequency = 14200000; + m_rxDeviceName = ""; + m_rxVolume = 1.0f; + m_log2Decim = 0; + m_rxIQMapping = LR; + m_dcBlock = false; + m_iqCorrection = false; + m_fcPosRx = FC_POS_CENTER; + m_txDeviceName = ""; + m_txVolume = 1.0f; + m_txIQMapping = LR; + m_log2Interp = 0; + m_fcPosTx = FC_POS_CENTER; + m_streamIndex = 0; + m_spectrumStreamIndex = 0; + m_txEnable = false; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; +} + +AudioCATSISOSettings::AudioCATSISOSettings(const AudioCATSISOSettings& other) +{ + m_rxCenterFrequency = other.m_rxCenterFrequency; + m_txCenterFrequency = other.m_txCenterFrequency; + m_rxDeviceName = other.m_rxDeviceName; + m_rxVolume = other.m_rxVolume; + m_log2Decim = other.m_log2Decim; + m_rxIQMapping = other.m_rxIQMapping; + m_dcBlock = other.m_dcBlock; + m_iqCorrection = other.m_iqCorrection; + m_fcPosRx = other.m_fcPosRx; + m_txDeviceName = other.m_txDeviceName; + m_txVolume = other.m_txVolume; + m_txIQMapping = other.m_txIQMapping; + m_log2Interp = other.m_log2Interp; + m_fcPosTx = other.m_fcPosTx; + m_streamIndex = other.m_streamIndex; + m_spectrumStreamIndex = other.m_spectrumStreamIndex; + m_txEnable = other.m_txEnable; + m_useReverseAPI = other.m_useReverseAPI; + m_reverseAPIAddress = other.m_reverseAPIAddress; + m_reverseAPIPort = other.m_reverseAPIPort; + m_reverseAPIDeviceIndex = other.m_reverseAPIDeviceIndex; +} + +QByteArray AudioCATSISOSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_rxDeviceName); + s.writeU64(2, m_rxCenterFrequency); + s.writeFloat(3, m_rxVolume); + s.writeU32(4, m_log2Decim); + s.writeS32(5, (int)m_rxIQMapping); + s.writeBool(6, m_dcBlock); + s.writeBool(7, m_iqCorrection); + s.writeS32(8, (int) m_fcPosRx); + + s.writeString(21, m_txDeviceName); + s.writeU64(22, m_rxCenterFrequency); + s.writeFloat(23, m_txVolume); + s.writeS32(24, (int)m_txIQMapping); + s.writeU32(25, m_log2Decim); + s.writeS32(26, (int) m_fcPosTx); + + s.writeBool(51, m_useReverseAPI); + s.writeString(52, m_reverseAPIAddress); + s.writeU32(53, m_reverseAPIPort); + s.writeU32(54, m_reverseAPIDeviceIndex); + s.writeS32(55, m_streamIndex); + s.writeS32(56, m_spectrumStreamIndex); + s.writeBool(57, m_txEnable); + + return s.final(); +} + +bool AudioCATSISOSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + uint32_t uintval; + int intval; + + d.readString(1, &m_rxDeviceName, ""); + d.readU64(2, &m_rxCenterFrequency, 14200000); + d.readFloat(3, &m_rxVolume, 1.0f); + d.readU32(4, &m_log2Decim, 0); + d.readS32(5, (int *)&m_rxIQMapping, IQMapping::L); + d.readBool(6, &m_dcBlock, false); + d.readBool(7, &m_iqCorrection, false); + d.readS32(8, &intval, 2); + m_fcPosRx = (fcPos_t) intval; + + d.readString(21, &m_txDeviceName, ""); + d.readU64(22, &m_txCenterFrequency, 14200000); + d.readFloat(23, &m_txVolume, 1.0f); + d.readS32(24,(int *)&m_txIQMapping, IQMapping::LR); + d.readU32(25, &m_log2Interp, 0); + d.readS32(26, &intval, 2); + m_fcPosTx = (fcPos_t) intval; + + d.readBool(51, &m_useReverseAPI, false); + d.readString(52, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(53, &uintval, 0); + + if ((uintval > 1023) && (uintval < 65535)) { + m_reverseAPIPort = uintval; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(54, &uintval, 0); + m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval; + + d.readS32(55, &m_streamIndex, 0); + d.readS32(56, &m_spectrumStreamIndex, 0); + d.readBool(57, &m_txEnable, false); + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +void AudioCATSISOSettings::applySettings(const QStringList& settingsKeys, const AudioCATSISOSettings& settings) +{ + if (settingsKeys.contains("rxDeviceName")) { + m_rxDeviceName = settings.m_rxDeviceName; + } + if (settingsKeys.contains("rxCenterFrequency")) { + m_rxCenterFrequency = settings.m_rxCenterFrequency; + } + if (settingsKeys.contains("rxVolume")) { + m_rxVolume = settings.m_rxVolume; + } + if (settingsKeys.contains("log2Decim")) { + m_log2Decim = settings.m_log2Decim; + } + if (settingsKeys.contains("rxIQMapping")) { + m_rxIQMapping = settings.m_rxIQMapping; + } + if (settingsKeys.contains("dcBlock")) { + m_dcBlock = settings.m_dcBlock; + } + if (settingsKeys.contains("iqCorrection")) { + m_iqCorrection = settings.m_iqCorrection; + } + if (settingsKeys.contains("fcPosRx")) { + m_fcPosRx = settings.m_fcPosRx; + } + + if (settingsKeys.contains("txDeviceName")) { + m_txDeviceName = settings.m_txDeviceName; + } + if (settingsKeys.contains("txCenterFrequency")) { + m_txCenterFrequency = settings.m_txCenterFrequency; + } + if (settingsKeys.contains("txVolume")) { + m_txVolume = settings.m_txVolume; + } + if (settingsKeys.contains("txIQMapping")) { + m_txIQMapping = settings.m_txIQMapping; + } + if (settingsKeys.contains("log2Interp")) { + m_log2Interp = settings.m_log2Interp; + } + if (settingsKeys.contains("fcPosTx")) { + m_fcPosTx = settings.m_fcPosTx; + } + + if (settingsKeys.contains("streamIndex")) { + m_streamIndex = settings.m_streamIndex; + } + if (settingsKeys.contains("spectrumStreamIndex")) { + m_spectrumStreamIndex = settings.m_spectrumStreamIndex; + } + if (settingsKeys.contains("txEnable")) { + m_txEnable = settings.m_txEnable; + } + if (settingsKeys.contains("useReverseAPI")) { + m_useReverseAPI = settings.m_useReverseAPI; + } + if (settingsKeys.contains("reverseAPIAddress")) { + m_reverseAPIAddress = settings.m_reverseAPIAddress; + } + if (settingsKeys.contains("reverseAPIPort")) { + m_reverseAPIPort = settings.m_reverseAPIPort; + } + if (settingsKeys.contains("reverseAPIDeviceIndex")) { + m_reverseAPIDeviceIndex = settings.m_reverseAPIDeviceIndex; + } +} + +QString AudioCATSISOSettings::getDebugString(const QStringList& settingsKeys, bool force) const +{ + std::ostringstream ostr; + + if (settingsKeys.contains("rxDeviceName") || force) { + ostr << " m_rxDeviceName: " << m_rxDeviceName.toStdString(); + } + if (settingsKeys.contains("rxCenterFrequency") || force) { + ostr << " m_rxCenterFrequency: " << m_rxCenterFrequency; + } + if (settingsKeys.contains("rxVolume") || force) { + ostr << " m_rxVolume: " << m_rxVolume; + } + if (settingsKeys.contains("log2Decim") || force) { + ostr << " m_log2Decim: " << m_log2Decim; + } + if (settingsKeys.contains("rxIQMapping") || force) { + ostr << " m_rxIQMapping: " << m_rxIQMapping; + } + if (settingsKeys.contains("dcBlock") || force) { + ostr << " m_dcBlock: " << m_dcBlock; + } + if (settingsKeys.contains("iqCorrection") || force) { + ostr << " m_iqCorrection: " << m_iqCorrection; + } + if (settingsKeys.contains("fcPosRx") || force) { + ostr << " m_fcPosRx: " << m_fcPosRx; + } + + if (settingsKeys.contains("txDeviceName") || force) { + ostr << " m_txDeviceName: " << m_txDeviceName.toStdString(); + } + if (settingsKeys.contains("txVolume") || force) { + ostr << " m_txVolume: " << m_txVolume; + } + if (settingsKeys.contains("txIQMapping") || force) { + ostr << " m_txIQMapping: " << m_txIQMapping; + } + if (settingsKeys.contains("log2Interp") || force) { + ostr << " m_log2Interp: " << m_log2Interp; + } + if (settingsKeys.contains("fcPosTx") || force) { + ostr << " m_fcPosTx: " << m_fcPosTx; + } + + if (settingsKeys.contains("streamIndex") || force) { + ostr << " m_streamIndex: " << m_streamIndex; + } + if (settingsKeys.contains("spectrumStreamIndex") || force) { + ostr << " m_spectrumStreamIndex: " << m_spectrumStreamIndex; + } + if (settingsKeys.contains("txEnable") || force) { + ostr << " m_txEnable: " << m_txEnable; + } + if (settingsKeys.contains("useReverseAPI") || force) { + ostr << " m_useReverseAPI: " << m_useReverseAPI; + } + if (settingsKeys.contains("reverseAPIAddress") || force) { + ostr << " m_reverseAPIAddress: " << m_reverseAPIAddress.toStdString(); + } + if (settingsKeys.contains("reverseAPIPort") || force) { + ostr << " m_reverseAPIPort: " << m_reverseAPIPort; + } + if (settingsKeys.contains("reverseAPIDeviceIndex") || force) { + ostr << " m_reverseAPIDeviceIndex: " << m_reverseAPIDeviceIndex; + } + + return QString(ostr.str().c_str()); +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisosettings.h b/plugins/samplemimo/audiocatsiso/audiocatsisosettings.h new file mode 100644 index 000000000..3dbf5f581 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisosettings.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 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 _AUDIOCATSISO_AUDIOCATSISOSETTINGS_H_ +#define _AUDIOCATSISO_AUDIOCATSISOSETTINGS_H_ + +#include +#include "audio/audiodeviceinfo.h" + +struct AudioCATSISOSettings { + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + enum IQMapping { + LR, + RL, + L, + R + }; + + quint64 m_rxCenterFrequency; + quint64 m_txCenterFrequency; + bool m_transverterMode; + qint64 m_transverterDeltaFrequency; + bool m_iqOrder; + int m_streamIndex; + int m_spectrumStreamIndex; //!< spectrum source + bool m_txEnable; + + QString m_rxDeviceName; // Including realm, as from getFullDeviceName below + IQMapping m_rxIQMapping; + unsigned int m_log2Decim; + fcPos_t m_fcPosRx; + bool m_dcBlock; + bool m_iqCorrection; + float m_rxVolume; + + QString m_txDeviceName; // Including realm, as from getFullDeviceName below + IQMapping m_txIQMapping; + unsigned int m_log2Interp; + fcPos_t m_fcPosTx; //!< Not implemented yet + float m_txVolume; //!< Not implemented yet + + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + + AudioCATSISOSettings(); + AudioCATSISOSettings(const AudioCATSISOSettings& other); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + AudioCATSISOSettings& operator=(const AudioCATSISOSettings&) = default; + void applySettings(const QStringList& settingsKeys, const AudioCATSISOSettings& settings); + QString getDebugString(const QStringList& settingsKeys, bool force=false) const; + + // Append realm to device names, because there may be multiple devices with the same name on Windows + static QString getFullDeviceName(const AudioDeviceInfo &deviceInfo) + { + QString realm = deviceInfo.realm(); + if (realm != "" && realm != "default" && realm != "alsa") + return deviceInfo.deviceName() + " " + realm; + else + return deviceInfo.deviceName(); + } + + static int getSampleRateFromIndex(unsigned int index); +}; + +#endif /* _AUDIOCATSISO_AUDIOCATSISOSETTINGS_H_ */ diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.cpp b/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.cpp new file mode 100644 index 000000000..57ba18994 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.cpp @@ -0,0 +1,52 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// 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 "SWGDeviceSettings.h" +#include "audiocatsiso.h" +#include "audiocatsisowebapiadapter.h" + +AudioCATSISOWebAPIAdapter::AudioCATSISOWebAPIAdapter() +{} + +AudioCATSISOWebAPIAdapter::~AudioCATSISOWebAPIAdapter() +{} + +int AudioCATSISOWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setAudioCatsisoSettings(new SWGSDRangel::SWGAudioCATSISOSettings()); + response.getAudioCatsisoSettings()->init(); + AudioCATSISO::webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int AudioCATSISOWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + AudioCATSISO::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response); + return 200; +} diff --git a/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.h b/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.h new file mode 100644 index 000000000..aed197359 --- /dev/null +++ b/plugins/samplemimo/audiocatsiso/audiocatsisowebapiadapter.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2023 Edouard Griffiths, F4EXB // +// // +// Implementation of static web API adapters used for preset serialization and // +// deserialization // +// // +// 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 _AUDIOCATSISO_AUDIOCATSISOWEBAPIADAPTER_H_ +#define _AUDIOCATSISO_AUDIOCATSISOWEBAPIADAPTER_H_ + +#include "device/devicewebapiadapter.h" +#include "audiocatsisosettings.h" + +class AudioCATSISOWebAPIAdapter : public DeviceWebAPIAdapter +{ +public: + AudioCATSISOWebAPIAdapter(); + virtual ~AudioCATSISOWebAPIAdapter(); + virtual QByteArray serialize() { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + +private: + AudioCATSISOSettings m_settings; +}; + +#endif // _AUDIOCATSISO_AUDIOCATSISOWEBAPIADAPTER_H_ diff --git a/sdrbase/resources/webapi.qrc b/sdrbase/resources/webapi.qrc index 9ecf65865..06e3acf1f 100644 --- a/sdrbase/resources/webapi.qrc +++ b/sdrbase/resources/webapi.qrc @@ -18,6 +18,7 @@ webapi/doc/swagger/include/APTDemod.yaml webapi/doc/swagger/include/ATVDemod.yaml webapi/doc/swagger/include/ATVMod.yaml + webapi/doc/swagger/include/AudioCATSISO.yaml webapi/doc/swagger/include/AudioInput.yaml webapi/doc/swagger/include/AudioOutput.yaml webapi/doc/swagger/include/BeamSteeringCWMod.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 8f4279c4b..cd992711f 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" });