mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-07 16:34:45 -04:00
M17 Modulator: copy of NFM modulator
This commit is contained in:
@@ -61,4 +61,5 @@ endif()
|
||||
|
||||
if (ENABLE_CHANNELTX_MODFREEDV AND CODEC2_FOUND)
|
||||
add_subdirectory(modfreedv)
|
||||
add_subdirectory(modm17)
|
||||
endif(CODEC2_FOUND)
|
||||
|
||||
@@ -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()
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_ */
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user