M17 Modulator: copy of NFM modulator

This commit is contained in:
f4exb 2022-06-10 01:42:24 +02:00
parent 424d072f0c
commit 7e8e1c12fa
36 changed files with 5301 additions and 7 deletions

View File

@ -61,4 +61,5 @@ endif()
if (ENABLE_CHANNELTX_MODFREEDV AND CODEC2_FOUND)
add_subdirectory(modfreedv)
add_subdirectory(modm17)
endif(CODEC2_FOUND)

View File

@ -0,0 +1,64 @@
project(modm17)
set(modm17_SOURCES
m17mod.cpp
m17modbaseband.cpp
m17modsource.cpp
m17modplugin.cpp
m17modsettings.cpp
m17modwebapiadapter.cpp
)
set(modm17_HEADERS
m17mod.h
m17modbaseband.h
m17modsource.h
m17modplugin.h
m17modsettings.h
m17modwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(modm17_SOURCES
${modm17_SOURCES}
m17modgui.cpp
m17modgui.ui
)
set(modm17_HEADERS
${modm17_HEADERS}
m17modgui.h
)
set(TARGET_NAME modm17)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME modm17srv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${modm17_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,766 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QTime>
#include <QDebug>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGChannelReport.h"
#include "SWGNFMModReport.h"
#include <stdio.h>
#include <complex.h>
#include <algorithm>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
#include "m17modbaseband.h"
#include "m17mod.h"
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureM17Mod, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceName, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceSeek, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgReportFileSourceStreamData, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgReportFileSourceStreamTiming, Message)
const char* const M17Mod::m_channelIdURI = "sdrangel.channeltx.modm17";
const char* const M17Mod::m_channelId = "M17Mod";
M17Mod::M17Mod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
m_deviceAPI(deviceAPI),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
m_sampleRate(48000)
{
setObjectName(m_channelId);
m_thread = new QThread(this);
m_basebandSource = new M17ModBaseband();
m_basebandSource->setInputFileStream(&m_ifstream);
m_basebandSource->setChannel(this);
m_basebandSource->moveToThread(m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSource(this);
m_deviceAPI->addChannelSourceAPI(this);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&M17Mod::networkManagerFinished
);
}
M17Mod::~M17Mod()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&M17Mod::networkManagerFinished
);
delete m_networkManager;
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this);
delete m_basebandSource;
delete m_thread;
}
void M17Mod::setDeviceAPI(DeviceAPI *deviceAPI)
{
if (deviceAPI != m_deviceAPI)
{
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this);
m_deviceAPI = deviceAPI;
m_deviceAPI->addChannelSource(this);
m_deviceAPI->addChannelSinkAPI(this);
}
}
void M17Mod::start()
{
qDebug("M17Mod::start");
m_basebandSource->reset();
m_thread->start();
}
void M17Mod::stop()
{
qDebug("M17Mod::stop");
m_thread->exit();
m_thread->wait();
}
void M17Mod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
{
m_basebandSource->pull(begin, nbSamples);
}
void M17Mod::setCenterFrequency(qint64 frequency)
{
M17ModSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureM17Mod *msgToGUI = MsgConfigureM17Mod::create(settings, false);
m_guiMessageQueue->push(msgToGUI);
}
}
bool M17Mod::handleMessage(const Message& cmd)
{
if (MsgConfigureM17Mod::match(cmd))
{
MsgConfigureM17Mod& cfg = (MsgConfigureM17Mod&) cmd;
qDebug() << "M17Mod::handleMessage: MsgConfigureM17Mod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgConfigureFileSourceName::match(cmd))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
m_fileName = conf.getFileName();
openFileStream();
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceName:"
<< " m_fileName: " << m_fileName;
return true;
}
else if (MsgConfigureFileSourceSeek::match(cmd))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
int seekPercentage = conf.getPercentage();
seekFileStream(seekPercentage);
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceSeek:"
<< " seekPercentage: " << seekPercentage;
return true;
}
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
{
std::size_t samplesCount;
if (m_ifstream.eof()) {
samplesCount = m_fileSize / sizeof(Real);
} else {
samplesCount = m_ifstream.tellg() / sizeof(Real);
}
MsgReportFileSourceStreamTiming *report;
report = MsgReportFileSourceStreamTiming::create(samplesCount);
getMessageQueueToGUI()->push(report);
return true;
}
else if (MsgConfigureFileSourceName::match(cmd))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
m_fileName = conf.getFileName();
openFileStream();
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceName:"
<< " m_fileName: " << m_fileName;
return true;
}
else if (MsgConfigureFileSourceSeek::match(cmd))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
int seekPercentage = conf.getPercentage();
seekFileStream(seekPercentage);
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceSeek:"
<< " seekPercentage: " << seekPercentage;
return true;
}
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
{
std::size_t samplesCount;
if (m_ifstream.eof()) {
samplesCount = m_fileSize / sizeof(Real);
} else {
samplesCount = m_ifstream.tellg() / sizeof(Real);
}
MsgReportFileSourceStreamTiming *report;
report = MsgReportFileSourceStreamTiming::create(samplesCount);
getMessageQueueToGUI()->push(report);
return true;
}
else if (DSPSignalNotification::match(cmd))
{
// Forward to the source
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "M17Mod::handleMessage: DSPSignalNotification";
m_basebandSource->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(new DSPSignalNotification(notif));
}
return true;
}
else if (MainCore::MsgChannelDemodQuery::match(cmd))
{
qDebug() << "M17Mod::handleMessage: MsgChannelDemodQuery";
sendSampleRateToDemodAnalyzer();
return true;
}
else
{
return false;
}
}
void M17Mod::openFileStream()
{
if (m_ifstream.is_open()) {
m_ifstream.close();
}
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
m_fileSize = m_ifstream.tellg();
m_ifstream.seekg(0,std::ios_base::beg);
m_sampleRate = 48000; // fixed rate
m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate);
qDebug() << "M17Mod::openFileStream: " << m_fileName.toStdString().c_str()
<< " fileSize: " << m_fileSize << "bytes"
<< " length: " << m_recordLength << " seconds";
MsgReportFileSourceStreamData *report;
report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength);
getMessageQueueToGUI()->push(report);
}
void M17Mod::seekFileStream(int seekPercentage)
{
QMutexLocker mutexLocker(&m_settingsMutex);
if (m_ifstream.is_open())
{
int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate;
seekPoint *= sizeof(Real);
m_ifstream.clear();
m_ifstream.seekg(seekPoint, std::ios::beg);
}
}
void M17Mod::applySettings(const M17ModSettings& settings, bool force)
{
qDebug() << "M17Mod::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fmDeviation: " << settings.m_fmDeviation
<< " m_volumeFactor: " << settings.m_volumeFactor
<< " m_toneFrequency: " << settings.m_toneFrequency
<< " m_channelMute: " << settings.m_channelMute
<< " m_playLoop: " << settings.m_playLoop
<< " m_modAFInput " << settings.m_modAFInput
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIAddress: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
}
if ((settings.m_volumeFactor != m_settings.m_volumeFactor) || force) {
reverseAPIKeys.append("volumeFactor");
}
if ((settings.m_channelMute != m_settings.m_channelMute) || force) {
reverseAPIKeys.append("channelMute");
}
if ((settings.m_playLoop != m_settings.m_playLoop) || force) {
reverseAPIKeys.append("playLoop");
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) {
reverseAPIKeys.append("modAFInput");
}
if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
reverseAPIKeys.append("toneFrequency");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force) {
reverseAPIKeys.append("feedbackAudioDeviceName");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSource(this, settings.m_streamIndex);
m_deviceAPI->addChannelSourceAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
M17ModBaseband::MsgConfigureM17ModBaseband *msg = M17ModBaseband::MsgConfigureM17ModBaseband::create(settings, force);
m_basebandSource->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "settings", pipes);
if (pipes.size() > 0) {
sendChannelSettings(pipes, reverseAPIKeys, settings, force);
}
m_settings = settings;
}
QByteArray M17Mod::serialize() const
{
return m_settings.serialize();
}
bool M17Mod::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureM17Mod *msg = MsgConfigureM17Mod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return success;
}
void M17Mod::sendSampleRateToDemodAnalyzer()
{
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue* messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
getAudioSampleRate()
);
messageQueue->push(msg);
}
}
}
int M17Mod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
response.getM17ModSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int M17Mod::webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage)
{
(void) errorMessage;
response.setIndex(m_settings.m_workspaceIndex);
return 200;
}
int M17Mod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
M17ModSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureM17Mod *msg = MsgConfigureM17Mod::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureM17Mod *msgToGUI = MsgConfigureM17Mod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void M17Mod::webapiUpdateChannelSettings(
M17ModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("channelMute")) {
settings.m_channelMute = response.getM17ModSettings()->getChannelMute() != 0;
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getM17ModSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getM17ModSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("modAFInput")) {
settings.m_modAFInput = (M17ModSettings::M17ModInputAF) response.getM17ModSettings()->getModAfInput();
}
if (channelSettingsKeys.contains("playLoop")) {
settings.m_playLoop = response.getM17ModSettings()->getPlayLoop() != 0;
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getM17ModSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getM17ModSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getM17ModSettings()->getTitle();
}
if (channelSettingsKeys.contains("toneFrequency")) {
settings.m_toneFrequency = response.getM17ModSettings()->getToneFrequency();
}
if (channelSettingsKeys.contains("volumeFactor")) {
settings.m_volumeFactor = response.getM17ModSettings()->getVolumeFactor();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getM17ModSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getM17ModSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getM17ModSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getM17ModSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getM17ModSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getNfmModSettings()->getReverseApiChannelIndex();
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getM17ModSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getM17ModSettings()->getRollupState());
}
}
int M17Mod::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setM17ModReport(new SWGSDRangel::SWGM17ModReport());
response.getM17ModReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void M17Mod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const M17ModSettings& settings)
{
response.getM17ModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0);
response.getM17ModSettings()->setFmDeviation(settings.m_fmDeviation);
response.getM17ModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getM17ModSettings()->setModAfInput((int) settings.m_modAFInput);
response.getM17ModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0);
response.getM17ModSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getM17ModSettings()->setRgbColor(settings.m_rgbColor);
if (response.getM17ModSettings()->getTitle()) {
*response.getM17ModSettings()->getTitle() = settings.m_title;
} else {
response.getM17ModSettings()->setTitle(new QString(settings.m_title));
}
response.getM17ModSettings()->setToneFrequency(settings.m_toneFrequency);
response.getM17ModSettings()->setVolumeFactor(settings.m_volumeFactor);
if (response.getM17ModSettings()->getAudioDeviceName()) {
*response.getM17ModSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
} else {
response.getM17ModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
response.getM17ModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getM17ModSettings()->getReverseApiAddress()) {
*response.getM17ModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getM17ModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getM17ModSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getM17ModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getM17ModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_channelMarker)
{
if (response.getM17ModSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getM17ModSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getM17ModSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getM17ModSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getM17ModSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getM17ModSettings()->setRollupState(swgRollupState);
}
}
}
void M17Mod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getM17ModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
response.getM17ModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
response.getM17ModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
}
void M17Mod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const M17ModSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void M17Mod::sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const M17ModSettings& settings,
bool force)
{
for (const auto& pipe : pipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
if (messageQueue)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
MainCore::MsgChannelSettings *msg = MainCore::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
messageQueue->push(msg);
}
}
}
void M17Mod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const M17ModSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(1); // single source (Tx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString(m_channelId));
swgChannelSettings->setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
SWGSDRangel::SWGM17ModSettings *swgM17ModSettings = swgChannelSettings->getM17ModSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("channelMute") || force) {
swgM17ModSettings->setChannelMute(settings.m_channelMute ? 1 : 0);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgM17ModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("modAFInput") || force) {
swgM17ModSettings->setModAfInput((int) settings.m_modAFInput);
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgM17ModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
if (channelSettingsKeys.contains("playLoop") || force) {
swgM17ModSettings->setPlayLoop(settings.m_playLoop ? 1 : 0);
}
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgM17ModSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgM17ModSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgM17ModSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgM17ModSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("toneFrequency") || force) {
swgM17ModSettings->setToneFrequency(settings.m_toneFrequency);
}
if (channelSettingsKeys.contains("volumeFactor") || force) {
swgM17ModSettings->setVolumeFactor(settings.m_volumeFactor);
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgM17ModSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgM17ModSettings->setChannelMarker(swgChannelMarker);
}
if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force))
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
swgM17ModSettings->setRollupState(swgRollupState);
}
}
void M17Mod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "M17Mod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("M17Mod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
double M17Mod::getMagSq() const
{
return m_basebandSource->getMagSq();
}
void M17Mod::setLevelMeter(QObject *levelMeter)
{
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
}
uint32_t M17Mod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSinkStreams();
}
int M17Mod::getAudioSampleRate() const
{
return m_basebandSource->getAudioSampleRate();
}
int M17Mod::getFeedbackAudioSampleRate() const
{
return m_basebandSource->getFeedbackAudioSampleRate();
}

View File

@ -0,0 +1,288 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MOD_H_
#define PLUGINS_CHANNELTX_MODM17_M17MOD_H_
#include <vector>
#include <iostream>
#include <fstream>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesource.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "m17modsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class M17ModBaseband;
class ObjectPipe;
class M17Mod : public BasebandSampleSource, public ChannelAPI {
public:
class MsgConfigureM17Mod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const M17ModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureM17Mod* create(const M17ModSettings& settings, bool force)
{
return new MsgConfigureM17Mod(settings, force);
}
private:
M17ModSettings m_settings;
bool m_force;
MsgConfigureM17Mod(const M17ModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureFileSourceName : public Message
{
MESSAGE_CLASS_DECLARATION
public:
const QString& getFileName() const { return m_fileName; }
static MsgConfigureFileSourceName* create(const QString& fileName)
{
return new MsgConfigureFileSourceName(fileName);
}
private:
QString m_fileName;
MsgConfigureFileSourceName(const QString& fileName) :
Message(),
m_fileName(fileName)
{ }
};
class MsgConfigureFileSourceSeek : public Message
{
MESSAGE_CLASS_DECLARATION
public:
int getPercentage() const { return m_seekPercentage; }
static MsgConfigureFileSourceSeek* create(int seekPercentage)
{
return new MsgConfigureFileSourceSeek(seekPercentage);
}
protected:
int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
MsgConfigureFileSourceSeek(int seekPercentage) :
Message(),
m_seekPercentage(seekPercentage)
{ }
};
class MsgConfigureFileSourceStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureFileSourceStreamTiming* create()
{
return new MsgConfigureFileSourceStreamTiming();
}
private:
MsgConfigureFileSourceStreamTiming() :
Message()
{ }
};
class MsgReportFileSourceStreamTiming : public Message
{
MESSAGE_CLASS_DECLARATION
public:
std::size_t getSamplesCount() const { return m_samplesCount; }
static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount)
{
return new MsgReportFileSourceStreamTiming(samplesCount);
}
protected:
std::size_t m_samplesCount;
MsgReportFileSourceStreamTiming(std::size_t samplesCount) :
Message(),
m_samplesCount(samplesCount)
{ }
};
class MsgReportFileSourceStreamData : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
quint32 getRecordLength() const { return m_recordLength; }
static MsgReportFileSourceStreamData* create(int sampleRate,
quint32 recordLength)
{
return new MsgReportFileSourceStreamData(sampleRate, recordLength);
}
protected:
int m_sampleRate;
quint32 m_recordLength;
MsgReportFileSourceStreamData(int sampleRate,
quint32 recordLength) :
Message(),
m_sampleRate(sampleRate),
m_recordLength(recordLength)
{ }
};
//=================================================================
M17Mod(DeviceAPI *deviceAPI);
virtual ~M17Mod();
virtual void destroy() { delete this; }
virtual void setDeviceAPI(DeviceAPI *deviceAPI);
virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; }
virtual void start();
virtual void stop();
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSourceName() { return objectName(); }
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_settings.m_inputFrequencyOffset;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const M17ModSettings& settings);
static void webapiUpdateChannelSettings(
M17ModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
double getMagSq() const;
void setLevelMeter(QObject *levelMeter);
uint32_t getNumberOfDeviceStreams() const;
int getAudioSampleRate() const;
int getFeedbackAudioSampleRate() const;
static const char* const m_channelIdURI;
static const char* const m_channelId;
private:
enum RateState {
RSInitialFill,
RSRunning
};
DeviceAPI* m_deviceAPI;
QThread *m_thread;
M17ModBaseband* m_basebandSource;
M17ModSettings m_settings;
SampleVector m_sampleBuffer;
QMutex m_settingsMutex;
std::ifstream m_ifstream;
QString m_fileName;
quint64 m_fileSize; //!< raw file size (bytes)
quint32 m_recordLength; //!< record length in seconds computed from file size
int m_sampleRate;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
virtual bool handleMessage(const Message& cmd);
void applySettings(const M17ModSettings& settings, bool force = false);
void sendSampleRateToDemodAnalyzer();
void openFileStream();
void seekFileStream(int seekPercentage);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const M17ModSettings& settings, bool force);
void sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const M17ModSettings& settings,
bool force
);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const M17ModSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MOD_H_ */

View File

@ -0,0 +1,232 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/upchannelizer.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "m17modbaseband.h"
MESSAGE_CLASS_DEFINITION(M17ModBaseband::MsgConfigureM17ModBaseband, Message)
M17ModBaseband::M17ModBaseband() :
m_mutex(QMutex::Recursive)
{
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
m_channelizer = new UpChannelizer(&m_source);
qDebug("M17ModBaseband::M17ModBaseband");
QObject::connect(
&m_sampleFifo,
&SampleSourceFifo::dataRead,
this,
&M17ModBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_source.getFeedbackAudioFifo(), getInputMessageQueue());
m_source.applyFeedbackAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
M17ModBaseband::~M17ModBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_source.getFeedbackAudioFifo());
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
delete m_channelizer;
}
void M17ModBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void M17ModBaseband::setChannel(ChannelAPI *channel)
{
m_source.setChannel(channel);
}
void M17ModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
{
unsigned int part1Begin, part1End, part2Begin, part2End;
m_sampleFifo.read(nbSamples, part1Begin, part1End, part2Begin, part2End);
SampleVector& data = m_sampleFifo.getData();
if (part1Begin != part1End)
{
std::copy(
data.begin() + part1Begin,
data.begin() + part1End,
begin
);
}
unsigned int shift = part1End - part1Begin;
if (part2Begin != part2End)
{
std::copy(
data.begin() + part2Begin,
data.begin() + part2End,
begin + shift
);
}
}
void M17ModBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
SampleVector& data = m_sampleFifo.getData();
unsigned int ipart1begin;
unsigned int ipart1end;
unsigned int ipart2begin;
unsigned int ipart2end;
qreal rmsLevel, peakLevel;
int numSamples;
unsigned int remainder = m_sampleFifo.remainder();
while ((remainder > 0) && (m_inputMessageQueue.size() == 0))
{
m_sampleFifo.write(remainder, ipart1begin, ipart1end, ipart2begin, ipart2end);
if (ipart1begin != ipart1end) { // first part of FIFO data
processFifo(data, ipart1begin, ipart1end);
}
if (ipart2begin != ipart2end) { // second part of FIFO data (used when block wraps around)
processFifo(data, ipart2begin, ipart2end);
}
remainder = m_sampleFifo.remainder();
}
m_source.getLevels(rmsLevel, peakLevel, numSamples);
emit levelChanged(rmsLevel, peakLevel, numSamples);
}
void M17ModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
{
m_channelizer->prefetch(iEnd - iBegin);
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
}
void M17ModBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool M17ModBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureM17ModBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureM17ModBaseband& cfg = (MsgConfigureM17ModBaseband&) cmd;
qDebug() << "M17ModBaseband::handleMessage: MsgConfigureM17ModBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "M17ModBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
return true;
}
else
{
return false;
}
}
void M17ModBaseband::applySettings(const M17ModSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_source.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
audioDeviceManager->removeAudioSource(getAudioFifo());
int audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
if (getAudioSampleRate() != audioSampleRate)
{
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(audioSampleRate);
}
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
audioDeviceManager->addAudioSource(getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
} else {
audioDeviceManager->removeAudioSource(getAudioFifo());
}
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
audioDeviceManager->removeAudioSink(getFeedbackAudioFifo());
audioDeviceManager->addAudioSink(getFeedbackAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (getFeedbackAudioSampleRate() != audioSampleRate) {
m_source.applyFeedbackAudioSampleRate(audioSampleRate);
}
}
m_source.applySettings(settings, force);
m_settings = settings;
}
int M17ModBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}

View File

@ -0,0 +1,101 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MODBASEBAND_H
#define INCLUDE_M17MODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesourcefifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "m17modsource.h"
class UpChannelizer;
class ChannelAPI;
class M17ModBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureM17ModBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const M17ModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureM17ModBaseband* create(const M17ModSettings& settings, bool force)
{
return new MsgConfigureM17ModBaseband(settings, force);
}
private:
M17ModSettings m_settings;
bool m_force;
MsgConfigureM17ModBaseband(const M17ModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
M17ModBaseband();
~M17ModBaseband();
void reset();
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
double getMagSq() const { return m_source.getMagSq(); }
int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
int getFeedbackAudioSampleRate() const { return m_source.getFeedbackAudioSampleRate(); }
int getChannelSampleRate() const;
void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
AudioFifo *getFeedbackAudioFifo() { return m_source.getFeedbackAudioFifo(); }
void setChannel(ChannelAPI *channel);
signals:
/**
* Level changed
* \param rmsLevel RMS level in range 0.0 - 1.0
* \param peakLevel Peak level in range 0.0 - 1.0
* \param numSamples Number of audio samples analyzed
*/
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
private:
SampleSourceFifo m_sampleFifo;
UpChannelizer *m_channelizer;
M17ModSource m_source;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
M17ModSettings m_settings;
QMutex m_mutex;
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
bool handleMessage(const Message& cmd);
void applySettings(const M17ModSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_M17MODBASEBAND_H

View File

@ -0,0 +1,599 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDockWidget>
#include <QMainWindow>
#include <QFileDialog>
#include <QTime>
#include <QDebug>
#include <QRegExp>
#include <QResizeEvent>
#include "device/deviceuiset.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "maincore.h"
#include "ui_m17modgui.h"
#include "m17modgui.h"
M17ModGUI* M17ModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx)
{
M17ModGUI* gui = new M17ModGUI(pluginAPI, deviceUISet, channelTx);
return gui;
}
void M17ModGUI::destroy()
{
delete this;
}
void M17ModGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray M17ModGUI::serialize() const
{
return m_settings.serialize();
}
bool M17ModGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
void M17ModGUI::resizeEvent(QResizeEvent* size)
{
int maxWidth = getRollupContents()->maximumWidth();
int minHeight = getRollupContents()->minimumHeight() + getAdditionalHeight();
resize(width() < maxWidth ? width() : maxWidth, minHeight);
size->accept();
}
bool M17ModGUI::handleMessage(const Message& message)
{
if (M17Mod::MsgReportFileSourceStreamData::match(message))
{
m_recordSampleRate = ((M17Mod::MsgReportFileSourceStreamData&)message).getSampleRate();
m_recordLength = ((M17Mod::MsgReportFileSourceStreamData&)message).getRecordLength();
m_samplesCount = 0;
updateWithStreamData();
return true;
}
else if (M17Mod::MsgReportFileSourceStreamTiming::match(message))
{
m_samplesCount = ((M17Mod::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
updateWithStreamTime();
return true;
}
else if (M17Mod::MsgConfigureM17Mod::match(message))
{
const M17Mod::MsgConfigureM17Mod& cfg = (M17Mod::MsgConfigureM17Mod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
updateAbsoluteCenterFrequency();
return true;
}
else
{
return false;
}
}
void M17ModGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void M17ModGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void M17ModGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySettings();
}
void M17ModGUI::on_rfBW_valueChanged(int value)
{
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_settings.m_rfBandwidth = value * 100.0;
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
applySettings();
}
void M17ModGUI::on_fmDev_valueChanged(int value)
{
ui->fmDevText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(value / 10.0, 0, 'f', 1));
m_settings.m_fmDeviation = value * 200.0;
applySettings();
}
void M17ModGUI::on_volume_valueChanged(int value)
{
ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
m_settings.m_volumeFactor = value / 10.0;
applySettings();
}
void M17ModGUI::on_toneFrequency_valueChanged(int value)
{
ui->toneFrequencyText->setText(QString("%1k").arg(value / 100.0, 0, 'f', 2));
m_settings.m_toneFrequency = value * 10.0;
applySettings();
}
void M17ModGUI::on_channelMute_toggled(bool checked)
{
m_settings.m_channelMute = checked;
applySettings();
}
void M17ModGUI::on_playLoop_toggled(bool checked)
{
m_settings.m_playLoop = checked;
applySettings();
}
void M17ModGUI::on_play_toggled(bool checked)
{
ui->tone->setEnabled(!checked); // release other source inputs
ui->mic->setEnabled(!checked);
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputFile : M17ModSettings::M17ModInputNone;
applySettings();
ui->navTimeSlider->setEnabled(!checked);
m_enableNavTime = !checked;
}
void M17ModGUI::on_tone_toggled(bool checked)
{
ui->play->setEnabled(!checked); // release other source inputs
ui->mic->setEnabled(!checked);
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputTone : M17ModSettings::M17ModInputNone;
applySettings();
}
void M17ModGUI::on_mic_toggled(bool checked)
{
ui->play->setEnabled(!checked); // release other source inputs
ui->tone->setEnabled(!checked); // release other source inputs
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputAudio : M17ModSettings::M17ModInputNone;
applySettings();
}
void M17ModGUI::on_feedbackEnable_toggled(bool checked)
{
m_settings.m_feedbackAudioEnable = checked;
applySettings();
}
void M17ModGUI::on_feedbackVolume_valueChanged(int value)
{
ui->feedbackVolumeText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
m_settings.m_feedbackVolumeFactor = value / 100.0;
applySettings();
}
void M17ModGUI::on_navTimeSlider_valueChanged(int value)
{
if (m_enableNavTime && ((value >= 0) && (value <= 100)))
{
int t_sec = (m_recordLength * value) / 100;
QTime t(0, 0, 0, 0);
t = t.addSecs(t_sec);
M17Mod::MsgConfigureFileSourceSeek* message = M17Mod::MsgConfigureFileSourceSeek::create(value);
m_m17Mod->getInputMessageQueue()->push(message);
}
}
void M17ModGUI::on_showFileDialog_clicked(bool checked)
{
(void) checked;
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog);
if (fileName != "")
{
m_fileName = fileName;
ui->recordFileText->setText(m_fileName);
ui->play->setEnabled(true);
configureFileName();
}
}
void M17ModGUI::configureFileName()
{
qDebug() << "M17ModGUI::configureFileName: " << m_fileName.toStdString().c_str();
M17Mod::MsgConfigureFileSourceName* message = M17Mod::MsgConfigureFileSourceName::create(m_fileName);
m_m17Mod->getInputMessageQueue()->push(message);
}
void M17ModGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySettings();
}
void M17ModGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.setDefaultTitle(m_displayedName);
if (m_deviceUISet->m_deviceMIMOEngine)
{
dialog.setNumberOfStreams(m_m17Mod->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
}
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitle(m_channelMarker.getTitle());
setTitleColor(m_settings.m_rgbColor);
if (m_deviceUISet->m_deviceMIMOEngine)
{
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
updateIndexLabel();
}
applySettings();
}
resetContextMenuType();
}
M17ModGUI::M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::M17ModGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_recordLength(0),
m_recordSampleRate(48000),
m_samplesCount(0),
m_audioSampleRate(-1),
m_feedbackAudioSampleRate(-1),
m_tickCount(0),
m_enableNavTime(false),
m_dcsCodeValidator(QRegExp("[0-7]{1,3}"))
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channeltx/modm17/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_m17Mod = (M17Mod*) channelTx;
m_m17Mod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
CRightClickEnabler *feedbackRightClickEnabler = new CRightClickEnabler(ui->feedbackEnable);
connect(feedbackRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioFeedbackSelect()));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::red);
m_channelMarker.setBandwidth(12500);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("M17 Modulator");
m_channelMarker.setSourceOrSinkStream(false);
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_deviceUISet->addChannelMarker(&m_channelMarker);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
ui->play->setEnabled(false);
ui->play->setChecked(false);
ui->tone->setChecked(false);
ui->mic->setChecked(false);
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_m17Mod->setLevelMeter(ui->volumeMeter);
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setRollupState(&m_rollupState);
displaySettings();
makeUIConnections();
applySettings();
}
M17ModGUI::~M17ModGUI()
{
delete ui;
}
void M17ModGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void M17ModGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
M17Mod::MsgConfigureM17Mod *msg = M17Mod::MsgConfigureM17Mod::create(m_settings, force);
m_m17Mod->getInputMessageQueue()->push(msg);
}
}
void M17ModGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
setTitle(m_channelMarker.getTitle());
updateIndexLabel();
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
ui->fmDevText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(m_settings.m_fmDeviation / 2000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 200.0);
ui->volumeText->setText(QString("%1").arg(m_settings.m_volumeFactor, 0, 'f', 1));
ui->volume->setValue(m_settings.m_volumeFactor * 10.0);
ui->toneFrequencyText->setText(QString("%1k").arg(m_settings.m_toneFrequency / 1000.0, 0, 'f', 2));
ui->toneFrequency->setValue(m_settings.m_toneFrequency / 10.0);
ui->channelMute->setChecked(m_settings.m_channelMute);
ui->playLoop->setChecked(m_settings.m_playLoop);
ui->tone->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->mic->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->play->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->tone->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone);
ui->mic->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio);
ui->play->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile);
ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
}
void M17ModGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void M17ModGUI::enterEvent(QEvent* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void M17ModGUI::audioSelect()
{
qDebug("M17ModGUI::audioSelect");
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void M17ModGUI::audioFeedbackSelect()
{
qDebug("M17ModGUI::audioFeedbackSelect");
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, false); // false for output
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_feedbackAudioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void M17ModGUI::tick()
{
double powDb = CalcDb::dbPower(m_m17Mod->getMagSq());
m_channelPowerDbAvg(powDb);
ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1));
int audioSampleRate = m_m17Mod->getAudioSampleRate();
if (audioSampleRate != m_audioSampleRate)
{
if (audioSampleRate < 0) {
ui->mic->setColor(QColor("red"));
} else {
ui->mic->resetColor();
}
m_audioSampleRate = audioSampleRate;
}
int feedbackAudioSampleRate = m_m17Mod->getFeedbackAudioSampleRate();
if (feedbackAudioSampleRate != m_feedbackAudioSampleRate)
{
if (feedbackAudioSampleRate < 0) {
ui->feedbackEnable->setStyleSheet("QToolButton { background-color : red; }");
} else {
ui->feedbackEnable->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
m_feedbackAudioSampleRate = feedbackAudioSampleRate;
}
if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == M17ModSettings::M17ModInputFile))
{
M17Mod::MsgConfigureFileSourceStreamTiming* message = M17Mod::MsgConfigureFileSourceStreamTiming::create();
m_m17Mod->getInputMessageQueue()->push(message);
}
}
void M17ModGUI::updateWithStreamData()
{
QTime recordLength(0, 0, 0, 0);
recordLength = recordLength.addSecs(m_recordLength);
QString s_time = recordLength.toString("HH:mm:ss");
ui->recordLengthText->setText(s_time);
updateWithStreamTime();
}
void M17ModGUI::updateWithStreamTime()
{
int t_sec = 0;
int t_msec = 0;
if (m_recordSampleRate > 0)
{
t_msec = ((m_samplesCount * 1000) / m_recordSampleRate) % 1000;
t_sec = m_samplesCount / m_recordSampleRate;
}
QTime t(0, 0, 0, 0);
t = t.addSecs(t_sec);
t = t.addMSecs(t_msec);
QString s_timems = t.toString("HH:mm:ss.zzz");
QString s_time = t.toString("HH:mm:ss");
ui->relTimeText->setText(s_timems);
if (!m_enableNavTime)
{
float posRatio = (float) t_sec / (float) m_recordLength;
ui->navTimeSlider->setValue((int) (posRatio * 100.0));
}
}
void M17ModGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &M17ModGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &M17ModGUI::on_rfBW_valueChanged);
QObject::connect(ui->fmDev, &QSlider::valueChanged, this, &M17ModGUI::on_fmDev_valueChanged);
QObject::connect(ui->toneFrequency, &QDial::valueChanged, this, &M17ModGUI::on_toneFrequency_valueChanged);
QObject::connect(ui->volume, &QDial::valueChanged, this, &M17ModGUI::on_volume_valueChanged);
QObject::connect(ui->channelMute, &QToolButton::toggled, this, &M17ModGUI::on_channelMute_toggled);
QObject::connect(ui->tone, &ButtonSwitch::toggled, this, &M17ModGUI::on_tone_toggled);
QObject::connect(ui->mic, &ButtonSwitch::toggled, this, &M17ModGUI::on_mic_toggled);
QObject::connect(ui->play, &ButtonSwitch::toggled, this, &M17ModGUI::on_play_toggled);
QObject::connect(ui->playLoop, &ButtonSwitch::toggled, this, &M17ModGUI::on_playLoop_toggled);
QObject::connect(ui->navTimeSlider, &QSlider::valueChanged, this, &M17ModGUI::on_navTimeSlider_valueChanged);
QObject::connect(ui->showFileDialog, &QPushButton::clicked, this, &M17ModGUI::on_showFileDialog_clicked);
QObject::connect(ui->feedbackEnable, &QToolButton::toggled, this, &M17ModGUI::on_feedbackEnable_toggled);
QObject::connect(ui->feedbackVolume, &QDial::valueChanged, this, &M17ModGUI::on_feedbackVolume_valueChanged);
}
void M17ModGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}

View File

@ -0,0 +1,140 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_
#define PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_
#include <QRegExpValidator>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "m17mod.h"
#include "m17modsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSource;
namespace Ui {
class M17ModGUI;
}
class M17ModGUI : public ChannelGUI {
Q_OBJECT
public:
static M17ModGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; };
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; };
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; };
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; };
virtual QString getTitle() const { return m_settings.m_title; };
virtual QColor getTitleColor() const { return m_settings.m_rgbColor; };
virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; }
virtual bool getHidden() const { return m_settings.m_hidden; }
virtual ChannelMarker& getChannelMarker() { return m_channelMarker; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual void setStreamIndex(int streamIndex) { m_settings.m_streamIndex = streamIndex; }
public slots:
void channelMarkerChangedByCursor();
protected:
void resizeEvent(QResizeEvent* size);
private:
Ui::M17ModGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
M17ModSettings m_settings;
qint64 m_deviceCenterFrequency;
int m_basebandSampleRate;
bool m_doApplySettings;
M17Mod* m_m17Mod;
MovingAverageUtil<double, double, 20> m_channelPowerDbAvg;
QString m_fileName;
quint32 m_recordLength;
int m_recordSampleRate;
int m_samplesCount;
int m_audioSampleRate;
int m_feedbackAudioSampleRate;
std::size_t m_tickCount;
bool m_enableNavTime;
M17ModSettings::M17ModInputAF m_modAFInput;
MessageQueue m_inputMessageQueue;
QRegExpValidator m_dcsCodeValidator;
explicit M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = nullptr);
virtual ~M17ModGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateWithStreamData();
void updateWithStreamTime();
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void handleSourceMessages();
void on_deltaFrequency_changed(qint64 value);
void on_channelSpacingApply_clicked();
void on_rfBW_valueChanged(int value);
void on_fmDev_valueChanged(int value);
void on_toneFrequency_valueChanged(int value);
void on_volume_valueChanged(int value);
void on_channelMute_toggled(bool checked);
void on_tone_toggled(bool checked);
void on_mic_toggled(bool checked);
void on_play_toggled(bool checked);
void on_playLoop_toggled(bool checked);
void on_navTimeSlider_valueChanged(int value);
void on_showFileDialog_clicked(bool checked);
void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void configureFileName();
void audioSelect();
void audioFeedbackSelect();
void tick();
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_ */

View File

@ -0,0 +1,737 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>M17ModGUI</class>
<widget class="RollupContents" name="M17ModGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>278</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>560</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>M17 Modulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>271</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>358</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="deltaFreqPowLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="deltaFrequencyLayout">
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="channelPower">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>-100.0 dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QToolButton" name="channelMute">
<property name="toolTip">
<string>Mute/Unmute channel</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/txon.png</normaloff>
<normalon>:/txoff.png</normalon>:/txon.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fmDeviationLayout">
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>RFBW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rfBW">
<property name="toolTip">
<string>RF bandwidth (kHz)</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>480</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>160</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWText">
<property name="minimumSize">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>25.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
<string>Dev</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="fmDev">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FM peak deviation (kHz)</string>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>+20.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="volumeLayout">
<item>
<widget class="QLabel" name="volLabel">
<property name="text">
<string>Vol</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="volume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio input gain</string>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="volumeText">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Audio input gain value</string>
</property>
<property name="text">
<string>1.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterVU" name="volumeMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="recordFileSelectLayout">
<item>
<widget class="ButtonSwitch" name="tone">
<property name="toolTip">
<string>Tone modulation (1 kHz)</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/carrier.png</normaloff>:/carrier.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="toneFrequency">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Tone frequency</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>250</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="toneFrequencyText">
<property name="minimumSize">
<size>
<width>36</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Tone frequency (kHz)</string>
</property>
<property name="text">
<string>1.00k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="mic">
<property name="toolTip">
<string>Audio input</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/microphone.png</normaloff>:/microphone.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feedbackEnable">
<property name="toolTip">
<string>Left: enable / disable audio feedback - Right: select audio output device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_off.png</normaloff>
<normalon>:/sound_on.png</normalon>:/sound_off.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="feedbackVolume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedbackVolumeText">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="text">
<string>1.00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="fileNameLayout">
<item>
<widget class="QLabel" name="recordFileText">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="playControllLayout">
<item>
<widget class="QPushButton" name="showFileDialog">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Open record file (48 kHz 32 bit float LE mono)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="playLoop">
<property name="toolTip">
<string>Play record file in a loop</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="play">
<property name="toolTip">
<string>Record file play/pause</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/pause.png</normalon>
<disabledoff>:/play.png</disabledoff>
<disabledon>:/play.png</disabledon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="linePlay1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="relTimeText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Record time from start</string>
</property>
<property name="text">
<string>00:00:00.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="linePlay2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="recordLengthText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Total record time</string>
</property>
<property name="text">
<string>00:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_nav">
<item>
<widget class="QSlider" name="navTimeSlider">
<property name="toolTip">
<string>Record file time navigator</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>LevelMeterVU</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "m17modgui.h"
#endif
#include "m17mod.h"
#include "m17modwebapiadapter.h"
#include "m17modplugin.h"
const PluginDescriptor M17ModPlugin::m_pluginDescriptor = {
M17Mod::m_channelId,
QStringLiteral("M17 Modulator"),
QStringLiteral("7.4.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
M17ModPlugin::M17ModPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& M17ModPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void M17ModPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register AM modulator
m_pluginAPI->registerTxChannel(M17Mod::m_channelIdURI, M17Mod::m_channelId, this);
}
void M17ModPlugin::createTxChannel(DeviceAPI *deviceAPI, BasebandSampleSource **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
M17Mod *instance = new M17Mod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* M17ModPlugin::createTxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSource *txChannel) const
{
(void) deviceUISet;
(void) txChannel;
return nullptr;
}
#else
ChannelGUI* M17ModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) const
{
return M17ModGUI::create(m_pluginAPI, deviceUISet, txChannel);
}
#endif
ChannelWebAPIAdapter* M17ModPlugin::createChannelWebAPIAdapter() const
{
return new M17ModWebAPIAdapter();
}

View File

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

View File

@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/ctcssfrequencies.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "m17modsettings.h"
M17ModSettings::M17ModSettings() :
m_channelMarker(nullptr),
m_rollupState(nullptr)
{
resetToDefaults();
}
void M17ModSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 16000.0f;
m_fmDeviation = 10000.0f; //!< full deviation
m_toneFrequency = 1000.0f;
m_volumeFactor = 1.0f;
m_channelMute = false;
m_playLoop = false;
m_rgbColor = QColor(255, 0, 255).rgb();
m_title = "M17 Modulator";
m_modAFInput = M17ModInputAF::M17ModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 0.5f;
m_feedbackAudioEnable = false;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
}
QByteArray M17ModSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeReal(2, m_rfBandwidth);
s.writeReal(4, m_fmDeviation);
s.writeU32(5, m_rgbColor);
s.writeReal(6, m_toneFrequency);
s.writeReal(7, m_volumeFactor);
if (m_channelMarker) {
s.writeBlob(11, m_channelMarker->serialize());
}
s.writeString(12, m_title);
s.writeS32(13, (int) m_modAFInput);
s.writeString(14, m_audioDeviceName);
s.writeBool(15, m_useReverseAPI);
s.writeString(16, m_reverseAPIAddress);
s.writeU32(17, m_reverseAPIPort);
s.writeU32(18, m_reverseAPIDeviceIndex);
s.writeU32(19, m_reverseAPIChannelIndex);
s.writeString(20, m_feedbackAudioDeviceName);
s.writeReal(21, m_feedbackVolumeFactor);
s.writeBool(22, m_feedbackAudioEnable);
s.writeS32(23, m_streamIndex);
if (m_rollupState) {
s.writeBlob(27, m_rollupState->serialize());
}
s.writeS32(28, m_workspaceIndex);
s.writeBlob(29, m_geometryBytes);
s.writeBool(30, m_hidden);
return s.final();
}
bool M17ModSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
d.readS32(1, &tmp, 0);
m_inputFrequencyOffset = tmp;
d.readReal(2, &m_rfBandwidth, 12500.0);
d.readReal(4, &m_fmDeviation, 10000.0);
d.readU32(5, &m_rgbColor);
d.readReal(6, &m_toneFrequency, 1000.0);
d.readReal(7, &m_volumeFactor, 1.0);
d.readBlob(8, &bytetmp);
if (m_channelMarker)
{
d.readBlob(11, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readString(12, &m_title, "M17 Modulator");
d.readS32(13, &tmp, 0);
if ((tmp < 0) || (tmp > (int) M17ModInputAF::M17ModInputTone)) {
m_modAFInput = M17ModInputNone;
} else {
m_modAFInput = (M17ModInputAF) tmp;
}
d.readString(14, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readBool(15, &m_useReverseAPI, false);
d.readString(16, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(17, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(18, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(19, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readString(20, &m_feedbackAudioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readReal(21, &m_feedbackVolumeFactor, 1.0);
d.readBool(22, &m_feedbackAudioEnable, false);
d.readS32(23, &m_streamIndex, 0);
d.readS32(25, &tmp, 0023);
if (m_rollupState)
{
d.readBlob(27, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(28, &m_workspaceIndex, 0);
d.readBlob(29, &m_geometryBytes);
d.readBool(30, &m_hidden, false);
return true;
}
else
{
qDebug() << "NFMModSettings::deserialize: ERROR";
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,74 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_
#define PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_
#include <QByteArray>
#include "dsp/cwkeyersettings.h"
class Serializable;
struct M17ModSettings
{
enum M17ModInputAF
{
M17ModInputNone,
M17ModInputFile,
M17ModInputAudio,
M17ModInputTone
};
qint64 m_inputFrequencyOffset;
float m_rfBandwidth;
float m_fmDeviation;
float m_toneFrequency;
float m_volumeFactor;
bool m_channelMute;
bool m_playLoop;
quint32 m_rgbColor;
QString m_title;
M17ModInputAF m_modAFInput;
QString m_audioDeviceName; //!< This is the audio device you get the audio samples from
QString m_feedbackAudioDeviceName; //!< This is the audio device you send the audio samples to for audio feedback
float m_feedbackVolumeFactor;
bool m_feedbackAudioEnable;
int m_streamIndex;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
Serializable *m_channelMarker;
Serializable *m_rollupState;
M17ModSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_ */

View File

@ -0,0 +1,418 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/datafifo.h"
#include "util/messagequeue.h"
#include "maincore.h"
#include "m17modsource.h"
const int M17ModSource::m_levelNbSamples = 480; // every 10ms
M17ModSource::M17ModSource() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_modPhasor(0.0f),
m_audioSampleRate(48000),
m_audioFifo(12000),
m_feedbackAudioFifo(48000),
m_levelCalcCount(0),
m_peakLevel(0.0f),
m_levelSum(0.0f),
m_ifstream(nullptr),
m_mutex(QMutex::Recursive)
{
m_audioFifo.setLabel("M17ModSource.m_audioFifo");
m_feedbackAudioFifo.setLabel("M17ModSource.m_feedbackAudioFifo");
m_audioBuffer.resize(24000);
m_audioBufferFill = 0;
m_audioReadBuffer.resize(24000);
m_audioReadBufferFill = 0;
m_feedbackAudioBuffer.resize(1<<14);
m_feedbackAudioBufferFill = 0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
m_magsq = 0.0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
M17ModSource::~M17ModSource()
{
}
void M17ModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
{
std::for_each(
begin,
begin + nbSamples,
[this](Sample& s) {
pullOne(s);
}
);
}
void M17ModSource::pullOne(Sample& sample)
{
if (m_settings.m_channelMute)
{
sample.m_real = 0.0f;
sample.m_imag = 0.0f;
return;
}
Complex ci;
if (m_interpolatorDistance > 1.0f) // decimate
{
modulateSample();
while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
else
{
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
m_interpolatorDistanceRemain += m_interpolatorDistance;
ci *= m_carrierNco.nextIQ(); // shift to carrier frequency
double magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
magsq /= (SDR_TX_SCALED*SDR_TX_SCALED);
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
sample.m_real = (FixReal) ci.real();
sample.m_imag = (FixReal) ci.imag();
}
void M17ModSource::prefetch(unsigned int nbSamples)
{
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
pullAudio(nbSamplesAudio);
}
void M17ModSource::pullAudio(unsigned int nbSamplesAudio)
{
QMutexLocker mlock(&m_mutex);
if (nbSamplesAudio > m_audioBuffer.size()) {
m_audioBuffer.resize(nbSamplesAudio);
}
std::copy(&m_audioReadBuffer[0], &m_audioReadBuffer[nbSamplesAudio], &m_audioBuffer[0]);
m_audioBufferFill = 0;
if (m_audioReadBufferFill > nbSamplesAudio) // copy back remaining samples at the start of the read buffer
{
std::copy(&m_audioReadBuffer[nbSamplesAudio], &m_audioReadBuffer[m_audioReadBufferFill], &m_audioReadBuffer[0]);
m_audioReadBufferFill = m_audioReadBufferFill - nbSamplesAudio; // adjust current read buffer fill pointer
}
}
void M17ModSource::modulateSample()
{
Real t1, t;
pullAF(t);
if (m_settings.m_feedbackAudioEnable) {
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
}
calculateLevel(t);
t1 = m_lowpass.filter(t) * 1.2f;
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * t1;
// limit phasor range to ]-pi,pi]
if (m_modPhasor > M_PI) {
m_modPhasor -= (2.0f * M_PI);
}
m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
m_demodBuffer[m_demodBufferFill] = t1 * std::numeric_limits<int16_t>::max();
++m_demodBufferFill;
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<ObjectPipe*> dataPipes;
MainCore::instance()->getDataPipes().getDataPipes(m_channel, "demod", dataPipes);
if (dataPipes.size() > 0)
{
QList<ObjectPipe*>::iterator it = dataPipes.begin();
for (; it != dataPipes.end(); ++it)
{
DataFifo *fifo = qobject_cast<DataFifo*>((*it)->m_element);
if (fifo) {
fifo->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16), DataFifo::DataTypeI16);
}
}
}
m_demodBufferFill = 0;
}
}
void M17ModSource::pullAF(Real& sample)
{
switch (m_settings.m_modAFInput)
{
case M17ModSettings::M17ModInputTone:
sample = m_toneNco.next();
break;
case M17ModSettings::M17ModInputFile:
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
if (m_ifstream && m_ifstream->is_open())
{
if (m_ifstream->eof())
{
if (m_settings.m_playLoop)
{
m_ifstream->clear();
m_ifstream->seekg(0, std::ios::beg);
}
}
if (m_ifstream->eof())
{
sample = 0.0f;
}
else
{
m_ifstream->read(reinterpret_cast<char*>(&sample), sizeof(Real));
sample *= m_settings.m_volumeFactor;
}
}
else
{
sample = 0.0f;
}
break;
case M17ModSettings::M17ModInputAudio:
if (m_audioBufferFill < m_audioBuffer.size())
{
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
m_audioBufferFill++;
}
else
{
unsigned int size = m_audioBuffer.size();
qDebug("NFMModSource::pullAF: starve audio samples: size: %u", size);
sample = ((m_audioBuffer[size-1].l + m_audioBuffer[size-1].r) / 65536.0f) * m_settings.m_volumeFactor;
}
break;
case M17ModSettings::M17ModInputNone:
default:
sample = 0.0f;
break;
}
}
void M17ModSource::pushFeedback(Real sample)
{
Complex c(sample, sample);
Complex ci;
if (m_feedbackInterpolatorDistance < 1.0f) // interpolate
{
while (!m_feedbackInterpolator.interpolate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
else // decimate
{
if (m_feedbackInterpolator.decimate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
}
void M17ModSource::processOneSample(Complex& ci)
{
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].l = ci.real();
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].r = ci.imag();
++m_feedbackAudioBufferFill;
if (m_feedbackAudioBufferFill >= m_feedbackAudioBuffer.size())
{
uint res = m_feedbackAudioFifo.write((const quint8*)&m_feedbackAudioBuffer[0], m_feedbackAudioBufferFill);
if (res != m_feedbackAudioBufferFill)
{
qDebug("M17ModSource::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
m_feedbackAudioFifo.clear();
}
m_feedbackAudioBufferFill = 0;
}
}
void M17ModSource::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
{
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
m_levelSum += sample * sample;
m_levelCalcCount++;
}
else
{
m_rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
m_peakLevelOut = m_peakLevel;
m_peakLevel = 0.0f;
m_levelSum = 0.0f;
m_levelCalcCount = 0;
}
}
void M17ModSource::applyAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("M17ModSource::applyAudioSampleRate: invalid sample rate %d", sampleRate);
return;
}
qDebug("M17ModSource::applyAudioSampleRate: %d", sampleRate);
m_interpolatorDistanceRemain = 0;
m_interpolatorConsumed = false;
m_interpolatorDistance = (Real) sampleRate / (Real) m_channelSampleRate;
m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth);
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
m_audioSampleRate = sampleRate;
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_channel, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue* messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
messageQueue->push(msg);
}
}
}
void M17ModSource::applyFeedbackAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("NFMModSource::applyFeedbackAudioSampleRate: invalid sample rate %d", sampleRate);
return;
}
qDebug("NFMModSource::applyFeedbackAudioSampleRate: %d", sampleRate);
m_feedbackInterpolatorDistanceRemain = 0;
m_feedbackInterpolatorConsumed = false;
m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
m_feedbackAudioSampleRate = sampleRate;
}
void M17ModSource::applySettings(const M17ModSettings& settings, bool force)
{
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_settings.m_rfBandwidth = settings.m_rfBandwidth;
applyAudioSampleRate(m_audioSampleRate);
}
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
connect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
} else {
disconnect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
}
}
m_settings = settings;
}
void M17ModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "M17ModSource::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((channelFrequencyOffset != m_channelFrequencyOffset)
|| (channelSampleRate != m_channelSampleRate) || force)
{
m_carrierNco.setFreq(channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorConsumed = false;
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) channelSampleRate;
m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void M17ModSource::handleAudio()
{
QMutexLocker mlock(&m_mutex);
unsigned int nbRead;
while ((nbRead = m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioReadBuffer[m_audioReadBufferFill]), 4096)) != 0)
{
if (m_audioReadBufferFill + nbRead + 4096 < m_audioReadBuffer.size()) {
m_audioReadBufferFill += nbRead;
}
}
}

View File

@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MODSOURCE_H
#define INCLUDE_M17MODSOURCE_H
#include <QObject>
#include <QMutex>
#include <QVector>
#include <iostream>
#include <fstream>
#include "dsp/channelsamplesource.h"
#include "dsp/nco.h"
#include "dsp/ncof.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "dsp/filterrc.h"
#include "util/movingaverage.h"
#include "audio/audiofifo.h"
#include "m17modsettings.h"
class ChannelAPI;
class M17ModSource : public QObject, public ChannelSampleSource
{
Q_OBJECT
public:
M17ModSource();
virtual ~M17ModSource();
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
virtual void pullOne(Sample& sample);
virtual void prefetch(unsigned int nbSamples);
void setInputFileStream(std::ifstream *ifstream) { m_ifstream = ifstream; }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
AudioFifo *getFeedbackAudioFifo() { return &m_feedbackAudioFifo; }
void applyAudioSampleRate(int sampleRate);
void applyFeedbackAudioSampleRate(int sampleRate);
int getAudioSampleRate() const { return m_audioSampleRate; }
int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
double getMagSq() const { return m_magsq; }
void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
{
rmsLevel = m_rmsLevel;
peakLevel = m_peakLevelOut;
numSamples = m_levelNbSamples;
}
void applySettings(const M17ModSettings& settings, bool force = false);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
private:
int m_channelSampleRate;
int m_channelFrequencyOffset;
M17ModSettings m_settings;
ChannelAPI *m_channel;
NCO m_carrierNco;
NCOF m_toneNco;
float m_modPhasor; //!< baseband modulator phasor
Complex m_modSample;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
bool m_interpolatorConsumed;
Interpolator m_feedbackInterpolator;
Real m_feedbackInterpolatorDistance;
Real m_feedbackInterpolatorDistanceRemain;
bool m_feedbackInterpolatorConsumed;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
Lowpass<Real> m_lowpass;
double m_magsq;
MovingAverageUtil<double, double, 16> m_movingAverage;
int m_audioSampleRate;
AudioVector m_audioBuffer;
unsigned int m_audioBufferFill;
AudioVector m_audioReadBuffer;
unsigned int m_audioReadBufferFill;
AudioFifo m_audioFifo;
int m_feedbackAudioSampleRate;
AudioVector m_feedbackAudioBuffer;
uint m_feedbackAudioBufferFill;
AudioFifo m_feedbackAudioFifo;
quint32 m_levelCalcCount;
qreal m_rmsLevel;
qreal m_peakLevelOut;
Real m_peakLevel;
Real m_levelSum;
std::ifstream *m_ifstream;
QMutex m_mutex;
static const int m_levelNbSamples;
static const float m_preemphasis;
void processOneSample(Complex& ci);
void pullAF(Real& sample);
void pullAudio(unsigned int nbSamples);
void pushFeedback(Real sample);
void calculateLevel(Real& sample);
void modulateSample();
private slots:
void handleAudio();
};
#endif // INCLUDE_M17MODSOURCE_H

View File

@ -0,0 +1,51 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "dsp/cwkeyer.h"
#include "m17mod.h"
#include "m17modwebapiadapter.h"
M17ModWebAPIAdapter::M17ModWebAPIAdapter()
{}
M17ModWebAPIAdapter::~M17ModWebAPIAdapter()
{}
int M17ModWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
response.getM17ModSettings()->init();
M17Mod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int M17ModWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
M17Mod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
M17Mod::webapiFormatChannelSettings(response, m_settings);
return 200;
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MOD_WEBAPIADAPTER_H
#define INCLUDE_M17MOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "m17modsettings.h"
/**
* Standalone API adapter only for the settings
*/
class M17ModWebAPIAdapter : public ChannelWebAPIAdapter {
public:
M17ModWebAPIAdapter();
virtual ~M17ModWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
M17ModSettings m_settings;
};
#endif // INCLUDE_M17MOD_WEBAPIADAPTER_H

View File

@ -3424,6 +3424,9 @@ margin-bottom: 20px;
"M17DemodReport" : {
"$ref" : "#/definitions/M17DemodReport"
},
"M17ModReport" : {
"$ref" : "#/definitions/M17ModReport"
},
"NFMDemodReport" : {
"$ref" : "#/definitions/NFMDemodReport"
},
@ -3579,6 +3582,9 @@ margin-bottom: 20px;
"M17DemodSettings" : {
"$ref" : "#/definitions/M17DemodSettings"
},
"M17ModSettings" : {
"$ref" : "#/definitions/M17ModSettings"
},
"NFMDemodSettings" : {
"$ref" : "#/definitions/NFMDemodSettings"
},
@ -8330,6 +8336,91 @@ margin-bottom: 20px;
}
},
"description" : "M17Demod"
};
defs.M17ModReport = {
"properties" : {
"channelPowerDB" : {
"type" : "number",
"format" : "float",
"description" : "power transmitted in channel (dB)"
},
"audioSampleRate" : {
"type" : "integer"
},
"channelSampleRate" : {
"type" : "integer"
}
},
"description" : "NFMMod"
};
defs.M17ModSettings = {
"properties" : {
"inputFrequencyOffset" : {
"type" : "integer",
"format" : "int64"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float"
},
"fmDeviation" : {
"type" : "number",
"format" : "float"
},
"toneFrequency" : {
"type" : "number",
"format" : "float"
},
"volumeFactor" : {
"type" : "number",
"format" : "float"
},
"channelMute" : {
"type" : "integer"
},
"playLoop" : {
"type" : "integer"
},
"rgbColor" : {
"type" : "integer"
},
"title" : {
"type" : "string"
},
"audioDeviceName" : {
"type" : "string"
},
"modAFInput" : {
"type" : "integer"
},
"streamIndex" : {
"type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
},
"useReverseAPI" : {
"type" : "integer",
"description" : "Synchronize with reverse API (1 for yes, 0 for no)"
},
"reverseAPIAddress" : {
"type" : "string"
},
"reverseAPIPort" : {
"type" : "integer"
},
"reverseAPIDeviceIndex" : {
"type" : "integer"
},
"reverseAPIChannelIndex" : {
"type" : "integer"
},
"channelMarker" : {
"$ref" : "#/definitions/ChannelMarker"
},
"rollupState" : {
"$ref" : "#/definitions/RollupState"
}
},
"description" : "M17Mod"
};
defs.MapActions = {
"properties" : {
@ -56289,7 +56380,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2022-06-05T00:26:14.019+02:00
Generated 2022-06-09T22:25:54.513+02:00
</div>
</div>
</div>

View File

@ -51,6 +51,8 @@ ChannelReport:
$ref: "/doc/swagger/include/FreqTracker.yaml#/FreqTrackerReport"
M17DemodReport:
$ref: "/doc/swagger/include/M17Demod.yaml#/M17DemodReport"
M17ModReport:
$ref: "/doc/swagger/include/M17Mod.yaml#/M17ModReport"
NFMDemodReport:
$ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodReport"
NFMModReport:

View File

@ -69,6 +69,8 @@ ChannelSettings:
$ref: "/doc/swagger/include/IEEE_802_15_4_Mod.yaml#/IEEE_802_15_4_ModSettings"
M17DemodSettings:
$ref: "/doc/swagger/include/M17Demod.yaml#/M17DemodSettings"
M17ModSettings:
$ref: "/doc/swagger/include/M17Mod.yaml#/M17ModSettings"
NFMDemodSettings:
$ref: "/doc/swagger/include/NFMDemod.yaml#/NFMDemodSettings"
NFMModSettings:

View File

@ -0,0 +1,60 @@
M17ModSettings:
description: M17Mod
properties:
inputFrequencyOffset:
type: integer
format: int64
rfBandwidth:
type: number
format: float
fmDeviation:
type: number
format: float
toneFrequency:
type: number
format: float
volumeFactor:
type: number
format: float
channelMute:
type: integer
playLoop:
type: integer
rgbColor:
type: integer
title:
type: string
audioDeviceName:
type: string
modAFInput:
type: integer
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer
channelMarker:
$ref: "/doc/swagger/include/ChannelMarker.yaml#/ChannelMarker"
rollupState:
$ref: "/doc/swagger/include/RollupState.yaml#/RollupState"
M17ModReport:
description: NFMMod
properties:
channelPowerDB:
description: power transmitted in channel (dB)
type: number
format: float
audioSampleRate:
type: integer
channelSampleRate:
type: integer

View File

@ -4516,16 +4516,21 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setInterferometerSettings(new SWGSDRangel::SWGInterferometerSettings());
channelSettings->getInterferometerSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "NFMDemodSettings")
{
channelSettings->setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings());
channelSettings->getNfmDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "M17DemodSettings")
{
channelSettings->setM17DemodSettings(new SWGSDRangel::SWGM17DemodSettings());
channelSettings->getM17DemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "M17ModSettings")
{
channelSettings->setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
channelSettings->getM17ModSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "NFMDemodSettings")
{
channelSettings->setNfmDemodSettings(new SWGSDRangel::SWGNFMDemodSettings());
channelSettings->getNfmDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "NFMModSettings")
{
channelSettings->setNfmModSettings(new SWGSDRangel::SWGNFMModSettings());

View File

@ -48,6 +48,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channeltx.freedvmod", "FreeDVModSettings"},
{"sdrangel.channel.freqtracker", "FreqTrackerSettings"},
{"sdrangel.channel.m17demod", "M17DemodSettings"},
{"sdrangel.channeltx.modm17", "M17ModSettings"},
{"sdrangel.channel.nfmdemod", "NFMDemodSettings"},
{"de.maintech.sdrangelove.channel.nfm", "NFMDemodSettings"}, // remap
{"sdrangel.channeltx.modnfm", "NFMModSettings"},
@ -155,6 +156,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"FreqTracker", "FreqTrackerSettings"},
{"IEEE_802_15_4_Mod", "IEEE_802_15_4_ModSettings"},
{"M17Demod", "M17DemodSettings"},
{"M17Mod", "M17ModSettings"},
{"NFMDemod", "NFMDemodSettings"},
{"NFMMod", "NFMModSettings"},
{"NoiseFigure", "NoiseFigureSettings"},

View File

@ -51,6 +51,8 @@ ChannelReport:
$ref: "http://swgserver:8081/api/swagger/include/FreqTracker.yaml#/FreqTrackerReport"
M17DemodReport:
$ref: "http://swgserver:8081/api/swagger/include/M17Demod.yaml#/M17DemodReport"
M17ModReport:
$ref: "http://swgserver:8081/api/swagger/include/M17Mod.yaml#/M17ModReport"
NFMDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodReport"
NFMModReport:

View File

@ -69,6 +69,8 @@ ChannelSettings:
$ref: "http://swgserver:8081/api/swagger/include/IEEE_802_15_4_Mod.yaml#/IEEE_802_15_4_ModSettings"
M17DemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/M17Demod.yaml#/M17DemodSettings"
M17ModSettings:
$ref: "http://swgserver:8081/api/swagger/include/M17Mod.yaml#/M17ModSettings"
NFMDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/NFMDemod.yaml#/NFMDemodSettings"
NFMModSettings:

View File

@ -0,0 +1,60 @@
M17ModSettings:
description: M17Mod
properties:
inputFrequencyOffset:
type: integer
format: int64
rfBandwidth:
type: number
format: float
fmDeviation:
type: number
format: float
toneFrequency:
type: number
format: float
volumeFactor:
type: number
format: float
channelMute:
type: integer
playLoop:
type: integer
rgbColor:
type: integer
title:
type: string
audioDeviceName:
type: string
modAFInput:
type: integer
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer
channelMarker:
$ref: "http://swgserver:8081/api/swagger/include/ChannelMarker.yaml#/ChannelMarker"
rollupState:
$ref: "http://swgserver:8081/api/swagger/include/RollupState.yaml#/RollupState"
M17ModReport:
description: NFMMod
properties:
channelPowerDB:
description: power transmitted in channel (dB)
type: number
format: float
audioSampleRate:
type: integer
channelSampleRate:
type: integer

View File

@ -3424,6 +3424,9 @@ margin-bottom: 20px;
"M17DemodReport" : {
"$ref" : "#/definitions/M17DemodReport"
},
"M17ModReport" : {
"$ref" : "#/definitions/M17ModReport"
},
"NFMDemodReport" : {
"$ref" : "#/definitions/NFMDemodReport"
},
@ -3579,6 +3582,9 @@ margin-bottom: 20px;
"M17DemodSettings" : {
"$ref" : "#/definitions/M17DemodSettings"
},
"M17ModSettings" : {
"$ref" : "#/definitions/M17ModSettings"
},
"NFMDemodSettings" : {
"$ref" : "#/definitions/NFMDemodSettings"
},
@ -8330,6 +8336,91 @@ margin-bottom: 20px;
}
},
"description" : "M17Demod"
};
defs.M17ModReport = {
"properties" : {
"channelPowerDB" : {
"type" : "number",
"format" : "float",
"description" : "power transmitted in channel (dB)"
},
"audioSampleRate" : {
"type" : "integer"
},
"channelSampleRate" : {
"type" : "integer"
}
},
"description" : "NFMMod"
};
defs.M17ModSettings = {
"properties" : {
"inputFrequencyOffset" : {
"type" : "integer",
"format" : "int64"
},
"rfBandwidth" : {
"type" : "number",
"format" : "float"
},
"fmDeviation" : {
"type" : "number",
"format" : "float"
},
"toneFrequency" : {
"type" : "number",
"format" : "float"
},
"volumeFactor" : {
"type" : "number",
"format" : "float"
},
"channelMute" : {
"type" : "integer"
},
"playLoop" : {
"type" : "integer"
},
"rgbColor" : {
"type" : "integer"
},
"title" : {
"type" : "string"
},
"audioDeviceName" : {
"type" : "string"
},
"modAFInput" : {
"type" : "integer"
},
"streamIndex" : {
"type" : "integer",
"description" : "MIMO channel. Not relevant when connected to SI (single Rx)."
},
"useReverseAPI" : {
"type" : "integer",
"description" : "Synchronize with reverse API (1 for yes, 0 for no)"
},
"reverseAPIAddress" : {
"type" : "string"
},
"reverseAPIPort" : {
"type" : "integer"
},
"reverseAPIDeviceIndex" : {
"type" : "integer"
},
"reverseAPIChannelIndex" : {
"type" : "integer"
},
"channelMarker" : {
"$ref" : "#/definitions/ChannelMarker"
},
"rollupState" : {
"$ref" : "#/definitions/RollupState"
}
},
"description" : "M17Mod"
};
defs.MapActions = {
"properties" : {
@ -56289,7 +56380,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2022-06-05T00:26:14.019+02:00
Generated 2022-06-09T22:25:54.513+02:00
</div>
</div>
</div>

View File

@ -72,6 +72,8 @@ SWGChannelReport::SWGChannelReport() {
m_freq_tracker_report_isSet = false;
m17_demod_report = nullptr;
m_m17_demod_report_isSet = false;
m17_mod_report = nullptr;
m_m17_mod_report_isSet = false;
nfm_demod_report = nullptr;
m_nfm_demod_report_isSet = false;
nfm_mod_report = nullptr;
@ -160,6 +162,8 @@ SWGChannelReport::init() {
m_freq_tracker_report_isSet = false;
m17_demod_report = new SWGM17DemodReport();
m_m17_demod_report_isSet = false;
m17_mod_report = new SWGM17ModReport();
m_m17_mod_report_isSet = false;
nfm_demod_report = new SWGNFMDemodReport();
m_nfm_demod_report_isSet = false;
nfm_mod_report = new SWGNFMModReport();
@ -264,6 +268,9 @@ SWGChannelReport::cleanup() {
if(m17_demod_report != nullptr) {
delete m17_demod_report;
}
if(m17_mod_report != nullptr) {
delete m17_mod_report;
}
if(nfm_demod_report != nullptr) {
delete nfm_demod_report;
}
@ -375,6 +382,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&m17_demod_report, pJson["M17DemodReport"], "SWGM17DemodReport", "SWGM17DemodReport");
::SWGSDRangel::setValue(&m17_mod_report, pJson["M17ModReport"], "SWGM17ModReport", "SWGM17ModReport");
::SWGSDRangel::setValue(&nfm_demod_report, pJson["NFMDemodReport"], "SWGNFMDemodReport", "SWGNFMDemodReport");
::SWGSDRangel::setValue(&nfm_mod_report, pJson["NFMModReport"], "SWGNFMModReport", "SWGNFMModReport");
@ -493,6 +502,9 @@ SWGChannelReport::asJsonObject() {
if((m17_demod_report != nullptr) && (m17_demod_report->isSet())){
toJsonValue(QString("M17DemodReport"), m17_demod_report, obj, QString("SWGM17DemodReport"));
}
if((m17_mod_report != nullptr) && (m17_mod_report->isSet())){
toJsonValue(QString("M17ModReport"), m17_mod_report, obj, QString("SWGM17ModReport"));
}
if((nfm_demod_report != nullptr) && (nfm_demod_report->isSet())){
toJsonValue(QString("NFMDemodReport"), nfm_demod_report, obj, QString("SWGNFMDemodReport"));
}
@ -771,6 +783,16 @@ SWGChannelReport::setM17DemodReport(SWGM17DemodReport* m17_demod_report) {
this->m_m17_demod_report_isSet = true;
}
SWGM17ModReport*
SWGChannelReport::getM17ModReport() {
return m17_mod_report;
}
void
SWGChannelReport::setM17ModReport(SWGM17ModReport* m17_mod_report) {
this->m17_mod_report = m17_mod_report;
this->m_m17_mod_report_isSet = true;
}
SWGNFMDemodReport*
SWGChannelReport::getNfmDemodReport() {
return nfm_demod_report;
@ -1022,6 +1044,9 @@ SWGChannelReport::isSet(){
if(m17_demod_report && m17_demod_report->isSet()){
isObjectUpdated = true; break;
}
if(m17_mod_report && m17_mod_report->isSet()){
isObjectUpdated = true; break;
}
if(nfm_demod_report && nfm_demod_report->isSet()){
isObjectUpdated = true; break;
}

View File

@ -42,6 +42,7 @@
#include "SWGFreqTrackerReport.h"
#include "SWGIEEE_802_15_4_ModReport.h"
#include "SWGM17DemodReport.h"
#include "SWGM17ModReport.h"
#include "SWGNFMDemodReport.h"
#include "SWGNFMModReport.h"
#include "SWGNoiseFigureReport.h"
@ -146,6 +147,9 @@ public:
SWGM17DemodReport* getM17DemodReport();
void setM17DemodReport(SWGM17DemodReport* m17_demod_report);
SWGM17ModReport* getM17ModReport();
void setM17ModReport(SWGM17ModReport* m17_mod_report);
SWGNFMDemodReport* getNfmDemodReport();
void setNfmDemodReport(SWGNFMDemodReport* nfm_demod_report);
@ -270,6 +274,9 @@ private:
SWGM17DemodReport* m17_demod_report;
bool m_m17_demod_report_isSet;
SWGM17ModReport* m17_mod_report;
bool m_m17_mod_report_isSet;
SWGNFMDemodReport* nfm_demod_report;
bool m_nfm_demod_report_isSet;

View File

@ -88,6 +88,8 @@ SWGChannelSettings::SWGChannelSettings() {
m_ieee_802_15_4_mod_settings_isSet = false;
m17_demod_settings = nullptr;
m_m17_demod_settings_isSet = false;
m17_mod_settings = nullptr;
m_m17_mod_settings_isSet = false;
nfm_demod_settings = nullptr;
m_nfm_demod_settings_isSet = false;
nfm_mod_settings = nullptr;
@ -198,6 +200,8 @@ SWGChannelSettings::init() {
m_ieee_802_15_4_mod_settings_isSet = false;
m17_demod_settings = new SWGM17DemodSettings();
m_m17_demod_settings_isSet = false;
m17_mod_settings = new SWGM17ModSettings();
m_m17_mod_settings_isSet = false;
nfm_demod_settings = new SWGNFMDemodSettings();
m_nfm_demod_settings_isSet = false;
nfm_mod_settings = new SWGNFMModSettings();
@ -328,6 +332,9 @@ SWGChannelSettings::cleanup() {
if(m17_demod_settings != nullptr) {
delete m17_demod_settings;
}
if(m17_mod_settings != nullptr) {
delete m17_mod_settings;
}
if(nfm_demod_settings != nullptr) {
delete nfm_demod_settings;
}
@ -464,6 +471,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&m17_demod_settings, pJson["M17DemodSettings"], "SWGM17DemodSettings", "SWGM17DemodSettings");
::SWGSDRangel::setValue(&m17_mod_settings, pJson["M17ModSettings"], "SWGM17ModSettings", "SWGM17ModSettings");
::SWGSDRangel::setValue(&nfm_demod_settings, pJson["NFMDemodSettings"], "SWGNFMDemodSettings", "SWGNFMDemodSettings");
::SWGSDRangel::setValue(&nfm_mod_settings, pJson["NFMModSettings"], "SWGNFMModSettings", "SWGNFMModSettings");
@ -612,6 +621,9 @@ SWGChannelSettings::asJsonObject() {
if((m17_demod_settings != nullptr) && (m17_demod_settings->isSet())){
toJsonValue(QString("M17DemodSettings"), m17_demod_settings, obj, QString("SWGM17DemodSettings"));
}
if((m17_mod_settings != nullptr) && (m17_mod_settings->isSet())){
toJsonValue(QString("M17ModSettings"), m17_mod_settings, obj, QString("SWGM17ModSettings"));
}
if((nfm_demod_settings != nullptr) && (nfm_demod_settings->isSet())){
toJsonValue(QString("NFMDemodSettings"), nfm_demod_settings, obj, QString("SWGNFMDemodSettings"));
}
@ -979,6 +991,16 @@ SWGChannelSettings::setM17DemodSettings(SWGM17DemodSettings* m17_demod_settings)
this->m_m17_demod_settings_isSet = true;
}
SWGM17ModSettings*
SWGChannelSettings::getM17ModSettings() {
return m17_mod_settings;
}
void
SWGChannelSettings::setM17ModSettings(SWGM17ModSettings* m17_mod_settings) {
this->m17_mod_settings = m17_mod_settings;
this->m_m17_mod_settings_isSet = true;
}
SWGNFMDemodSettings*
SWGChannelSettings::getNfmDemodSettings() {
return nfm_demod_settings;
@ -1284,6 +1306,9 @@ SWGChannelSettings::isSet(){
if(m17_demod_settings && m17_demod_settings->isSet()){
isObjectUpdated = true; break;
}
if(m17_mod_settings && m17_mod_settings->isSet()){
isObjectUpdated = true; break;
}
if(nfm_demod_settings && nfm_demod_settings->isSet()){
isObjectUpdated = true; break;
}

View File

@ -50,6 +50,7 @@
#include "SWGLocalSinkSettings.h"
#include "SWGLocalSourceSettings.h"
#include "SWGM17DemodSettings.h"
#include "SWGM17ModSettings.h"
#include "SWGNFMDemodSettings.h"
#include "SWGNFMModSettings.h"
#include "SWGNoiseFigureSettings.h"
@ -179,6 +180,9 @@ public:
SWGM17DemodSettings* getM17DemodSettings();
void setM17DemodSettings(SWGM17DemodSettings* m17_demod_settings);
SWGM17ModSettings* getM17ModSettings();
void setM17ModSettings(SWGM17ModSettings* m17_mod_settings);
SWGNFMDemodSettings* getNfmDemodSettings();
void setNfmDemodSettings(SWGNFMDemodSettings* nfm_demod_settings);
@ -336,6 +340,9 @@ private:
SWGM17DemodSettings* m17_demod_settings;
bool m_m17_demod_settings_isSet;
SWGM17ModSettings* m17_mod_settings;
bool m_m17_mod_settings_isSet;
SWGNFMDemodSettings* nfm_demod_settings;
bool m_nfm_demod_settings_isSet;

View File

@ -0,0 +1,154 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
#include "SWGM17ModReport.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGM17ModReport::SWGM17ModReport(QString* json) {
init();
this->fromJson(*json);
}
SWGM17ModReport::SWGM17ModReport() {
channel_power_db = 0.0f;
m_channel_power_db_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
SWGM17ModReport::~SWGM17ModReport() {
this->cleanup();
}
void
SWGM17ModReport::init() {
channel_power_db = 0.0f;
m_channel_power_db_isSet = false;
audio_sample_rate = 0;
m_audio_sample_rate_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
void
SWGM17ModReport::cleanup() {
}
SWGM17ModReport*
SWGM17ModReport::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGM17ModReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", "");
::SWGSDRangel::setValue(&audio_sample_rate, pJson["audioSampleRate"], "qint32", "");
::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", "");
}
QString
SWGM17ModReport::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGM17ModReport::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(m_channel_power_db_isSet){
obj->insert("channelPowerDB", QJsonValue(channel_power_db));
}
if(m_audio_sample_rate_isSet){
obj->insert("audioSampleRate", QJsonValue(audio_sample_rate));
}
if(m_channel_sample_rate_isSet){
obj->insert("channelSampleRate", QJsonValue(channel_sample_rate));
}
return obj;
}
float
SWGM17ModReport::getChannelPowerDb() {
return channel_power_db;
}
void
SWGM17ModReport::setChannelPowerDb(float channel_power_db) {
this->channel_power_db = channel_power_db;
this->m_channel_power_db_isSet = true;
}
qint32
SWGM17ModReport::getAudioSampleRate() {
return audio_sample_rate;
}
void
SWGM17ModReport::setAudioSampleRate(qint32 audio_sample_rate) {
this->audio_sample_rate = audio_sample_rate;
this->m_audio_sample_rate_isSet = true;
}
qint32
SWGM17ModReport::getChannelSampleRate() {
return channel_sample_rate;
}
void
SWGM17ModReport::setChannelSampleRate(qint32 channel_sample_rate) {
this->channel_sample_rate = channel_sample_rate;
this->m_channel_sample_rate_isSet = true;
}
bool
SWGM17ModReport::isSet(){
bool isObjectUpdated = false;
do{
if(m_channel_power_db_isSet){
isObjectUpdated = true; break;
}
if(m_audio_sample_rate_isSet){
isObjectUpdated = true; break;
}
if(m_channel_sample_rate_isSet){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,70 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/*
* SWGM17ModReport.h
*
* NFMMod
*/
#ifndef SWGM17ModReport_H_
#define SWGM17ModReport_H_
#include <QJsonObject>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGM17ModReport: public SWGObject {
public:
SWGM17ModReport();
SWGM17ModReport(QString* json);
virtual ~SWGM17ModReport();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGM17ModReport* fromJson(QString &jsonString) override;
float getChannelPowerDb();
void setChannelPowerDb(float channel_power_db);
qint32 getAudioSampleRate();
void setAudioSampleRate(qint32 audio_sample_rate);
qint32 getChannelSampleRate();
void setChannelSampleRate(qint32 channel_sample_rate);
virtual bool isSet() override;
private:
float channel_power_db;
bool m_channel_power_db_isSet;
qint32 audio_sample_rate;
bool m_audio_sample_rate_isSet;
qint32 channel_sample_rate;
bool m_channel_sample_rate_isSet;
};
}
#endif /* SWGM17ModReport_H_ */

View File

@ -0,0 +1,532 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
#include "SWGM17ModSettings.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGM17ModSettings::SWGM17ModSettings(QString* json) {
init();
this->fromJson(*json);
}
SWGM17ModSettings::SWGM17ModSettings() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
fm_deviation = 0.0f;
m_fm_deviation_isSet = false;
tone_frequency = 0.0f;
m_tone_frequency_isSet = false;
volume_factor = 0.0f;
m_volume_factor_isSet = false;
channel_mute = 0;
m_channel_mute_isSet = false;
play_loop = 0;
m_play_loop_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
m_title_isSet = false;
audio_device_name = nullptr;
m_audio_device_name_isSet = false;
mod_af_input = 0;
m_mod_af_input_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = nullptr;
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
channel_marker = nullptr;
m_channel_marker_isSet = false;
rollup_state = nullptr;
m_rollup_state_isSet = false;
}
SWGM17ModSettings::~SWGM17ModSettings() {
this->cleanup();
}
void
SWGM17ModSettings::init() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
fm_deviation = 0.0f;
m_fm_deviation_isSet = false;
tone_frequency = 0.0f;
m_tone_frequency_isSet = false;
volume_factor = 0.0f;
m_volume_factor_isSet = false;
channel_mute = 0;
m_channel_mute_isSet = false;
play_loop = 0;
m_play_loop_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
m_title_isSet = false;
audio_device_name = new QString("");
m_audio_device_name_isSet = false;
mod_af_input = 0;
m_mod_af_input_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = new QString("");
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
channel_marker = new SWGChannelMarker();
m_channel_marker_isSet = false;
rollup_state = new SWGRollupState();
m_rollup_state_isSet = false;
}
void
SWGM17ModSettings::cleanup() {
if(title != nullptr) {
delete title;
}
if(audio_device_name != nullptr) {
delete audio_device_name;
}
if(reverse_api_address != nullptr) {
delete reverse_api_address;
}
if(channel_marker != nullptr) {
delete channel_marker;
}
if(rollup_state != nullptr) {
delete rollup_state;
}
}
SWGM17ModSettings*
SWGM17ModSettings::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGM17ModSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", "");
::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", "");
::SWGSDRangel::setValue(&fm_deviation, pJson["fmDeviation"], "float", "");
::SWGSDRangel::setValue(&tone_frequency, pJson["toneFrequency"], "float", "");
::SWGSDRangel::setValue(&volume_factor, pJson["volumeFactor"], "float", "");
::SWGSDRangel::setValue(&channel_mute, pJson["channelMute"], "qint32", "");
::SWGSDRangel::setValue(&play_loop, pJson["playLoop"], "qint32", "");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString");
::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", "");
::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString");
::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", "");
::SWGSDRangel::setValue(&channel_marker, pJson["channelMarker"], "SWGChannelMarker", "SWGChannelMarker");
::SWGSDRangel::setValue(&rollup_state, pJson["rollupState"], "SWGRollupState", "SWGRollupState");
}
QString
SWGM17ModSettings::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGM17ModSettings::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(m_input_frequency_offset_isSet){
obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset));
}
if(m_rf_bandwidth_isSet){
obj->insert("rfBandwidth", QJsonValue(rf_bandwidth));
}
if(m_fm_deviation_isSet){
obj->insert("fmDeviation", QJsonValue(fm_deviation));
}
if(m_tone_frequency_isSet){
obj->insert("toneFrequency", QJsonValue(tone_frequency));
}
if(m_volume_factor_isSet){
obj->insert("volumeFactor", QJsonValue(volume_factor));
}
if(m_channel_mute_isSet){
obj->insert("channelMute", QJsonValue(channel_mute));
}
if(m_play_loop_isSet){
obj->insert("playLoop", QJsonValue(play_loop));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString"));
}
if(audio_device_name != nullptr && *audio_device_name != QString("")){
toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString"));
}
if(m_mod_af_input_isSet){
obj->insert("modAFInput", QJsonValue(mod_af_input));
}
if(m_stream_index_isSet){
obj->insert("streamIndex", QJsonValue(stream_index));
}
if(m_use_reverse_api_isSet){
obj->insert("useReverseAPI", QJsonValue(use_reverse_api));
}
if(reverse_api_address != nullptr && *reverse_api_address != QString("")){
toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString"));
}
if(m_reverse_api_port_isSet){
obj->insert("reverseAPIPort", QJsonValue(reverse_api_port));
}
if(m_reverse_api_device_index_isSet){
obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index));
}
if(m_reverse_api_channel_index_isSet){
obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index));
}
if((channel_marker != nullptr) && (channel_marker->isSet())){
toJsonValue(QString("channelMarker"), channel_marker, obj, QString("SWGChannelMarker"));
}
if((rollup_state != nullptr) && (rollup_state->isSet())){
toJsonValue(QString("rollupState"), rollup_state, obj, QString("SWGRollupState"));
}
return obj;
}
qint64
SWGM17ModSettings::getInputFrequencyOffset() {
return input_frequency_offset;
}
void
SWGM17ModSettings::setInputFrequencyOffset(qint64 input_frequency_offset) {
this->input_frequency_offset = input_frequency_offset;
this->m_input_frequency_offset_isSet = true;
}
float
SWGM17ModSettings::getRfBandwidth() {
return rf_bandwidth;
}
void
SWGM17ModSettings::setRfBandwidth(float rf_bandwidth) {
this->rf_bandwidth = rf_bandwidth;
this->m_rf_bandwidth_isSet = true;
}
float
SWGM17ModSettings::getFmDeviation() {
return fm_deviation;
}
void
SWGM17ModSettings::setFmDeviation(float fm_deviation) {
this->fm_deviation = fm_deviation;
this->m_fm_deviation_isSet = true;
}
float
SWGM17ModSettings::getToneFrequency() {
return tone_frequency;
}
void
SWGM17ModSettings::setToneFrequency(float tone_frequency) {
this->tone_frequency = tone_frequency;
this->m_tone_frequency_isSet = true;
}
float
SWGM17ModSettings::getVolumeFactor() {
return volume_factor;
}
void
SWGM17ModSettings::setVolumeFactor(float volume_factor) {
this->volume_factor = volume_factor;
this->m_volume_factor_isSet = true;
}
qint32
SWGM17ModSettings::getChannelMute() {
return channel_mute;
}
void
SWGM17ModSettings::setChannelMute(qint32 channel_mute) {
this->channel_mute = channel_mute;
this->m_channel_mute_isSet = true;
}
qint32
SWGM17ModSettings::getPlayLoop() {
return play_loop;
}
void
SWGM17ModSettings::setPlayLoop(qint32 play_loop) {
this->play_loop = play_loop;
this->m_play_loop_isSet = true;
}
qint32
SWGM17ModSettings::getRgbColor() {
return rgb_color;
}
void
SWGM17ModSettings::setRgbColor(qint32 rgb_color) {
this->rgb_color = rgb_color;
this->m_rgb_color_isSet = true;
}
QString*
SWGM17ModSettings::getTitle() {
return title;
}
void
SWGM17ModSettings::setTitle(QString* title) {
this->title = title;
this->m_title_isSet = true;
}
QString*
SWGM17ModSettings::getAudioDeviceName() {
return audio_device_name;
}
void
SWGM17ModSettings::setAudioDeviceName(QString* audio_device_name) {
this->audio_device_name = audio_device_name;
this->m_audio_device_name_isSet = true;
}
qint32
SWGM17ModSettings::getModAfInput() {
return mod_af_input;
}
void
SWGM17ModSettings::setModAfInput(qint32 mod_af_input) {
this->mod_af_input = mod_af_input;
this->m_mod_af_input_isSet = true;
}
qint32
SWGM17ModSettings::getStreamIndex() {
return stream_index;
}
void
SWGM17ModSettings::setStreamIndex(qint32 stream_index) {
this->stream_index = stream_index;
this->m_stream_index_isSet = true;
}
qint32
SWGM17ModSettings::getUseReverseApi() {
return use_reverse_api;
}
void
SWGM17ModSettings::setUseReverseApi(qint32 use_reverse_api) {
this->use_reverse_api = use_reverse_api;
this->m_use_reverse_api_isSet = true;
}
QString*
SWGM17ModSettings::getReverseApiAddress() {
return reverse_api_address;
}
void
SWGM17ModSettings::setReverseApiAddress(QString* reverse_api_address) {
this->reverse_api_address = reverse_api_address;
this->m_reverse_api_address_isSet = true;
}
qint32
SWGM17ModSettings::getReverseApiPort() {
return reverse_api_port;
}
void
SWGM17ModSettings::setReverseApiPort(qint32 reverse_api_port) {
this->reverse_api_port = reverse_api_port;
this->m_reverse_api_port_isSet = true;
}
qint32
SWGM17ModSettings::getReverseApiDeviceIndex() {
return reverse_api_device_index;
}
void
SWGM17ModSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) {
this->reverse_api_device_index = reverse_api_device_index;
this->m_reverse_api_device_index_isSet = true;
}
qint32
SWGM17ModSettings::getReverseApiChannelIndex() {
return reverse_api_channel_index;
}
void
SWGM17ModSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) {
this->reverse_api_channel_index = reverse_api_channel_index;
this->m_reverse_api_channel_index_isSet = true;
}
SWGChannelMarker*
SWGM17ModSettings::getChannelMarker() {
return channel_marker;
}
void
SWGM17ModSettings::setChannelMarker(SWGChannelMarker* channel_marker) {
this->channel_marker = channel_marker;
this->m_channel_marker_isSet = true;
}
SWGRollupState*
SWGM17ModSettings::getRollupState() {
return rollup_state;
}
void
SWGM17ModSettings::setRollupState(SWGRollupState* rollup_state) {
this->rollup_state = rollup_state;
this->m_rollup_state_isSet = true;
}
bool
SWGM17ModSettings::isSet(){
bool isObjectUpdated = false;
do{
if(m_input_frequency_offset_isSet){
isObjectUpdated = true; break;
}
if(m_rf_bandwidth_isSet){
isObjectUpdated = true; break;
}
if(m_fm_deviation_isSet){
isObjectUpdated = true; break;
}
if(m_tone_frequency_isSet){
isObjectUpdated = true; break;
}
if(m_volume_factor_isSet){
isObjectUpdated = true; break;
}
if(m_channel_mute_isSet){
isObjectUpdated = true; break;
}
if(m_play_loop_isSet){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}
if(title && *title != QString("")){
isObjectUpdated = true; break;
}
if(audio_device_name && *audio_device_name != QString("")){
isObjectUpdated = true; break;
}
if(m_mod_af_input_isSet){
isObjectUpdated = true; break;
}
if(m_stream_index_isSet){
isObjectUpdated = true; break;
}
if(m_use_reverse_api_isSet){
isObjectUpdated = true; break;
}
if(reverse_api_address && *reverse_api_address != QString("")){
isObjectUpdated = true; break;
}
if(m_reverse_api_port_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_device_index_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_channel_index_isSet){
isObjectUpdated = true; break;
}
if(channel_marker && channel_marker->isSet()){
isObjectUpdated = true; break;
}
if(rollup_state && rollup_state->isSet()){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,169 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/*
* SWGM17ModSettings.h
*
* M17Mod
*/
#ifndef SWGM17ModSettings_H_
#define SWGM17ModSettings_H_
#include <QJsonObject>
#include "SWGChannelMarker.h"
#include "SWGRollupState.h"
#include <QString>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGM17ModSettings: public SWGObject {
public:
SWGM17ModSettings();
SWGM17ModSettings(QString* json);
virtual ~SWGM17ModSettings();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGM17ModSettings* fromJson(QString &jsonString) override;
qint64 getInputFrequencyOffset();
void setInputFrequencyOffset(qint64 input_frequency_offset);
float getRfBandwidth();
void setRfBandwidth(float rf_bandwidth);
float getFmDeviation();
void setFmDeviation(float fm_deviation);
float getToneFrequency();
void setToneFrequency(float tone_frequency);
float getVolumeFactor();
void setVolumeFactor(float volume_factor);
qint32 getChannelMute();
void setChannelMute(qint32 channel_mute);
qint32 getPlayLoop();
void setPlayLoop(qint32 play_loop);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
QString* getTitle();
void setTitle(QString* title);
QString* getAudioDeviceName();
void setAudioDeviceName(QString* audio_device_name);
qint32 getModAfInput();
void setModAfInput(qint32 mod_af_input);
qint32 getStreamIndex();
void setStreamIndex(qint32 stream_index);
qint32 getUseReverseApi();
void setUseReverseApi(qint32 use_reverse_api);
QString* getReverseApiAddress();
void setReverseApiAddress(QString* reverse_api_address);
qint32 getReverseApiPort();
void setReverseApiPort(qint32 reverse_api_port);
qint32 getReverseApiDeviceIndex();
void setReverseApiDeviceIndex(qint32 reverse_api_device_index);
qint32 getReverseApiChannelIndex();
void setReverseApiChannelIndex(qint32 reverse_api_channel_index);
SWGChannelMarker* getChannelMarker();
void setChannelMarker(SWGChannelMarker* channel_marker);
SWGRollupState* getRollupState();
void setRollupState(SWGRollupState* rollup_state);
virtual bool isSet() override;
private:
qint64 input_frequency_offset;
bool m_input_frequency_offset_isSet;
float rf_bandwidth;
bool m_rf_bandwidth_isSet;
float fm_deviation;
bool m_fm_deviation_isSet;
float tone_frequency;
bool m_tone_frequency_isSet;
float volume_factor;
bool m_volume_factor_isSet;
qint32 channel_mute;
bool m_channel_mute_isSet;
qint32 play_loop;
bool m_play_loop_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;
QString* title;
bool m_title_isSet;
QString* audio_device_name;
bool m_audio_device_name_isSet;
qint32 mod_af_input;
bool m_mod_af_input_isSet;
qint32 stream_index;
bool m_stream_index_isSet;
qint32 use_reverse_api;
bool m_use_reverse_api_isSet;
QString* reverse_api_address;
bool m_reverse_api_address_isSet;
qint32 reverse_api_port;
bool m_reverse_api_port_isSet;
qint32 reverse_api_device_index;
bool m_reverse_api_device_index_isSet;
qint32 reverse_api_channel_index;
bool m_reverse_api_channel_index_isSet;
SWGChannelMarker* channel_marker;
bool m_channel_marker_isSet;
SWGRollupState* rollup_state;
bool m_rollup_state_isSet;
};
}
#endif /* SWGM17ModSettings_H_ */

View File

@ -185,6 +185,8 @@
#include "SWGLoggingInfo.h"
#include "SWGM17DemodReport.h"
#include "SWGM17DemodSettings.h"
#include "SWGM17ModReport.h"
#include "SWGM17ModSettings.h"
#include "SWGMapActions.h"
#include "SWGMapAnimation.h"
#include "SWGMapCoordinate.h"
@ -1192,6 +1194,16 @@ namespace SWGSDRangel {
obj->init();
return obj;
}
if(QString("SWGM17ModReport").compare(type) == 0) {
SWGM17ModReport *obj = new SWGM17ModReport();
obj->init();
return obj;
}
if(QString("SWGM17ModSettings").compare(type) == 0) {
SWGM17ModSettings *obj = new SWGM17ModSettings();
obj->init();
return obj;
}
if(QString("SWGMapActions").compare(type) == 0) {
SWGMapActions *obj = new SWGMapActions();
obj->init();