1
0
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:
f4exb
2022-06-10 01:42:24 +02:00
parent 424d072f0c
commit 7e8e1c12fa
36 changed files with 5301 additions and 7 deletions
+1
View File
@@ -61,4 +61,5 @@ endif()
if (ENABLE_CHANNELTX_MODFREEDV AND CODEC2_FOUND)
add_subdirectory(modfreedv)
add_subdirectory(modm17)
endif(CODEC2_FOUND)
+64
View File
@@ -0,0 +1,64 @@
project(modm17)
set(modm17_SOURCES
m17mod.cpp
m17modbaseband.cpp
m17modsource.cpp
m17modplugin.cpp
m17modsettings.cpp
m17modwebapiadapter.cpp
)
set(modm17_HEADERS
m17mod.h
m17modbaseband.h
m17modsource.h
m17modplugin.h
m17modsettings.h
m17modwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(modm17_SOURCES
${modm17_SOURCES}
m17modgui.cpp
m17modgui.ui
)
set(modm17_HEADERS
${modm17_HEADERS}
m17modgui.h
)
set(TARGET_NAME modm17)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME modm17srv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${modm17_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()
+766
View File
@@ -0,0 +1,766 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QTime>
#include <QDebug>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGChannelReport.h"
#include "SWGNFMModReport.h"
#include <stdio.h>
#include <complex.h>
#include <algorithm>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
#include "m17modbaseband.h"
#include "m17mod.h"
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureM17Mod, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceName, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceSeek, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgConfigureFileSourceStreamTiming, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgReportFileSourceStreamData, Message)
MESSAGE_CLASS_DEFINITION(M17Mod::MsgReportFileSourceStreamTiming, Message)
const char* const M17Mod::m_channelIdURI = "sdrangel.channeltx.modm17";
const char* const M17Mod::m_channelId = "M17Mod";
M17Mod::M17Mod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
m_deviceAPI(deviceAPI),
m_settingsMutex(QMutex::Recursive),
m_fileSize(0),
m_recordLength(0),
m_sampleRate(48000)
{
setObjectName(m_channelId);
m_thread = new QThread(this);
m_basebandSource = new M17ModBaseband();
m_basebandSource->setInputFileStream(&m_ifstream);
m_basebandSource->setChannel(this);
m_basebandSource->moveToThread(m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSource(this);
m_deviceAPI->addChannelSourceAPI(this);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&M17Mod::networkManagerFinished
);
}
M17Mod::~M17Mod()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&M17Mod::networkManagerFinished
);
delete m_networkManager;
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this);
delete m_basebandSource;
delete m_thread;
}
void M17Mod::setDeviceAPI(DeviceAPI *deviceAPI)
{
if (deviceAPI != m_deviceAPI)
{
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this);
m_deviceAPI = deviceAPI;
m_deviceAPI->addChannelSource(this);
m_deviceAPI->addChannelSinkAPI(this);
}
}
void M17Mod::start()
{
qDebug("M17Mod::start");
m_basebandSource->reset();
m_thread->start();
}
void M17Mod::stop()
{
qDebug("M17Mod::stop");
m_thread->exit();
m_thread->wait();
}
void M17Mod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
{
m_basebandSource->pull(begin, nbSamples);
}
void M17Mod::setCenterFrequency(qint64 frequency)
{
M17ModSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureM17Mod *msgToGUI = MsgConfigureM17Mod::create(settings, false);
m_guiMessageQueue->push(msgToGUI);
}
}
bool M17Mod::handleMessage(const Message& cmd)
{
if (MsgConfigureM17Mod::match(cmd))
{
MsgConfigureM17Mod& cfg = (MsgConfigureM17Mod&) cmd;
qDebug() << "M17Mod::handleMessage: MsgConfigureM17Mod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgConfigureFileSourceName::match(cmd))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
m_fileName = conf.getFileName();
openFileStream();
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceName:"
<< " m_fileName: " << m_fileName;
return true;
}
else if (MsgConfigureFileSourceSeek::match(cmd))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
int seekPercentage = conf.getPercentage();
seekFileStream(seekPercentage);
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceSeek:"
<< " seekPercentage: " << seekPercentage;
return true;
}
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
{
std::size_t samplesCount;
if (m_ifstream.eof()) {
samplesCount = m_fileSize / sizeof(Real);
} else {
samplesCount = m_ifstream.tellg() / sizeof(Real);
}
MsgReportFileSourceStreamTiming *report;
report = MsgReportFileSourceStreamTiming::create(samplesCount);
getMessageQueueToGUI()->push(report);
return true;
}
else if (MsgConfigureFileSourceName::match(cmd))
{
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
m_fileName = conf.getFileName();
openFileStream();
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceName:"
<< " m_fileName: " << m_fileName;
return true;
}
else if (MsgConfigureFileSourceSeek::match(cmd))
{
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
int seekPercentage = conf.getPercentage();
seekFileStream(seekPercentage);
qDebug() << "M17Mod::handleMessage: MsgConfigureFileSourceSeek:"
<< " seekPercentage: " << seekPercentage;
return true;
}
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
{
std::size_t samplesCount;
if (m_ifstream.eof()) {
samplesCount = m_fileSize / sizeof(Real);
} else {
samplesCount = m_ifstream.tellg() / sizeof(Real);
}
MsgReportFileSourceStreamTiming *report;
report = MsgReportFileSourceStreamTiming::create(samplesCount);
getMessageQueueToGUI()->push(report);
return true;
}
else if (DSPSignalNotification::match(cmd))
{
// Forward to the source
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "M17Mod::handleMessage: DSPSignalNotification";
m_basebandSource->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(new DSPSignalNotification(notif));
}
return true;
}
else if (MainCore::MsgChannelDemodQuery::match(cmd))
{
qDebug() << "M17Mod::handleMessage: MsgChannelDemodQuery";
sendSampleRateToDemodAnalyzer();
return true;
}
else
{
return false;
}
}
void M17Mod::openFileStream()
{
if (m_ifstream.is_open()) {
m_ifstream.close();
}
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
m_fileSize = m_ifstream.tellg();
m_ifstream.seekg(0,std::ios_base::beg);
m_sampleRate = 48000; // fixed rate
m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate);
qDebug() << "M17Mod::openFileStream: " << m_fileName.toStdString().c_str()
<< " fileSize: " << m_fileSize << "bytes"
<< " length: " << m_recordLength << " seconds";
MsgReportFileSourceStreamData *report;
report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength);
getMessageQueueToGUI()->push(report);
}
void M17Mod::seekFileStream(int seekPercentage)
{
QMutexLocker mutexLocker(&m_settingsMutex);
if (m_ifstream.is_open())
{
int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate;
seekPoint *= sizeof(Real);
m_ifstream.clear();
m_ifstream.seekg(seekPoint, std::ios::beg);
}
}
void M17Mod::applySettings(const M17ModSettings& settings, bool force)
{
qDebug() << "M17Mod::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fmDeviation: " << settings.m_fmDeviation
<< " m_volumeFactor: " << settings.m_volumeFactor
<< " m_toneFrequency: " << settings.m_toneFrequency
<< " m_channelMute: " << settings.m_channelMute
<< " m_playLoop: " << settings.m_playLoop
<< " m_modAFInput " << settings.m_modAFInput
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIAddress: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
}
if ((settings.m_volumeFactor != m_settings.m_volumeFactor) || force) {
reverseAPIKeys.append("volumeFactor");
}
if ((settings.m_channelMute != m_settings.m_channelMute) || force) {
reverseAPIKeys.append("channelMute");
}
if ((settings.m_playLoop != m_settings.m_playLoop) || force) {
reverseAPIKeys.append("playLoop");
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) {
reverseAPIKeys.append("modAFInput");
}
if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
reverseAPIKeys.append("toneFrequency");
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
reverseAPIKeys.append("audioDeviceName");
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force) {
reverseAPIKeys.append("feedbackAudioDeviceName");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSource(this, settings.m_streamIndex);
m_deviceAPI->addChannelSourceAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
M17ModBaseband::MsgConfigureM17ModBaseband *msg = M17ModBaseband::MsgConfigureM17ModBaseband::create(settings, force);
m_basebandSource->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "settings", pipes);
if (pipes.size() > 0) {
sendChannelSettings(pipes, reverseAPIKeys, settings, force);
}
m_settings = settings;
}
QByteArray M17Mod::serialize() const
{
return m_settings.serialize();
}
bool M17Mod::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureM17Mod *msg = MsgConfigureM17Mod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return success;
}
void M17Mod::sendSampleRateToDemodAnalyzer()
{
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue* messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
getAudioSampleRate()
);
messageQueue->push(msg);
}
}
}
int M17Mod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
response.getM17ModSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int M17Mod::webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage)
{
(void) errorMessage;
response.setIndex(m_settings.m_workspaceIndex);
return 200;
}
int M17Mod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
M17ModSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureM17Mod *msg = MsgConfigureM17Mod::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureM17Mod *msgToGUI = MsgConfigureM17Mod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void M17Mod::webapiUpdateChannelSettings(
M17ModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("channelMute")) {
settings.m_channelMute = response.getM17ModSettings()->getChannelMute() != 0;
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getM17ModSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getM17ModSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("modAFInput")) {
settings.m_modAFInput = (M17ModSettings::M17ModInputAF) response.getM17ModSettings()->getModAfInput();
}
if (channelSettingsKeys.contains("playLoop")) {
settings.m_playLoop = response.getM17ModSettings()->getPlayLoop() != 0;
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getM17ModSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getM17ModSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getM17ModSettings()->getTitle();
}
if (channelSettingsKeys.contains("toneFrequency")) {
settings.m_toneFrequency = response.getM17ModSettings()->getToneFrequency();
}
if (channelSettingsKeys.contains("volumeFactor")) {
settings.m_volumeFactor = response.getM17ModSettings()->getVolumeFactor();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getM17ModSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getM17ModSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getM17ModSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getM17ModSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getM17ModSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getNfmModSettings()->getReverseApiChannelIndex();
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getM17ModSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getM17ModSettings()->getRollupState());
}
}
int M17Mod::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setM17ModReport(new SWGSDRangel::SWGM17ModReport());
response.getM17ModReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void M17Mod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const M17ModSettings& settings)
{
response.getM17ModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0);
response.getM17ModSettings()->setFmDeviation(settings.m_fmDeviation);
response.getM17ModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getM17ModSettings()->setModAfInput((int) settings.m_modAFInput);
response.getM17ModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0);
response.getM17ModSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getM17ModSettings()->setRgbColor(settings.m_rgbColor);
if (response.getM17ModSettings()->getTitle()) {
*response.getM17ModSettings()->getTitle() = settings.m_title;
} else {
response.getM17ModSettings()->setTitle(new QString(settings.m_title));
}
response.getM17ModSettings()->setToneFrequency(settings.m_toneFrequency);
response.getM17ModSettings()->setVolumeFactor(settings.m_volumeFactor);
if (response.getM17ModSettings()->getAudioDeviceName()) {
*response.getM17ModSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
} else {
response.getM17ModSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
response.getM17ModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getM17ModSettings()->getReverseApiAddress()) {
*response.getM17ModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getM17ModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getM17ModSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getM17ModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getM17ModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_channelMarker)
{
if (response.getM17ModSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getM17ModSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getM17ModSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getM17ModSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getM17ModSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getM17ModSettings()->setRollupState(swgRollupState);
}
}
}
void M17Mod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getM17ModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
response.getM17ModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
response.getM17ModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
}
void M17Mod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const M17ModSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void M17Mod::sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const M17ModSettings& settings,
bool force)
{
for (const auto& pipe : pipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
if (messageQueue)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
MainCore::MsgChannelSettings *msg = MainCore::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
messageQueue->push(msg);
}
}
}
void M17Mod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const M17ModSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(1); // single source (Tx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString(m_channelId));
swgChannelSettings->setM17ModSettings(new SWGSDRangel::SWGM17ModSettings());
SWGSDRangel::SWGM17ModSettings *swgM17ModSettings = swgChannelSettings->getM17ModSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("channelMute") || force) {
swgM17ModSettings->setChannelMute(settings.m_channelMute ? 1 : 0);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgM17ModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("modAFInput") || force) {
swgM17ModSettings->setModAfInput((int) settings.m_modAFInput);
}
if (channelSettingsKeys.contains("audioDeviceName") || force) {
swgM17ModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
}
if (channelSettingsKeys.contains("playLoop") || force) {
swgM17ModSettings->setPlayLoop(settings.m_playLoop ? 1 : 0);
}
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgM17ModSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgM17ModSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgM17ModSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgM17ModSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("toneFrequency") || force) {
swgM17ModSettings->setToneFrequency(settings.m_toneFrequency);
}
if (channelSettingsKeys.contains("volumeFactor") || force) {
swgM17ModSettings->setVolumeFactor(settings.m_volumeFactor);
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgM17ModSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgM17ModSettings->setChannelMarker(swgChannelMarker);
}
if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force))
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
swgM17ModSettings->setRollupState(swgRollupState);
}
}
void M17Mod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "M17Mod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("M17Mod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
double M17Mod::getMagSq() const
{
return m_basebandSource->getMagSq();
}
void M17Mod::setLevelMeter(QObject *levelMeter)
{
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
}
uint32_t M17Mod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSinkStreams();
}
int M17Mod::getAudioSampleRate() const
{
return m_basebandSource->getAudioSampleRate();
}
int M17Mod::getFeedbackAudioSampleRate() const
{
return m_basebandSource->getFeedbackAudioSampleRate();
}
+288
View File
@@ -0,0 +1,288 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MOD_H_
#define PLUGINS_CHANNELTX_MODM17_M17MOD_H_
#include <vector>
#include <iostream>
#include <fstream>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesource.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "m17modsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class M17ModBaseband;
class ObjectPipe;
class M17Mod : public BasebandSampleSource, public ChannelAPI {
public:
class MsgConfigureM17Mod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const M17ModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureM17Mod* create(const M17ModSettings& settings, bool force)
{
return new MsgConfigureM17Mod(settings, force);
}
private:
M17ModSettings m_settings;
bool m_force;
MsgConfigureM17Mod(const M17ModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgConfigureFileSourceName : public Message
{
MESSAGE_CLASS_DECLARATION
public:
const QString& getFileName() const { return m_fileName; }
static MsgConfigureFileSourceName* create(const QString& fileName)
{
return new MsgConfigureFileSourceName(fileName);
}
private:
QString m_fileName;
MsgConfigureFileSourceName(const QString& fileName) :
Message(),
m_fileName(fileName)
{ }
};
class MsgConfigureFileSourceSeek : public Message
{
MESSAGE_CLASS_DECLARATION
public:
int getPercentage() const { return m_seekPercentage; }
static MsgConfigureFileSourceSeek* create(int seekPercentage)
{
return new MsgConfigureFileSourceSeek(seekPercentage);
}
protected:
int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
MsgConfigureFileSourceSeek(int seekPercentage) :
Message(),
m_seekPercentage(seekPercentage)
{ }
};
class MsgConfigureFileSourceStreamTiming : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgConfigureFileSourceStreamTiming* create()
{
return new MsgConfigureFileSourceStreamTiming();
}
private:
MsgConfigureFileSourceStreamTiming() :
Message()
{ }
};
class MsgReportFileSourceStreamTiming : public Message
{
MESSAGE_CLASS_DECLARATION
public:
std::size_t getSamplesCount() const { return m_samplesCount; }
static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount)
{
return new MsgReportFileSourceStreamTiming(samplesCount);
}
protected:
std::size_t m_samplesCount;
MsgReportFileSourceStreamTiming(std::size_t samplesCount) :
Message(),
m_samplesCount(samplesCount)
{ }
};
class MsgReportFileSourceStreamData : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSampleRate() const { return m_sampleRate; }
quint32 getRecordLength() const { return m_recordLength; }
static MsgReportFileSourceStreamData* create(int sampleRate,
quint32 recordLength)
{
return new MsgReportFileSourceStreamData(sampleRate, recordLength);
}
protected:
int m_sampleRate;
quint32 m_recordLength;
MsgReportFileSourceStreamData(int sampleRate,
quint32 recordLength) :
Message(),
m_sampleRate(sampleRate),
m_recordLength(recordLength)
{ }
};
//=================================================================
M17Mod(DeviceAPI *deviceAPI);
virtual ~M17Mod();
virtual void destroy() { delete this; }
virtual void setDeviceAPI(DeviceAPI *deviceAPI);
virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; }
virtual void start();
virtual void stop();
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSourceName() { return objectName(); }
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_settings.m_inputFrequencyOffset;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const M17ModSettings& settings);
static void webapiUpdateChannelSettings(
M17ModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
double getMagSq() const;
void setLevelMeter(QObject *levelMeter);
uint32_t getNumberOfDeviceStreams() const;
int getAudioSampleRate() const;
int getFeedbackAudioSampleRate() const;
static const char* const m_channelIdURI;
static const char* const m_channelId;
private:
enum RateState {
RSInitialFill,
RSRunning
};
DeviceAPI* m_deviceAPI;
QThread *m_thread;
M17ModBaseband* m_basebandSource;
M17ModSettings m_settings;
SampleVector m_sampleBuffer;
QMutex m_settingsMutex;
std::ifstream m_ifstream;
QString m_fileName;
quint64 m_fileSize; //!< raw file size (bytes)
quint32 m_recordLength; //!< record length in seconds computed from file size
int m_sampleRate;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
virtual bool handleMessage(const Message& cmd);
void applySettings(const M17ModSettings& settings, bool force = false);
void sendSampleRateToDemodAnalyzer();
void openFileStream();
void seekFileStream(int seekPercentage);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const M17ModSettings& settings, bool force);
void sendChannelSettings(
const QList<ObjectPipe*>& pipes,
QList<QString>& channelSettingsKeys,
const M17ModSettings& settings,
bool force
);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const M17ModSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MOD_H_ */
+232
View File
@@ -0,0 +1,232 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/upchannelizer.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "m17modbaseband.h"
MESSAGE_CLASS_DEFINITION(M17ModBaseband::MsgConfigureM17ModBaseband, Message)
M17ModBaseband::M17ModBaseband() :
m_mutex(QMutex::Recursive)
{
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
m_channelizer = new UpChannelizer(&m_source);
qDebug("M17ModBaseband::M17ModBaseband");
QObject::connect(
&m_sampleFifo,
&SampleSourceFifo::dataRead,
this,
&M17ModBaseband::handleData,
Qt::QueuedConnection
);
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_source.getFeedbackAudioFifo(), getInputMessageQueue());
m_source.applyFeedbackAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
M17ModBaseband::~M17ModBaseband()
{
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_source.getFeedbackAudioFifo());
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
delete m_channelizer;
}
void M17ModBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void M17ModBaseband::setChannel(ChannelAPI *channel)
{
m_source.setChannel(channel);
}
void M17ModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
{
unsigned int part1Begin, part1End, part2Begin, part2End;
m_sampleFifo.read(nbSamples, part1Begin, part1End, part2Begin, part2End);
SampleVector& data = m_sampleFifo.getData();
if (part1Begin != part1End)
{
std::copy(
data.begin() + part1Begin,
data.begin() + part1End,
begin
);
}
unsigned int shift = part1End - part1Begin;
if (part2Begin != part2End)
{
std::copy(
data.begin() + part2Begin,
data.begin() + part2End,
begin + shift
);
}
}
void M17ModBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
SampleVector& data = m_sampleFifo.getData();
unsigned int ipart1begin;
unsigned int ipart1end;
unsigned int ipart2begin;
unsigned int ipart2end;
qreal rmsLevel, peakLevel;
int numSamples;
unsigned int remainder = m_sampleFifo.remainder();
while ((remainder > 0) && (m_inputMessageQueue.size() == 0))
{
m_sampleFifo.write(remainder, ipart1begin, ipart1end, ipart2begin, ipart2end);
if (ipart1begin != ipart1end) { // first part of FIFO data
processFifo(data, ipart1begin, ipart1end);
}
if (ipart2begin != ipart2end) { // second part of FIFO data (used when block wraps around)
processFifo(data, ipart2begin, ipart2end);
}
remainder = m_sampleFifo.remainder();
}
m_source.getLevels(rmsLevel, peakLevel, numSamples);
emit levelChanged(rmsLevel, peakLevel, numSamples);
}
void M17ModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
{
m_channelizer->prefetch(iEnd - iBegin);
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
}
void M17ModBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool M17ModBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureM17ModBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureM17ModBaseband& cfg = (MsgConfigureM17ModBaseband&) cmd;
qDebug() << "M17ModBaseband::handleMessage: MsgConfigureM17ModBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "M17ModBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
return true;
}
else
{
return false;
}
}
void M17ModBaseband::applySettings(const M17ModSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_source.getAudioSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(m_source.getAudioSampleRate()); // reapply in case of channel sample rate change
}
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
audioDeviceManager->removeAudioSource(getAudioFifo());
int audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
if (getAudioSampleRate() != audioSampleRate)
{
m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
m_source.applyAudioSampleRate(audioSampleRate);
}
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
audioDeviceManager->addAudioSource(getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
} else {
audioDeviceManager->removeAudioSource(getAudioFifo());
}
}
if ((settings.m_feedbackAudioDeviceName != m_settings.m_feedbackAudioDeviceName) || force)
{
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
audioDeviceManager->removeAudioSink(getFeedbackAudioFifo());
audioDeviceManager->addAudioSink(getFeedbackAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
if (getFeedbackAudioSampleRate() != audioSampleRate) {
m_source.applyFeedbackAudioSampleRate(audioSampleRate);
}
}
m_source.applySettings(settings, force);
m_settings = settings;
}
int M17ModBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
+101
View File
@@ -0,0 +1,101 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MODBASEBAND_H
#define INCLUDE_M17MODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesourcefifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "m17modsource.h"
class UpChannelizer;
class ChannelAPI;
class M17ModBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureM17ModBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const M17ModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureM17ModBaseband* create(const M17ModSettings& settings, bool force)
{
return new MsgConfigureM17ModBaseband(settings, force);
}
private:
M17ModSettings m_settings;
bool m_force;
MsgConfigureM17ModBaseband(const M17ModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
M17ModBaseband();
~M17ModBaseband();
void reset();
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
double getMagSq() const { return m_source.getMagSq(); }
int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
int getFeedbackAudioSampleRate() const { return m_source.getFeedbackAudioSampleRate(); }
int getChannelSampleRate() const;
void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
AudioFifo *getFeedbackAudioFifo() { return m_source.getFeedbackAudioFifo(); }
void setChannel(ChannelAPI *channel);
signals:
/**
* Level changed
* \param rmsLevel RMS level in range 0.0 - 1.0
* \param peakLevel Peak level in range 0.0 - 1.0
* \param numSamples Number of audio samples analyzed
*/
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
private:
SampleSourceFifo m_sampleFifo;
UpChannelizer *m_channelizer;
M17ModSource m_source;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
M17ModSettings m_settings;
QMutex m_mutex;
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
bool handleMessage(const Message& cmd);
void applySettings(const M17ModSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_M17MODBASEBAND_H
+599
View File
@@ -0,0 +1,599 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDockWidget>
#include <QMainWindow>
#include <QFileDialog>
#include <QTime>
#include <QDebug>
#include <QRegExp>
#include <QResizeEvent>
#include "device/deviceuiset.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "maincore.h"
#include "ui_m17modgui.h"
#include "m17modgui.h"
M17ModGUI* M17ModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx)
{
M17ModGUI* gui = new M17ModGUI(pluginAPI, deviceUISet, channelTx);
return gui;
}
void M17ModGUI::destroy()
{
delete this;
}
void M17ModGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray M17ModGUI::serialize() const
{
return m_settings.serialize();
}
bool M17ModGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
void M17ModGUI::resizeEvent(QResizeEvent* size)
{
int maxWidth = getRollupContents()->maximumWidth();
int minHeight = getRollupContents()->minimumHeight() + getAdditionalHeight();
resize(width() < maxWidth ? width() : maxWidth, minHeight);
size->accept();
}
bool M17ModGUI::handleMessage(const Message& message)
{
if (M17Mod::MsgReportFileSourceStreamData::match(message))
{
m_recordSampleRate = ((M17Mod::MsgReportFileSourceStreamData&)message).getSampleRate();
m_recordLength = ((M17Mod::MsgReportFileSourceStreamData&)message).getRecordLength();
m_samplesCount = 0;
updateWithStreamData();
return true;
}
else if (M17Mod::MsgReportFileSourceStreamTiming::match(message))
{
m_samplesCount = ((M17Mod::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
updateWithStreamTime();
return true;
}
else if (M17Mod::MsgConfigureM17Mod::match(message))
{
const M17Mod::MsgConfigureM17Mod& cfg = (M17Mod::MsgConfigureM17Mod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
displaySettings();
blockApplySettings(false);
return true;
}
else if (DSPSignalNotification::match(message))
{
const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
updateAbsoluteCenterFrequency();
return true;
}
else
{
return false;
}
}
void M17ModGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void M17ModGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void M17ModGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
updateAbsoluteCenterFrequency();
applySettings();
}
void M17ModGUI::on_rfBW_valueChanged(int value)
{
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_settings.m_rfBandwidth = value * 100.0;
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
applySettings();
}
void M17ModGUI::on_fmDev_valueChanged(int value)
{
ui->fmDevText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(value / 10.0, 0, 'f', 1));
m_settings.m_fmDeviation = value * 200.0;
applySettings();
}
void M17ModGUI::on_volume_valueChanged(int value)
{
ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
m_settings.m_volumeFactor = value / 10.0;
applySettings();
}
void M17ModGUI::on_toneFrequency_valueChanged(int value)
{
ui->toneFrequencyText->setText(QString("%1k").arg(value / 100.0, 0, 'f', 2));
m_settings.m_toneFrequency = value * 10.0;
applySettings();
}
void M17ModGUI::on_channelMute_toggled(bool checked)
{
m_settings.m_channelMute = checked;
applySettings();
}
void M17ModGUI::on_playLoop_toggled(bool checked)
{
m_settings.m_playLoop = checked;
applySettings();
}
void M17ModGUI::on_play_toggled(bool checked)
{
ui->tone->setEnabled(!checked); // release other source inputs
ui->mic->setEnabled(!checked);
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputFile : M17ModSettings::M17ModInputNone;
applySettings();
ui->navTimeSlider->setEnabled(!checked);
m_enableNavTime = !checked;
}
void M17ModGUI::on_tone_toggled(bool checked)
{
ui->play->setEnabled(!checked); // release other source inputs
ui->mic->setEnabled(!checked);
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputTone : M17ModSettings::M17ModInputNone;
applySettings();
}
void M17ModGUI::on_mic_toggled(bool checked)
{
ui->play->setEnabled(!checked); // release other source inputs
ui->tone->setEnabled(!checked); // release other source inputs
m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputAudio : M17ModSettings::M17ModInputNone;
applySettings();
}
void M17ModGUI::on_feedbackEnable_toggled(bool checked)
{
m_settings.m_feedbackAudioEnable = checked;
applySettings();
}
void M17ModGUI::on_feedbackVolume_valueChanged(int value)
{
ui->feedbackVolumeText->setText(QString("%1").arg(value / 100.0, 0, 'f', 2));
m_settings.m_feedbackVolumeFactor = value / 100.0;
applySettings();
}
void M17ModGUI::on_navTimeSlider_valueChanged(int value)
{
if (m_enableNavTime && ((value >= 0) && (value <= 100)))
{
int t_sec = (m_recordLength * value) / 100;
QTime t(0, 0, 0, 0);
t = t.addSecs(t_sec);
M17Mod::MsgConfigureFileSourceSeek* message = M17Mod::MsgConfigureFileSourceSeek::create(value);
m_m17Mod->getInputMessageQueue()->push(message);
}
}
void M17ModGUI::on_showFileDialog_clicked(bool checked)
{
(void) checked;
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"), 0, QFileDialog::DontUseNativeDialog);
if (fileName != "")
{
m_fileName = fileName;
ui->recordFileText->setText(m_fileName);
ui->play->setEnabled(true);
configureFileName();
}
}
void M17ModGUI::configureFileName()
{
qDebug() << "M17ModGUI::configureFileName: " << m_fileName.toStdString().c_str();
M17Mod::MsgConfigureFileSourceName* message = M17Mod::MsgConfigureFileSourceName::create(m_fileName);
m_m17Mod->getInputMessageQueue()->push(message);
}
void M17ModGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
getRollupContents()->saveState(m_rollupState);
applySettings();
}
void M17ModGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.setDefaultTitle(m_displayedName);
if (m_deviceUISet->m_deviceMIMOEngine)
{
dialog.setNumberOfStreams(m_m17Mod->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
}
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitle(m_channelMarker.getTitle());
setTitleColor(m_settings.m_rgbColor);
if (m_deviceUISet->m_deviceMIMOEngine)
{
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
updateIndexLabel();
}
applySettings();
}
resetContextMenuType();
}
M17ModGUI::M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::M17ModGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_recordLength(0),
m_recordSampleRate(48000),
m_samplesCount(0),
m_audioSampleRate(-1),
m_feedbackAudioSampleRate(-1),
m_tickCount(0),
m_enableNavTime(false),
m_dcsCodeValidator(QRegExp("[0-7]{1,3}"))
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channeltx/modm17/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_m17Mod = (M17Mod*) channelTx;
m_m17Mod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->mic);
connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
CRightClickEnabler *feedbackRightClickEnabler = new CRightClickEnabler(ui->feedbackEnable);
connect(feedbackRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioFeedbackSelect()));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::red);
m_channelMarker.setBandwidth(12500);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("M17 Modulator");
m_channelMarker.setSourceOrSinkStream(false);
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_deviceUISet->addChannelMarker(&m_channelMarker);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
ui->play->setEnabled(false);
ui->play->setChecked(false);
ui->tone->setChecked(false);
ui->mic->setChecked(false);
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_m17Mod->setLevelMeter(ui->volumeMeter);
m_settings.setChannelMarker(&m_channelMarker);
m_settings.setRollupState(&m_rollupState);
displaySettings();
makeUIConnections();
applySettings();
}
M17ModGUI::~M17ModGUI()
{
delete ui;
}
void M17ModGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void M17ModGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
M17Mod::MsgConfigureM17Mod *msg = M17Mod::MsgConfigureM17Mod::create(m_settings, force);
m_m17Mod->getInputMessageQueue()->push(msg);
}
}
void M17ModGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
setTitle(m_channelMarker.getTitle());
updateIndexLabel();
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
ui->fmDevText->setText(QString("%1%2k").arg(QChar(0xB1, 0x00)).arg(m_settings.m_fmDeviation / 2000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 200.0);
ui->volumeText->setText(QString("%1").arg(m_settings.m_volumeFactor, 0, 'f', 1));
ui->volume->setValue(m_settings.m_volumeFactor * 10.0);
ui->toneFrequencyText->setText(QString("%1k").arg(m_settings.m_toneFrequency / 1000.0, 0, 'f', 2));
ui->toneFrequency->setValue(m_settings.m_toneFrequency / 10.0);
ui->channelMute->setChecked(m_settings.m_channelMute);
ui->playLoop->setChecked(m_settings.m_playLoop);
ui->tone->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->mic->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->play->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
ui->tone->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone);
ui->mic->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio);
ui->play->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile);
ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);
}
void M17ModGUI::leaveEvent(QEvent* event)
{
m_channelMarker.setHighlighted(false);
ChannelGUI::leaveEvent(event);
}
void M17ModGUI::enterEvent(QEvent* event)
{
m_channelMarker.setHighlighted(true);
ChannelGUI::enterEvent(event);
}
void M17ModGUI::audioSelect()
{
qDebug("M17ModGUI::audioSelect");
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, true); // true for input
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void M17ModGUI::audioFeedbackSelect()
{
qDebug("M17ModGUI::audioFeedbackSelect");
AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName, false); // false for output
audioSelect.exec();
if (audioSelect.m_selected)
{
m_settings.m_feedbackAudioDeviceName = audioSelect.m_audioDeviceName;
applySettings();
}
}
void M17ModGUI::tick()
{
double powDb = CalcDb::dbPower(m_m17Mod->getMagSq());
m_channelPowerDbAvg(powDb);
ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1));
int audioSampleRate = m_m17Mod->getAudioSampleRate();
if (audioSampleRate != m_audioSampleRate)
{
if (audioSampleRate < 0) {
ui->mic->setColor(QColor("red"));
} else {
ui->mic->resetColor();
}
m_audioSampleRate = audioSampleRate;
}
int feedbackAudioSampleRate = m_m17Mod->getFeedbackAudioSampleRate();
if (feedbackAudioSampleRate != m_feedbackAudioSampleRate)
{
if (feedbackAudioSampleRate < 0) {
ui->feedbackEnable->setStyleSheet("QToolButton { background-color : red; }");
} else {
ui->feedbackEnable->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
}
m_feedbackAudioSampleRate = feedbackAudioSampleRate;
}
if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == M17ModSettings::M17ModInputFile))
{
M17Mod::MsgConfigureFileSourceStreamTiming* message = M17Mod::MsgConfigureFileSourceStreamTiming::create();
m_m17Mod->getInputMessageQueue()->push(message);
}
}
void M17ModGUI::updateWithStreamData()
{
QTime recordLength(0, 0, 0, 0);
recordLength = recordLength.addSecs(m_recordLength);
QString s_time = recordLength.toString("HH:mm:ss");
ui->recordLengthText->setText(s_time);
updateWithStreamTime();
}
void M17ModGUI::updateWithStreamTime()
{
int t_sec = 0;
int t_msec = 0;
if (m_recordSampleRate > 0)
{
t_msec = ((m_samplesCount * 1000) / m_recordSampleRate) % 1000;
t_sec = m_samplesCount / m_recordSampleRate;
}
QTime t(0, 0, 0, 0);
t = t.addSecs(t_sec);
t = t.addMSecs(t_msec);
QString s_timems = t.toString("HH:mm:ss.zzz");
QString s_time = t.toString("HH:mm:ss");
ui->relTimeText->setText(s_timems);
if (!m_enableNavTime)
{
float posRatio = (float) t_sec / (float) m_recordLength;
ui->navTimeSlider->setValue((int) (posRatio * 100.0));
}
}
void M17ModGUI::makeUIConnections()
{
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &M17ModGUI::on_deltaFrequency_changed);
QObject::connect(ui->rfBW, &QSlider::valueChanged, this, &M17ModGUI::on_rfBW_valueChanged);
QObject::connect(ui->fmDev, &QSlider::valueChanged, this, &M17ModGUI::on_fmDev_valueChanged);
QObject::connect(ui->toneFrequency, &QDial::valueChanged, this, &M17ModGUI::on_toneFrequency_valueChanged);
QObject::connect(ui->volume, &QDial::valueChanged, this, &M17ModGUI::on_volume_valueChanged);
QObject::connect(ui->channelMute, &QToolButton::toggled, this, &M17ModGUI::on_channelMute_toggled);
QObject::connect(ui->tone, &ButtonSwitch::toggled, this, &M17ModGUI::on_tone_toggled);
QObject::connect(ui->mic, &ButtonSwitch::toggled, this, &M17ModGUI::on_mic_toggled);
QObject::connect(ui->play, &ButtonSwitch::toggled, this, &M17ModGUI::on_play_toggled);
QObject::connect(ui->playLoop, &ButtonSwitch::toggled, this, &M17ModGUI::on_playLoop_toggled);
QObject::connect(ui->navTimeSlider, &QSlider::valueChanged, this, &M17ModGUI::on_navTimeSlider_valueChanged);
QObject::connect(ui->showFileDialog, &QPushButton::clicked, this, &M17ModGUI::on_showFileDialog_clicked);
QObject::connect(ui->feedbackEnable, &QToolButton::toggled, this, &M17ModGUI::on_feedbackEnable_toggled);
QObject::connect(ui->feedbackVolume, &QDial::valueChanged, this, &M17ModGUI::on_feedbackVolume_valueChanged);
}
void M17ModGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}
+140
View File
@@ -0,0 +1,140 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_
#define PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_
#include <QRegExpValidator>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "m17mod.h"
#include "m17modsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSource;
namespace Ui {
class M17ModGUI;
}
class M17ModGUI : public ChannelGUI {
Q_OBJECT
public:
static M17ModGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; };
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; };
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; };
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; };
virtual QString getTitle() const { return m_settings.m_title; };
virtual QColor getTitleColor() const { return m_settings.m_rgbColor; };
virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; }
virtual bool getHidden() const { return m_settings.m_hidden; }
virtual ChannelMarker& getChannelMarker() { return m_channelMarker; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual void setStreamIndex(int streamIndex) { m_settings.m_streamIndex = streamIndex; }
public slots:
void channelMarkerChangedByCursor();
protected:
void resizeEvent(QResizeEvent* size);
private:
Ui::M17ModGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
M17ModSettings m_settings;
qint64 m_deviceCenterFrequency;
int m_basebandSampleRate;
bool m_doApplySettings;
M17Mod* m_m17Mod;
MovingAverageUtil<double, double, 20> m_channelPowerDbAvg;
QString m_fileName;
quint32 m_recordLength;
int m_recordSampleRate;
int m_samplesCount;
int m_audioSampleRate;
int m_feedbackAudioSampleRate;
std::size_t m_tickCount;
bool m_enableNavTime;
M17ModSettings::M17ModInputAF m_modAFInput;
MessageQueue m_inputMessageQueue;
QRegExpValidator m_dcsCodeValidator;
explicit M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = nullptr);
virtual ~M17ModGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateWithStreamData();
void updateWithStreamTime();
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void handleSourceMessages();
void on_deltaFrequency_changed(qint64 value);
void on_channelSpacingApply_clicked();
void on_rfBW_valueChanged(int value);
void on_fmDev_valueChanged(int value);
void on_toneFrequency_valueChanged(int value);
void on_volume_valueChanged(int value);
void on_channelMute_toggled(bool checked);
void on_tone_toggled(bool checked);
void on_mic_toggled(bool checked);
void on_play_toggled(bool checked);
void on_playLoop_toggled(bool checked);
void on_navTimeSlider_valueChanged(int value);
void on_showFileDialog_clicked(bool checked);
void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void configureFileName();
void audioSelect();
void audioFeedbackSelect();
void tick();
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MODGUI_H_ */
+737
View File
@@ -0,0 +1,737 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>M17ModGUI</class>
<widget class="RollupContents" name="M17ModGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>278</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>560</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>M17 Modulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>358</width>
<height>271</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>358</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="deltaFreqPowLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="deltaFrequencyLayout">
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="channelPower">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>-100.0 dB</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QToolButton" name="channelMute">
<property name="toolTip">
<string>Mute/Unmute channel</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/txon.png</normaloff>
<normalon>:/txoff.png</normalon>:/txon.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="fmDeviationLayout">
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>RFBW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rfBW">
<property name="toolTip">
<string>RF bandwidth (kHz)</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>480</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>160</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWText">
<property name="minimumSize">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>25.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
<string>Dev</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="fmDev">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>FM peak deviation (kHz)</string>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevText">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>+20.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="volumeLayout">
<item>
<widget class="QLabel" name="volLabel">
<property name="text">
<string>Vol</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="volume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio input gain</string>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="volumeText">
<property name="minimumSize">
<size>
<width>25</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Audio input gain value</string>
</property>
<property name="text">
<string>1.0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterVU" name="volumeMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="recordFileSelectLayout">
<item>
<widget class="ButtonSwitch" name="tone">
<property name="toolTip">
<string>Tone modulation (1 kHz)</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/carrier.png</normaloff>:/carrier.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="toneFrequency">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Tone frequency</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>250</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="toneFrequencyText">
<property name="minimumSize">
<size>
<width>36</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Tone frequency (kHz)</string>
</property>
<property name="text">
<string>1.00k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="mic">
<property name="toolTip">
<string>Audio input</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/microphone.png</normaloff>:/microphone.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feedbackEnable">
<property name="toolTip">
<string>Left: enable / disable audio feedback - Right: select audio output device</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sound_off.png</normaloff>
<normalon>:/sound_on.png</normalon>:/sound_off.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="feedbackVolume">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>50</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedbackVolumeText">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Audio feedback volume</string>
</property>
<property name="text">
<string>1.00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="fileNameLayout">
<item>
<widget class="QLabel" name="recordFileText">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="playControllLayout">
<item>
<widget class="QPushButton" name="showFileDialog">
<property name="minimumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Open record file (48 kHz 32 bit float LE mono)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/preset-load.png</normaloff>:/preset-load.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="playLoop">
<property name="toolTip">
<string>Play record file in a loop</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="play">
<property name="toolTip">
<string>Record file play/pause</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/pause.png</normalon>
<disabledoff>:/play.png</disabledoff>
<disabledon>:/play.png</disabledon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="linePlay1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="relTimeText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>90</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Record time from start</string>
</property>
<property name="text">
<string>00:00:00.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="linePlay2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="recordLengthText">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Total record time</string>
</property>
<property name="text">
<string>00:00:00</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_nav">
<item>
<widget class="QSlider" name="navTimeSlider">
<property name="toolTip">
<string>Record file time navigator</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>LevelMeterVU</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>
+92
View File
@@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "m17modgui.h"
#endif
#include "m17mod.h"
#include "m17modwebapiadapter.h"
#include "m17modplugin.h"
const PluginDescriptor M17ModPlugin::m_pluginDescriptor = {
M17Mod::m_channelId,
QStringLiteral("M17 Modulator"),
QStringLiteral("7.4.0"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
M17ModPlugin::M17ModPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& M17ModPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void M17ModPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
// register AM modulator
m_pluginAPI->registerTxChannel(M17Mod::m_channelIdURI, M17Mod::m_channelId, this);
}
void M17ModPlugin::createTxChannel(DeviceAPI *deviceAPI, BasebandSampleSource **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
M17Mod *instance = new M17Mod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* M17ModPlugin::createTxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSource *txChannel) const
{
(void) deviceUISet;
(void) txChannel;
return nullptr;
}
#else
ChannelGUI* M17ModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) const
{
return M17ModGUI::create(m_pluginAPI, deviceUISet, txChannel);
}
#endif
ChannelWebAPIAdapter* M17ModPlugin::createChannelWebAPIAdapter() const
{
return new M17ModWebAPIAdapter();
}
+48
View File
@@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MODPLUGIN_H
#define INCLUDE_M17MODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSource;
class M17ModPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channeltx.modm17")
public:
explicit M17ModPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createTxChannel(DeviceAPI *deviceAPI, BasebandSampleSource **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_M17MODPLUGIN_H
+179
View File
@@ -0,0 +1,179 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/ctcssfrequencies.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "m17modsettings.h"
M17ModSettings::M17ModSettings() :
m_channelMarker(nullptr),
m_rollupState(nullptr)
{
resetToDefaults();
}
void M17ModSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 16000.0f;
m_fmDeviation = 10000.0f; //!< full deviation
m_toneFrequency = 1000.0f;
m_volumeFactor = 1.0f;
m_channelMute = false;
m_playLoop = false;
m_rgbColor = QColor(255, 0, 255).rgb();
m_title = "M17 Modulator";
m_modAFInput = M17ModInputAF::M17ModInputNone;
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
m_feedbackVolumeFactor = 0.5f;
m_feedbackAudioEnable = false;
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
}
QByteArray M17ModSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeReal(2, m_rfBandwidth);
s.writeReal(4, m_fmDeviation);
s.writeU32(5, m_rgbColor);
s.writeReal(6, m_toneFrequency);
s.writeReal(7, m_volumeFactor);
if (m_channelMarker) {
s.writeBlob(11, m_channelMarker->serialize());
}
s.writeString(12, m_title);
s.writeS32(13, (int) m_modAFInput);
s.writeString(14, m_audioDeviceName);
s.writeBool(15, m_useReverseAPI);
s.writeString(16, m_reverseAPIAddress);
s.writeU32(17, m_reverseAPIPort);
s.writeU32(18, m_reverseAPIDeviceIndex);
s.writeU32(19, m_reverseAPIChannelIndex);
s.writeString(20, m_feedbackAudioDeviceName);
s.writeReal(21, m_feedbackVolumeFactor);
s.writeBool(22, m_feedbackAudioEnable);
s.writeS32(23, m_streamIndex);
if (m_rollupState) {
s.writeBlob(27, m_rollupState->serialize());
}
s.writeS32(28, m_workspaceIndex);
s.writeBlob(29, m_geometryBytes);
s.writeBool(30, m_hidden);
return s.final();
}
bool M17ModSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
d.readS32(1, &tmp, 0);
m_inputFrequencyOffset = tmp;
d.readReal(2, &m_rfBandwidth, 12500.0);
d.readReal(4, &m_fmDeviation, 10000.0);
d.readU32(5, &m_rgbColor);
d.readReal(6, &m_toneFrequency, 1000.0);
d.readReal(7, &m_volumeFactor, 1.0);
d.readBlob(8, &bytetmp);
if (m_channelMarker)
{
d.readBlob(11, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readString(12, &m_title, "M17 Modulator");
d.readS32(13, &tmp, 0);
if ((tmp < 0) || (tmp > (int) M17ModInputAF::M17ModInputTone)) {
m_modAFInput = M17ModInputNone;
} else {
m_modAFInput = (M17ModInputAF) tmp;
}
d.readString(14, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readBool(15, &m_useReverseAPI, false);
d.readString(16, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(17, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(18, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(19, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readString(20, &m_feedbackAudioDeviceName, AudioDeviceManager::m_defaultDeviceName);
d.readReal(21, &m_feedbackVolumeFactor, 1.0);
d.readBool(22, &m_feedbackAudioEnable, false);
d.readS32(23, &m_streamIndex, 0);
d.readS32(25, &tmp, 0023);
if (m_rollupState)
{
d.readBlob(27, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(28, &m_workspaceIndex, 0);
d.readBlob(29, &m_geometryBytes);
d.readBool(30, &m_hidden, false);
return true;
}
else
{
qDebug() << "NFMModSettings::deserialize: ERROR";
resetToDefaults();
return false;
}
}
+74
View File
@@ -0,0 +1,74 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_
#define PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_
#include <QByteArray>
#include "dsp/cwkeyersettings.h"
class Serializable;
struct M17ModSettings
{
enum M17ModInputAF
{
M17ModInputNone,
M17ModInputFile,
M17ModInputAudio,
M17ModInputTone
};
qint64 m_inputFrequencyOffset;
float m_rfBandwidth;
float m_fmDeviation;
float m_toneFrequency;
float m_volumeFactor;
bool m_channelMute;
bool m_playLoop;
quint32 m_rgbColor;
QString m_title;
M17ModInputAF m_modAFInput;
QString m_audioDeviceName; //!< This is the audio device you get the audio samples from
QString m_feedbackAudioDeviceName; //!< This is the audio device you send the audio samples to for audio feedback
float m_feedbackVolumeFactor;
bool m_feedbackAudioEnable;
int m_streamIndex;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
Serializable *m_channelMarker;
Serializable *m_rollupState;
M17ModSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* PLUGINS_CHANNELTX_MODM17_M17MODSETTINGS_H_ */
+418
View File
@@ -0,0 +1,418 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/datafifo.h"
#include "util/messagequeue.h"
#include "maincore.h"
#include "m17modsource.h"
const int M17ModSource::m_levelNbSamples = 480; // every 10ms
M17ModSource::M17ModSource() :
m_channelSampleRate(48000),
m_channelFrequencyOffset(0),
m_modPhasor(0.0f),
m_audioSampleRate(48000),
m_audioFifo(12000),
m_feedbackAudioFifo(48000),
m_levelCalcCount(0),
m_peakLevel(0.0f),
m_levelSum(0.0f),
m_ifstream(nullptr),
m_mutex(QMutex::Recursive)
{
m_audioFifo.setLabel("M17ModSource.m_audioFifo");
m_feedbackAudioFifo.setLabel("M17ModSource.m_feedbackAudioFifo");
m_audioBuffer.resize(24000);
m_audioBufferFill = 0;
m_audioReadBuffer.resize(24000);
m_audioReadBufferFill = 0;
m_feedbackAudioBuffer.resize(1<<14);
m_feedbackAudioBufferFill = 0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
m_magsq = 0.0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
M17ModSource::~M17ModSource()
{
}
void M17ModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
{
std::for_each(
begin,
begin + nbSamples,
[this](Sample& s) {
pullOne(s);
}
);
}
void M17ModSource::pullOne(Sample& sample)
{
if (m_settings.m_channelMute)
{
sample.m_real = 0.0f;
sample.m_imag = 0.0f;
return;
}
Complex ci;
if (m_interpolatorDistance > 1.0f) // decimate
{
modulateSample();
while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
else
{
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
m_interpolatorDistanceRemain += m_interpolatorDistance;
ci *= m_carrierNco.nextIQ(); // shift to carrier frequency
double magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
magsq /= (SDR_TX_SCALED*SDR_TX_SCALED);
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
sample.m_real = (FixReal) ci.real();
sample.m_imag = (FixReal) ci.imag();
}
void M17ModSource::prefetch(unsigned int nbSamples)
{
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
pullAudio(nbSamplesAudio);
}
void M17ModSource::pullAudio(unsigned int nbSamplesAudio)
{
QMutexLocker mlock(&m_mutex);
if (nbSamplesAudio > m_audioBuffer.size()) {
m_audioBuffer.resize(nbSamplesAudio);
}
std::copy(&m_audioReadBuffer[0], &m_audioReadBuffer[nbSamplesAudio], &m_audioBuffer[0]);
m_audioBufferFill = 0;
if (m_audioReadBufferFill > nbSamplesAudio) // copy back remaining samples at the start of the read buffer
{
std::copy(&m_audioReadBuffer[nbSamplesAudio], &m_audioReadBuffer[m_audioReadBufferFill], &m_audioReadBuffer[0]);
m_audioReadBufferFill = m_audioReadBufferFill - nbSamplesAudio; // adjust current read buffer fill pointer
}
}
void M17ModSource::modulateSample()
{
Real t1, t;
pullAF(t);
if (m_settings.m_feedbackAudioEnable) {
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
}
calculateLevel(t);
t1 = m_lowpass.filter(t) * 1.2f;
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * t1;
// limit phasor range to ]-pi,pi]
if (m_modPhasor > M_PI) {
m_modPhasor -= (2.0f * M_PI);
}
m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
m_demodBuffer[m_demodBufferFill] = t1 * std::numeric_limits<int16_t>::max();
++m_demodBufferFill;
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<ObjectPipe*> dataPipes;
MainCore::instance()->getDataPipes().getDataPipes(m_channel, "demod", dataPipes);
if (dataPipes.size() > 0)
{
QList<ObjectPipe*>::iterator it = dataPipes.begin();
for (; it != dataPipes.end(); ++it)
{
DataFifo *fifo = qobject_cast<DataFifo*>((*it)->m_element);
if (fifo) {
fifo->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16), DataFifo::DataTypeI16);
}
}
}
m_demodBufferFill = 0;
}
}
void M17ModSource::pullAF(Real& sample)
{
switch (m_settings.m_modAFInput)
{
case M17ModSettings::M17ModInputTone:
sample = m_toneNco.next();
break;
case M17ModSettings::M17ModInputFile:
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
if (m_ifstream && m_ifstream->is_open())
{
if (m_ifstream->eof())
{
if (m_settings.m_playLoop)
{
m_ifstream->clear();
m_ifstream->seekg(0, std::ios::beg);
}
}
if (m_ifstream->eof())
{
sample = 0.0f;
}
else
{
m_ifstream->read(reinterpret_cast<char*>(&sample), sizeof(Real));
sample *= m_settings.m_volumeFactor;
}
}
else
{
sample = 0.0f;
}
break;
case M17ModSettings::M17ModInputAudio:
if (m_audioBufferFill < m_audioBuffer.size())
{
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
m_audioBufferFill++;
}
else
{
unsigned int size = m_audioBuffer.size();
qDebug("NFMModSource::pullAF: starve audio samples: size: %u", size);
sample = ((m_audioBuffer[size-1].l + m_audioBuffer[size-1].r) / 65536.0f) * m_settings.m_volumeFactor;
}
break;
case M17ModSettings::M17ModInputNone:
default:
sample = 0.0f;
break;
}
}
void M17ModSource::pushFeedback(Real sample)
{
Complex c(sample, sample);
Complex ci;
if (m_feedbackInterpolatorDistance < 1.0f) // interpolate
{
while (!m_feedbackInterpolator.interpolate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
else // decimate
{
if (m_feedbackInterpolator.decimate(&m_feedbackInterpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_feedbackInterpolatorDistanceRemain += m_feedbackInterpolatorDistance;
}
}
}
void M17ModSource::processOneSample(Complex& ci)
{
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].l = ci.real();
m_feedbackAudioBuffer[m_feedbackAudioBufferFill].r = ci.imag();
++m_feedbackAudioBufferFill;
if (m_feedbackAudioBufferFill >= m_feedbackAudioBuffer.size())
{
uint res = m_feedbackAudioFifo.write((const quint8*)&m_feedbackAudioBuffer[0], m_feedbackAudioBufferFill);
if (res != m_feedbackAudioBufferFill)
{
qDebug("M17ModSource::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
m_feedbackAudioFifo.clear();
}
m_feedbackAudioBufferFill = 0;
}
}
void M17ModSource::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
{
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
m_levelSum += sample * sample;
m_levelCalcCount++;
}
else
{
m_rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
m_peakLevelOut = m_peakLevel;
m_peakLevel = 0.0f;
m_levelSum = 0.0f;
m_levelCalcCount = 0;
}
}
void M17ModSource::applyAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("M17ModSource::applyAudioSampleRate: invalid sample rate %d", sampleRate);
return;
}
qDebug("M17ModSource::applyAudioSampleRate: %d", sampleRate);
m_interpolatorDistanceRemain = 0;
m_interpolatorConsumed = false;
m_interpolatorDistance = (Real) sampleRate / (Real) m_channelSampleRate;
m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth);
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
m_audioSampleRate = sampleRate;
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_channel, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue* messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
messageQueue->push(msg);
}
}
}
void M17ModSource::applyFeedbackAudioSampleRate(int sampleRate)
{
if (sampleRate < 0)
{
qWarning("NFMModSource::applyFeedbackAudioSampleRate: invalid sample rate %d", sampleRate);
return;
}
qDebug("NFMModSource::applyFeedbackAudioSampleRate: %d", sampleRate);
m_feedbackInterpolatorDistanceRemain = 0;
m_feedbackInterpolatorConsumed = false;
m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
m_feedbackAudioSampleRate = sampleRate;
}
void M17ModSource::applySettings(const M17ModSettings& settings, bool force)
{
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_settings.m_rfBandwidth = settings.m_rfBandwidth;
applyAudioSampleRate(m_audioSampleRate);
}
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
{
if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
connect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
} else {
disconnect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
}
}
m_settings = settings;
}
void M17ModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "M17ModSource::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((channelFrequencyOffset != m_channelFrequencyOffset)
|| (channelSampleRate != m_channelSampleRate) || force)
{
m_carrierNco.setFreq(channelFrequencyOffset, channelSampleRate);
}
if ((channelSampleRate != m_channelSampleRate) || force)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorConsumed = false;
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) channelSampleRate;
m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void M17ModSource::handleAudio()
{
QMutexLocker mlock(&m_mutex);
unsigned int nbRead;
while ((nbRead = m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioReadBuffer[m_audioReadBufferFill]), 4096)) != 0)
{
if (m_audioReadBufferFill + nbRead + 4096 < m_audioReadBuffer.size()) {
m_audioReadBufferFill += nbRead;
}
}
}
+137
View File
@@ -0,0 +1,137 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_M17MODSOURCE_H
#define INCLUDE_M17MODSOURCE_H
#include <QObject>
#include <QMutex>
#include <QVector>
#include <iostream>
#include <fstream>
#include "dsp/channelsamplesource.h"
#include "dsp/nco.h"
#include "dsp/ncof.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "dsp/filterrc.h"
#include "util/movingaverage.h"
#include "audio/audiofifo.h"
#include "m17modsettings.h"
class ChannelAPI;
class M17ModSource : public QObject, public ChannelSampleSource
{
Q_OBJECT
public:
M17ModSource();
virtual ~M17ModSource();
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
virtual void pullOne(Sample& sample);
virtual void prefetch(unsigned int nbSamples);
void setInputFileStream(std::ifstream *ifstream) { m_ifstream = ifstream; }
AudioFifo *getAudioFifo() { return &m_audioFifo; }
AudioFifo *getFeedbackAudioFifo() { return &m_feedbackAudioFifo; }
void applyAudioSampleRate(int sampleRate);
void applyFeedbackAudioSampleRate(int sampleRate);
int getAudioSampleRate() const { return m_audioSampleRate; }
int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
double getMagSq() const { return m_magsq; }
void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
{
rmsLevel = m_rmsLevel;
peakLevel = m_peakLevelOut;
numSamples = m_levelNbSamples;
}
void applySettings(const M17ModSettings& settings, bool force = false);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
private:
int m_channelSampleRate;
int m_channelFrequencyOffset;
M17ModSettings m_settings;
ChannelAPI *m_channel;
NCO m_carrierNco;
NCOF m_toneNco;
float m_modPhasor; //!< baseband modulator phasor
Complex m_modSample;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
bool m_interpolatorConsumed;
Interpolator m_feedbackInterpolator;
Real m_feedbackInterpolatorDistance;
Real m_feedbackInterpolatorDistanceRemain;
bool m_feedbackInterpolatorConsumed;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
Lowpass<Real> m_lowpass;
double m_magsq;
MovingAverageUtil<double, double, 16> m_movingAverage;
int m_audioSampleRate;
AudioVector m_audioBuffer;
unsigned int m_audioBufferFill;
AudioVector m_audioReadBuffer;
unsigned int m_audioReadBufferFill;
AudioFifo m_audioFifo;
int m_feedbackAudioSampleRate;
AudioVector m_feedbackAudioBuffer;
uint m_feedbackAudioBufferFill;
AudioFifo m_feedbackAudioFifo;
quint32 m_levelCalcCount;
qreal m_rmsLevel;
qreal m_peakLevelOut;
Real m_peakLevel;
Real m_levelSum;
std::ifstream *m_ifstream;
QMutex m_mutex;
static const int m_levelNbSamples;
static const float m_preemphasis;
void processOneSample(Complex& ci);
void pullAF(Real& sample);
void pullAudio(unsigned int nbSamples);
void pushFeedback(Real sample);
void calculateLevel(Real& sample);
void modulateSample();
private slots:
void handleAudio();
};
#endif // INCLUDE_M17MODSOURCE_H
@@ -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