mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-04-08 04:30:18 -04:00
SampleSourceFifo refactoring and Tx code reorganization
This commit is contained in:
parent
246ff824af
commit
3b74153ec6
@ -17,8 +17,8 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# configure version
|
||||
set(sdrangel_VERSION_MAJOR "4")
|
||||
set(sdrangel_VERSION_MINOR "11")
|
||||
set(sdrangel_VERSION_PATCH "12")
|
||||
set(sdrangel_VERSION_MINOR "12")
|
||||
set(sdrangel_VERSION_PATCH "0")
|
||||
set(sdrangel_VERSION_SUFFIX "")
|
||||
|
||||
# SDRAngel cmake options
|
||||
|
@ -17,6 +17,4 @@
|
||||
|
||||
#include "../bladerf1/devicebladerf1shared.h"
|
||||
|
||||
const float DeviceBladeRF1Shared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceBladeRF1Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee
|
||||
const int DeviceBladeRF1Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32
|
||||
const unsigned int DeviceBladeRF1Shared::m_sampleFifoMinRate = 48000;
|
||||
|
@ -24,9 +24,7 @@
|
||||
class DEVICES_API DeviceBladeRF1Shared
|
||||
{
|
||||
public:
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const int m_sampleFifoMinSize32;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
};
|
||||
|
||||
|
||||
|
@ -19,9 +19,7 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DeviceBladeRF2Shared::MsgReportBuddyChange, Message)
|
||||
|
||||
const float DeviceBladeRF2Shared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceBladeRF2Shared::m_sampleFifoMinSize = 75000; // 300 kS/s knee
|
||||
const int DeviceBladeRF2Shared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32
|
||||
const unsigned int DeviceBladeRF2Shared::m_sampleFifoMinRate = 48000;
|
||||
|
||||
DeviceBladeRF2Shared::DeviceBladeRF2Shared() :
|
||||
m_dev(0),
|
||||
|
@ -85,9 +85,7 @@ public:
|
||||
BladeRF2Input *m_source;
|
||||
BladeRF2Output *m_sink;
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const int m_sampleFifoMinSize32;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
};
|
||||
|
||||
|
||||
|
@ -19,5 +19,4 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DeviceHackRFShared::MsgSynchronizeFrequency, Message)
|
||||
|
||||
const float DeviceHackRFShared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceHackRFShared::m_sampleFifoMinSize = 150000; // 600kS/s knee
|
||||
const unsigned int DeviceHackRFShared::m_sampleFifoMinRate = 48000; // 48kS/s knee
|
||||
|
@ -47,8 +47,7 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
};
|
||||
|
||||
|
||||
|
@ -22,5 +22,4 @@ MESSAGE_CLASS_DEFINITION(DeviceLimeSDRShared::MsgReportClockSourceChange, Messag
|
||||
MESSAGE_CLASS_DEFINITION(DeviceLimeSDRShared::MsgReportGPIOChange, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DeviceLimeSDRShared::MsgReportDeviceInfo, Message)
|
||||
|
||||
const float DeviceLimeSDRShared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceLimeSDRShared::m_sampleFifoMinSize = 48000; // 192kS/s knee
|
||||
const unsigned int DeviceLimeSDRShared::m_sampleFifoMinRate = 48000;
|
||||
|
@ -161,8 +161,7 @@ public:
|
||||
uint32_t m_log2Soft;
|
||||
bool m_threadWasRunning; //!< flag to know if thread needs to be resumed after suspend
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
|
||||
DeviceLimeSDRShared() :
|
||||
m_deviceParams(0),
|
||||
|
@ -19,5 +19,4 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(DevicePlutoSDRShared::MsgCrossReportToBuddy, Message)
|
||||
|
||||
const float DevicePlutoSDRShared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DevicePlutoSDRShared::m_sampleFifoMinSize = 48000; // 192kS/s knee
|
||||
const unsigned int DevicePlutoSDRShared::m_sampleFifoMinRate = 48000;
|
||||
|
@ -90,8 +90,7 @@ public:
|
||||
ThreadInterface *m_thread; //!< holds the thread address if started else 0
|
||||
bool m_threadWasRunning; //!< flag to know if thread needs to be resumed after suspend
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
|
||||
DevicePlutoSDRShared() :
|
||||
m_deviceParams(0),
|
||||
|
@ -20,9 +20,7 @@
|
||||
MESSAGE_CLASS_DEFINITION(DeviceSoapySDRShared::MsgReportBuddyChange, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DeviceSoapySDRShared::MsgReportDeviceArgsChange, Message)
|
||||
|
||||
const float DeviceSoapySDRShared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceSoapySDRShared::m_sampleFifoMinSize = 75000; // 300 kS/s knee
|
||||
const int DeviceSoapySDRShared::m_sampleFifoMinSize32 = 150000; // Fixed for interpolation by 32
|
||||
const unsigned int DeviceSoapySDRShared::m_sampleFifoMinRate = 48000;
|
||||
|
||||
DeviceSoapySDRShared::DeviceSoapySDRShared() :
|
||||
m_device(0),
|
||||
|
@ -110,9 +110,7 @@ public:
|
||||
SoapySDRInput *m_source;
|
||||
SoapySDROutput *m_sink;
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const int m_sampleFifoMinSize32;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
};
|
||||
|
||||
|
||||
|
@ -24,8 +24,7 @@ MESSAGE_CLASS_DEFINITION(DeviceXTRXShared::MsgReportBuddyChange, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DeviceXTRXShared::MsgReportClockSourceChange, Message)
|
||||
MESSAGE_CLASS_DEFINITION(DeviceXTRXShared::MsgReportDeviceInfo, Message)
|
||||
|
||||
const float DeviceXTRXShared::m_sampleFifoLengthInSeconds = 0.25;
|
||||
const int DeviceXTRXShared::m_sampleFifoMinSize = 48000; // 192kS/s knee
|
||||
const unsigned int DeviceXTRXShared::m_sampleFifoMinRate = 48000;
|
||||
|
||||
DeviceXTRXShared::DeviceXTRXShared() :
|
||||
m_dev(0),
|
||||
|
@ -141,8 +141,7 @@ public:
|
||||
ThreadInterface *m_thread; //!< holds the thread address if started else 0
|
||||
bool m_threadWasRunning; //!< flag to know if thread needs to be resumed after suspend
|
||||
|
||||
static const float m_sampleFifoLengthInSeconds;
|
||||
static const int m_sampleFifoMinSize;
|
||||
static const unsigned int m_sampleFifoMinRate;
|
||||
|
||||
DeviceXTRXShared();
|
||||
~DeviceXTRXShared();
|
||||
|
Binary file not shown.
@ -0,0 +1,247 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "beamsteeringcwmodbaseband.h"
|
||||
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(BeamSteeringCWModBaseband::MsgConfigureBeamSteeringCWModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(BeamSteeringCWModBaseband::MsgSignalNotification, Message)
|
||||
|
||||
BeamSteeringCWModBaseband::BeamSteeringCWModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleMOFifo.init(2, SampleMOFifo::getSizePolicy(48000));
|
||||
m_vbegin.resize(2);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
m_streamSources[i].setStreamIndex(i);
|
||||
m_channelizers[i] = new UpSampleChannelizer(&m_streamSources[i]);
|
||||
m_sizes[i] = 0;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
&m_sampleMOFifo,
|
||||
&SampleMOFifo::dataSyncRead,
|
||||
this,
|
||||
&BeamSteeringCWModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
m_lastStream = 0;
|
||||
}
|
||||
|
||||
BeamSteeringCWModBaseband::~BeamSteeringCWModBaseband()
|
||||
{
|
||||
for (int i = 0; i < 2; i++) {
|
||||
delete m_channelizers[i];
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleMOFifo.reset();
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
m_streamSources[i].reset();
|
||||
m_sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::pull(SampleVector::iterator& begin, unsigned int nbSamples, unsigned int streamIndex)
|
||||
{
|
||||
if (streamIndex > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (streamIndex == m_lastStream) {
|
||||
qWarning("BeamSteeringCWModBaseband::pull: twice same stream in a row: %u", streamIndex);
|
||||
}
|
||||
|
||||
m_lastStream = streamIndex;
|
||||
m_vbegin[streamIndex] = begin;
|
||||
m_sizes[streamIndex] = nbSamples;
|
||||
|
||||
if (streamIndex == 1)
|
||||
{
|
||||
unsigned int part1Begin, part1End, part2Begin, part2End, size;
|
||||
|
||||
if (m_sizes[0] != m_sizes[1])
|
||||
{
|
||||
qWarning("BeamSteeringCWModBaseband::pull: unequal sizes: [0]: %d [1]: %d", m_sizes[0], m_sizes[1]);
|
||||
size = std::min(m_sizes[0], m_sizes[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = m_sizes[0];
|
||||
}
|
||||
|
||||
std::vector<SampleVector>& data = m_sampleMOFifo.getData();
|
||||
m_sampleMOFifo.readSync(size, part1Begin, part1End, part2Begin, part2End);
|
||||
|
||||
if (part1Begin != part1End)
|
||||
{
|
||||
std::copy(data[0].begin() + part1Begin, data[0].begin() + part1End, m_vbegin[0]);
|
||||
std::copy(data[1].begin() + part1Begin, data[1].begin() + part1End, m_vbegin[1]);
|
||||
}
|
||||
|
||||
if (part2Begin != part2End)
|
||||
{
|
||||
std::copy(data[0].begin() + part2Begin, data[0].begin() + part2End, m_vbegin[0]);
|
||||
std::copy(data[1].begin() + part2Begin, data[1].begin() + part2End, m_vbegin[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
std::vector<SampleVector>& data = m_sampleMOFifo.getData();
|
||||
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
unsigned int remainder = m_sampleMOFifo.remainderSync();
|
||||
|
||||
while ((remainder > 0) && (m_inputMessageQueue.size() == 0))
|
||||
{
|
||||
m_sampleMOFifo.writeSync(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_sampleMOFifo.remainderSync();
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::processFifo(std::vector<SampleVector>& data, unsigned int ibegin, unsigned int iend)
|
||||
{
|
||||
for (unsigned int stream = 0; stream < 2; stream++) {
|
||||
m_channelizers[stream]->pull(data[stream].begin() + ibegin, iend - ibegin);
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BeamSteeringCWModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureBeamSteeringCWModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureBeamSteeringCWModBaseband& cfg = (MsgConfigureBeamSteeringCWModBaseband&) cmd;
|
||||
qDebug() << "BeamSteeringCWModBaseband::handleMessage: MsgConfigureBeamSteeringCWModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgSignalNotification& cfg = (MsgSignalNotification&) cmd;
|
||||
int basebandSampleRate = cfg.getBasebandSampleRate();
|
||||
|
||||
qDebug() << "BeamSteeringCWModBaseband::handleMessage: MsgSignalNotification:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate;
|
||||
|
||||
m_sampleMOFifo.resize(SampleMOFifo::getSizePolicy(basebandSampleRate));
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
m_channelizers[i]->setBasebandSampleRate(basebandSampleRate, true);
|
||||
m_streamSources[i].reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug("BeamSteeringCWModBaseband::handleMessage: unhandled: %s", cmd.getIdentifier());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void BeamSteeringCWModBaseband::applySettings(const BeamSteeringCWModSettings& settings, bool force)
|
||||
{
|
||||
if ((m_settings.m_filterChainHash != settings.m_filterChainHash) || (m_settings.m_log2Interp != settings.m_log2Interp) || force)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
m_channelizers[i]->setInterpolation(settings.m_log2Interp, settings.m_filterChainHash);
|
||||
m_streamSources[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_settings.m_steerDegrees != settings.m_steerDegrees) || force)
|
||||
{
|
||||
float steeringAngle = settings.m_steerDegrees / 180.0f;
|
||||
steeringAngle = steeringAngle < -M_PI ? -M_PI : steeringAngle > M_PI ? M_PI : steeringAngle;
|
||||
m_streamSources[1].setPhase(M_PI*cos(steeringAngle));
|
||||
}
|
||||
|
||||
if ((m_settings.m_channelOutput != settings.m_channelOutput) || force)
|
||||
{
|
||||
if (settings.m_channelOutput == 0)
|
||||
{
|
||||
m_streamSources[0].muteChannel(false);
|
||||
m_streamSources[1].muteChannel(false);
|
||||
}
|
||||
else if (settings.m_channelOutput == 1)
|
||||
{
|
||||
m_streamSources[0].muteChannel(false);
|
||||
m_streamSources[1].muteChannel(true);
|
||||
}
|
||||
else if (settings.m_channelOutput == 2)
|
||||
{
|
||||
m_streamSources[0].muteChannel(true);
|
||||
m_streamSources[1].muteChannel(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_streamSources[0].muteChannel(false);
|
||||
m_streamSources[1].muteChannel(false);
|
||||
}
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
@ -10,7 +10,10 @@ else()
|
||||
endif()
|
||||
|
||||
set(filesource_SOURCES
|
||||
filesource.cpp
|
||||
filesource.cpp
|
||||
filesourcebaseband.cpp
|
||||
filesourcereport.cpp
|
||||
filesourcesource.cpp
|
||||
filesourceplugin.cpp
|
||||
filesourcesettings.cpp
|
||||
filesourcewebapiadapter.cpp
|
||||
@ -18,6 +21,9 @@ set(filesource_SOURCES
|
||||
|
||||
set(filesource_HEADERS
|
||||
filesource.h
|
||||
filesourcebaseband.h
|
||||
filesourcereport.h
|
||||
filesourcesource.h
|
||||
filesourceplugin.h
|
||||
filesourcesettings.h
|
||||
filesourcewebapiadapter.h
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
@ -37,13 +38,12 @@
|
||||
#include "device/deviceapi.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplesink.h"
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/hbfilterchainconverter.h"
|
||||
#include "dsp/filerecord.h"
|
||||
#include "util/db.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureChannelizer, Message)
|
||||
#include "filesourcebaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgSampleRateNotification, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceName, Message)
|
||||
@ -51,10 +51,6 @@ MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceWork, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgConfigureFileSourceSeek, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceAcquisition, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgPlayPause, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceStreamData, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportFileSourceStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSource::MsgReportHeaderCRC, Message)
|
||||
|
||||
const QString FileSource::m_channelIdURI = "sdrangel.channeltx.filesource";
|
||||
const QString FileSource::m_channelId ="FileSource";
|
||||
@ -62,33 +58,24 @@ const QString FileSource::m_channelId ="FileSource";
|
||||
FileSource::FileSource(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_fileName("..."),
|
||||
m_sampleSize(0),
|
||||
m_centerFrequency(0),
|
||||
m_frequencyOffset(0),
|
||||
m_fileSampleRate(0),
|
||||
m_samplesCount(0),
|
||||
m_sampleRate(0),
|
||||
m_deviceSampleRate(0),
|
||||
m_recordLength(0),
|
||||
m_startingTimeStamp(0),
|
||||
m_running(false)
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_frequencyOffset(0),
|
||||
m_basebandSampleRate(0),
|
||||
m_linearGain(0.0)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new FileSourceBaseband();
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
|
||||
m_linearGain = 1.0f;
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
|
||||
FileSource::~FileSource()
|
||||
@ -96,145 +83,33 @@ FileSource::~FileSource()
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void FileSource::pull(Sample& sample)
|
||||
{
|
||||
Real re;
|
||||
Real im;
|
||||
|
||||
struct Sample16
|
||||
{
|
||||
int16_t real;
|
||||
int16_t imag;
|
||||
};
|
||||
|
||||
struct Sample24
|
||||
{
|
||||
int32_t real;
|
||||
int32_t imag;
|
||||
};
|
||||
|
||||
if (!m_running)
|
||||
{
|
||||
re = 0;
|
||||
im = 0;
|
||||
}
|
||||
else if (m_sampleSize == 16)
|
||||
{
|
||||
Sample16 sample16;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&sample16), sizeof(Sample16));
|
||||
|
||||
if (m_ifstream.eof()) {
|
||||
handleEOF();
|
||||
} else {
|
||||
m_samplesCount++;
|
||||
}
|
||||
|
||||
// scale to +/-1.0
|
||||
re = (sample16.real * m_linearGain) / 32760.0f;
|
||||
im = (sample16.imag * m_linearGain) / 32760.0f;
|
||||
}
|
||||
else if (m_sampleSize == 24)
|
||||
{
|
||||
Sample24 sample24;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&sample24), sizeof(Sample24));
|
||||
|
||||
if (m_ifstream.eof()) {
|
||||
handleEOF();
|
||||
} else {
|
||||
m_samplesCount++;
|
||||
}
|
||||
|
||||
// scale to +/-1.0
|
||||
re = (sample24.real * m_linearGain) / 8388608.0f;
|
||||
im = (sample24.imag * m_linearGain) / 8388608.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
re = 0;
|
||||
im = 0;
|
||||
}
|
||||
|
||||
|
||||
if (SDR_TX_SAMP_SZ == 16)
|
||||
{
|
||||
sample.setReal(re * 32768.0f);
|
||||
sample.setImag(im * 32768.0f);
|
||||
}
|
||||
else if (SDR_TX_SAMP_SZ == 24)
|
||||
{
|
||||
sample.setReal(re * 8388608.0f);
|
||||
sample.setImag(im * 8388608.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.setReal(0);
|
||||
sample.setImag(0);
|
||||
}
|
||||
|
||||
Real magsq = re*re + im*im;
|
||||
m_movingAverage(magsq);
|
||||
m_magsq = m_movingAverage.asDouble();
|
||||
m_magsqSum += magsq;
|
||||
|
||||
if (magsq > m_magsqPeak) {
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
}
|
||||
|
||||
void FileSource::pullAudio(int nbSamples)
|
||||
{
|
||||
(void) nbSamples;
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void FileSource::start()
|
||||
{
|
||||
qDebug("FileSource::start");
|
||||
m_running = true;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(true); // acquisition on
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
qDebug("FileSource::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void FileSource::stop()
|
||||
{
|
||||
qDebug("FileSource::stop");
|
||||
m_running = false;
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportFileSourceAcquisition *report = MsgReportFileSourceAcquisition::create(false); // acquisition off
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
void FileSource::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool FileSource::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
int sampleRate = notif.getSampleRate();
|
||||
|
||||
qDebug() << "FileSource::handleMessage: MsgChannelizerNotification:"
|
||||
<< " channelSampleRate: " << sampleRate
|
||||
<< " offsetFrequency: " << notif.getFrequencyOffset();
|
||||
|
||||
if (sampleRate > 0) {
|
||||
setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
|
||||
@ -242,17 +117,18 @@ bool FileSource::handleMessage(const Message& cmd)
|
||||
<< " inputSampleRate: " << notif.getSampleRate()
|
||||
<< " centerFrequency: " << notif.getCenterFrequency();
|
||||
|
||||
setCenterFrequency(notif.getCenterFrequency());
|
||||
m_deviceSampleRate = notif.getSampleRate();
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
calculateFrequencyOffset(); // This is when device sample rate changes
|
||||
setCenterFrequency(notif.getCenterFrequency());
|
||||
|
||||
// Redo the channelizer stuff with the new sample rate to re-synchronize everything
|
||||
m_channelizer->set(m_channelizer->getInputMessageQueue(),
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
// Notify source of input sample rate change
|
||||
qDebug() << "FileSource::handleMessage: DSPSignalNotification: push to source";
|
||||
DSPSignalNotification *sig = new DSPSignalNotification(notif);
|
||||
m_basebandSource->getInputMessageQueue()->push(sig);
|
||||
|
||||
if (m_guiMessageQueue)
|
||||
{
|
||||
qDebug() << "FileSource::handleMessage: DSPSignalNotification: push to GUI";
|
||||
MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate());
|
||||
m_guiMessageQueue->push(msg);
|
||||
}
|
||||
@ -266,58 +142,37 @@ bool FileSource::handleMessage(const Message& cmd)
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
m_settings.m_log2Interp = cfg.getLog2Interp();
|
||||
m_settings.m_filterChainHash = cfg.getFilterChainHash();
|
||||
|
||||
qDebug() << "FileSource::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " log2Interp: " << m_settings.m_log2Interp
|
||||
<< " filterChainHash: " << m_settings.m_filterChainHash;
|
||||
|
||||
m_channelizer->set(m_channelizer->getInputMessageQueue(),
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
|
||||
calculateFrequencyOffset(); // This is when decimation or filter chain changes
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceName::match(cmd))
|
||||
{
|
||||
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
|
||||
m_fileName = conf.getFileName();
|
||||
openFileStream();
|
||||
qDebug() << "FileSource::handleMessage: MsgConfigureFileSourceName:" << conf.getFileName();
|
||||
FileSourceBaseband::MsgConfigureFileSourceName *msg = FileSourceBaseband::MsgConfigureFileSourceName::create(conf.getFileName());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceWork::match(cmd))
|
||||
{
|
||||
MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) cmd;
|
||||
|
||||
if (conf.isWorking()) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
FileSourceBaseband::MsgConfigureFileSourceWork *msg = FileSourceBaseband::MsgConfigureFileSourceWork::create(conf.isWorking());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceSeek::match(cmd))
|
||||
{
|
||||
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
|
||||
int seekMillis = conf.getMillis();
|
||||
seekFileStream(seekMillis);
|
||||
FileSourceBaseband::MsgConfigureFileSourceSeek *msg = FileSourceBaseband::MsgConfigureFileSourceSeek::create(conf.getMillis());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceStreamTiming::match(cmd))
|
||||
{
|
||||
MsgReportFileSourceStreamTiming *report;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
report = MsgReportFileSourceStreamTiming::create(getSamplesCount());
|
||||
FileSourceReport::MsgReportFileSourceStreamTiming *report =
|
||||
FileSourceReport::MsgReportFileSourceStreamTiming::create(m_basebandSource->getSamplesCount());
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
|
||||
@ -352,120 +207,22 @@ bool FileSource::deserialize(const QByteArray& data)
|
||||
}
|
||||
}
|
||||
|
||||
void FileSource::openFileStream()
|
||||
{
|
||||
//stop();
|
||||
|
||||
if (m_ifstream.is_open()) {
|
||||
m_ifstream.close();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
m_ifstream.open(m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
|
||||
#else
|
||||
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
|
||||
#endif
|
||||
quint64 fileSize = m_ifstream.tellg();
|
||||
m_samplesCount = 0;
|
||||
|
||||
if (fileSize > sizeof(FileRecord::Header))
|
||||
{
|
||||
FileRecord::Header header;
|
||||
m_ifstream.seekg(0,std::ios_base::beg);
|
||||
bool crcOK = FileRecord::readHeader(m_ifstream, header);
|
||||
m_fileSampleRate = header.sampleRate;
|
||||
m_centerFrequency = header.centerFrequency;
|
||||
m_startingTimeStamp = header.startTimeStamp;
|
||||
m_sampleSize = header.sampleSize;
|
||||
QString crcHex = QString("%1").arg(header.crc32 , 0, 16);
|
||||
|
||||
if (crcOK)
|
||||
{
|
||||
qDebug("FileSource::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
|
||||
m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical("FileSource::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
|
||||
m_recordLength = 0;
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI()) {
|
||||
MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(crcOK);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recordLength = 0;
|
||||
}
|
||||
|
||||
qDebug() << "FileSource::openFileStream: " << m_fileName.toStdString().c_str()
|
||||
<< " fileSize: " << fileSize << " bytes"
|
||||
<< " length: " << m_recordLength << " seconds"
|
||||
<< " sample rate: " << m_fileSampleRate << " S/s"
|
||||
<< " center frequency: " << m_centerFrequency << " Hz"
|
||||
<< " sample size: " << m_sampleSize << " bits"
|
||||
<< " starting TS: " << m_startingTimeStamp << "s";
|
||||
|
||||
if (getMessageQueueToGUI()) {
|
||||
MsgReportFileSourceStreamData *report = MsgReportFileSourceStreamData::create(m_fileSampleRate,
|
||||
m_sampleSize,
|
||||
m_centerFrequency,
|
||||
m_startingTimeStamp,
|
||||
m_recordLength); // file stream data
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
|
||||
if (m_recordLength == 0) {
|
||||
m_ifstream.close();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSource::seekFileStream(int seekMillis)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
if ((m_ifstream.is_open()) && !m_running)
|
||||
{
|
||||
quint64 seekPoint = ((m_recordLength * seekMillis) / 1000) * m_fileSampleRate;
|
||||
m_samplesCount = seekPoint;
|
||||
seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header)
|
||||
m_ifstream.clear();
|
||||
m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSource::handleEOF()
|
||||
{
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportFileSourceStreamTiming *report = MsgReportFileSourceStreamTiming::create(getSamplesCount());
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
|
||||
if (m_settings.m_loop)
|
||||
{
|
||||
stop();
|
||||
seekFileStream(0);
|
||||
start();
|
||||
}
|
||||
else
|
||||
{
|
||||
stop();
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgPlayPause *report = MsgPlayPause::create(false);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSource::applySettings(const FileSourceSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "FileSource::applySettings:"
|
||||
<< " force: " << force;
|
||||
<< "m_fileName:" << settings.m_fileName
|
||||
<< "m_loop:" << settings.m_loop
|
||||
<< "m_gainDB:" << settings.m_gainDB
|
||||
<< "m_log2Interp:" << settings.m_log2Interp
|
||||
<< "m_filterChainHash:" << settings.m_filterChainHash
|
||||
<< "m_useReverseAPI:" << settings.m_useReverseAPI
|
||||
<< "m_reverseAPIAddress:" << settings.m_reverseAPIAddress
|
||||
<< "m_reverseAPIChannelIndex:" << settings.m_reverseAPIChannelIndex
|
||||
<< "m_reverseAPIDeviceIndex:" << settings.m_reverseAPIDeviceIndex
|
||||
<< "m_reverseAPIPort:" << settings.m_reverseAPIPort
|
||||
<< "m_rgbColor:" << settings.m_rgbColor
|
||||
<< "m_title:" << settings.m_title
|
||||
<< " force: " << force;
|
||||
|
||||
QList<QString> reverseAPIKeys;
|
||||
|
||||
@ -475,13 +232,15 @@ void FileSource::applySettings(const FileSourceSettings& settings, bool force)
|
||||
if ((m_settings.m_fileName != settings.m_fileName) || force) {
|
||||
reverseAPIKeys.append("fileName");
|
||||
}
|
||||
|
||||
if ((m_settings.m_gainDB != settings.m_gainDB) || force)
|
||||
{
|
||||
m_linearGain = CalcDb::powerFromdB(settings.m_gainDB);
|
||||
reverseAPIKeys.append("gainDB");
|
||||
}
|
||||
|
||||
FileSourceBaseband::MsgConfigureFileSourceBaseband *msg = FileSourceBaseband::MsgConfigureFileSourceBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -509,7 +268,7 @@ void FileSource::validateFilterChainHash(FileSourceSettings& settings)
|
||||
void FileSource::calculateFrequencyOffset()
|
||||
{
|
||||
double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Interp, m_settings.m_filterChainHash);
|
||||
m_frequencyOffset = m_deviceSampleRate * shiftFactor;
|
||||
m_frequencyOffset = m_basebandSampleRate * shiftFactor;
|
||||
}
|
||||
|
||||
int FileSource::webapiSettingsGet(
|
||||
@ -630,12 +389,16 @@ void FileSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respon
|
||||
{
|
||||
qint64 t_sec = 0;
|
||||
qint64 t_msec = 0;
|
||||
quint64 samplesCount = getSamplesCount();
|
||||
quint64 samplesCount = m_basebandSource->getSamplesCount();
|
||||
uint32_t fileSampleRate = m_basebandSource->getFileSampleRate();
|
||||
quint64 startingTimeStamp = m_basebandSource->getStartingTimeStamp();
|
||||
quint64 fileRecordLength = m_basebandSource->getRecordLength();
|
||||
quint32 fileSampleSize = m_basebandSource->getFileSampleSize();
|
||||
|
||||
if (m_fileSampleRate > 0)
|
||||
if (fileSampleRate > 0)
|
||||
{
|
||||
t_sec = samplesCount / m_fileSampleRate;
|
||||
t_msec = (samplesCount - (t_sec * m_fileSampleRate)) * 1000 / m_fileSampleRate;
|
||||
t_sec = samplesCount / fileSampleRate;
|
||||
t_msec = (samplesCount - (t_sec * fileSampleRate)) * 1000 / fileSampleRate;
|
||||
}
|
||||
|
||||
QTime t(0, 0, 0, 0);
|
||||
@ -643,20 +406,20 @@ void FileSource::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& respon
|
||||
t = t.addMSecs(t_msec);
|
||||
response.getFileSourceReport()->setElapsedTime(new QString(t.toString("HH:mm:ss.zzz")));
|
||||
|
||||
qint64 startingTimeStampMsec = m_startingTimeStamp * 1000LL;
|
||||
qint64 startingTimeStampMsec = startingTimeStamp * 1000LL;
|
||||
QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec);
|
||||
dt = dt.addSecs(t_sec);
|
||||
dt = dt.addMSecs(t_msec);
|
||||
response.getFileSourceReport()->setAbsoluteTime(new QString(dt.toString("yyyy-MM-dd HH:mm:ss.zzz")));
|
||||
|
||||
QTime recordLength(0, 0, 0, 0);
|
||||
recordLength = recordLength.addSecs(m_recordLength);
|
||||
recordLength = recordLength.addSecs(fileRecordLength);
|
||||
response.getFileSourceReport()->setDurationTime(new QString(recordLength.toString("HH:mm:ss")));
|
||||
|
||||
response.getFileSourceReport()->setFileName(new QString(m_settings.m_fileName));
|
||||
response.getFileSourceReport()->setFileSampleRate(m_fileSampleRate);
|
||||
response.getFileSourceReport()->setFileSampleSize(m_sampleSize);
|
||||
response.getFileSourceReport()->setSampleRate(m_sampleRate);
|
||||
response.getFileSourceReport()->setFileSampleRate(fileSampleRate);
|
||||
response.getFileSourceReport()->setFileSampleSize(fileSampleSize);
|
||||
response.getFileSourceReport()->setSampleRate(m_basebandSampleRate);
|
||||
response.getFileSourceReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
}
|
||||
|
||||
@ -696,13 +459,14 @@ void FileSource::webapiReverseSendSettings(QList<QString>& channelSettingsKeys,
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -717,10 +481,23 @@ void FileSource::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("FileSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("FileSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void FileSource::getMagSqLevels(double& avg, double& peak, int& nbSamples) const
|
||||
{
|
||||
m_basebandSource->getMagSqLevels(avg, peak, nbSamples);
|
||||
}
|
||||
|
||||
void FileSource::propagateMessageQueueToGUI()
|
||||
{
|
||||
m_basebandSource->setMessageQueueToGUI(getMessageQueueToGUI());
|
||||
}
|
@ -33,40 +33,18 @@
|
||||
#include "util/message.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "filesourcesettings.h"
|
||||
#include "filesourcereport.h"
|
||||
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class DeviceAPI;
|
||||
class FileSourceThread;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class DeviceAPI;
|
||||
class FileSourceBaseband;
|
||||
|
||||
class FileSource : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getLog2Interp() const { return m_log2Interp; }
|
||||
int getFilterChainHash() const { return m_filterChainHash; }
|
||||
|
||||
static MsgConfigureChannelizer* create(unsigned int m_log2Interp, unsigned int m_filterChainHash) {
|
||||
return new MsgConfigureChannelizer(m_log2Interp, m_filterChainHash);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_log2Interp;
|
||||
unsigned int m_filterChainHash;
|
||||
|
||||
MsgConfigureChannelizer(unsigned int log2Interp, unsigned int filterChainHash) :
|
||||
Message(),
|
||||
m_log2Interp(log2Interp),
|
||||
m_filterChainHash(filterChainHash)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureFileSource : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
@ -207,113 +185,13 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgPlayPause : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getPlayPause() const { return m_playPause; }
|
||||
|
||||
static MsgPlayPause* create(bool playPause) {
|
||||
return new MsgPlayPause(playPause);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_playPause;
|
||||
|
||||
MsgPlayPause(bool playPause) :
|
||||
Message(),
|
||||
m_playPause(playPause)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportFileSourceStreamData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
quint32 getSampleSize() const { return m_sampleSize; }
|
||||
quint64 getCenterFrequency() const { return m_centerFrequency; }
|
||||
quint64 getStartingTimeStamp() const { return m_startingTimeStamp; }
|
||||
quint64 getRecordLength() const { return m_recordLength; }
|
||||
|
||||
static MsgReportFileSourceStreamData* create(int sampleRate,
|
||||
quint32 sampleSize,
|
||||
quint64 centerFrequency,
|
||||
quint64 startingTimeStamp,
|
||||
quint64 recordLength)
|
||||
{
|
||||
return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
quint32 m_sampleSize;
|
||||
quint64 m_centerFrequency;
|
||||
quint64 m_startingTimeStamp;
|
||||
quint64 m_recordLength;
|
||||
|
||||
MsgReportFileSourceStreamData(int sampleRate,
|
||||
quint32 sampleSize,
|
||||
quint64 centerFrequency,
|
||||
quint64 startingTimeStamp,
|
||||
quint64 recordLength) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_sampleSize(sampleSize),
|
||||
m_centerFrequency(centerFrequency),
|
||||
m_startingTimeStamp(startingTimeStamp),
|
||||
m_recordLength(recordLength)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportFileSourceStreamTiming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
quint64 getSamplesCount() const { return m_samplesCount; }
|
||||
|
||||
static MsgReportFileSourceStreamTiming* create(quint64 samplesCount)
|
||||
{
|
||||
return new MsgReportFileSourceStreamTiming(samplesCount);
|
||||
}
|
||||
|
||||
protected:
|
||||
quint64 m_samplesCount;
|
||||
|
||||
MsgReportFileSourceStreamTiming(quint64 samplesCount) :
|
||||
Message(),
|
||||
m_samplesCount(samplesCount)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportHeaderCRC : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool isOK() const { return m_ok; }
|
||||
|
||||
static MsgReportHeaderCRC* create(bool ok) {
|
||||
return new MsgReportHeaderCRC(ok);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_ok;
|
||||
|
||||
MsgReportHeaderCRC(bool ok) :
|
||||
Message(),
|
||||
m_ok(ok)
|
||||
{ }
|
||||
};
|
||||
|
||||
FileSource(DeviceAPI *deviceAPI);
|
||||
~FileSource();
|
||||
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -357,78 +235,33 @@ public:
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
/** Set center frequency given in Hz */
|
||||
void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; }
|
||||
void setCenterFrequency(uint64_t centerFrequency) { m_frequencyOffset = centerFrequency; }
|
||||
|
||||
/** Set sample rate given in Hz */
|
||||
void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; }
|
||||
void setSampleRate(uint32_t sampleRate) { m_basebandSampleRate = sampleRate; }
|
||||
|
||||
quint64 getSamplesCount() const { return m_samplesCount; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||
{
|
||||
if (m_magsqCount > 0)
|
||||
{
|
||||
m_magsq = m_magsqSum / m_magsqCount;
|
||||
m_magSqLevelStore.m_magsq = m_magsq;
|
||||
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
|
||||
}
|
||||
|
||||
avg = m_magSqLevelStore.m_magsq;
|
||||
peak = m_magSqLevelStore.m_magsqPeak;
|
||||
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
|
||||
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
double getMagSq() const;
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) const;
|
||||
void propagateMessageQueueToGUI();
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
private:
|
||||
struct MagSqLevelsStore
|
||||
{
|
||||
MagSqLevelsStore() :
|
||||
m_magsq(1e-12),
|
||||
m_magsqPeak(1e-12)
|
||||
{}
|
||||
double m_magsq;
|
||||
double m_magsqPeak;
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
QMutex m_mutex;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
QThread *m_thread;
|
||||
FileSourceBaseband* m_basebandSource;
|
||||
FileSourceSettings m_settings;
|
||||
std::ifstream m_ifstream;
|
||||
QString m_fileName;
|
||||
quint32 m_sampleSize;
|
||||
quint64 m_centerFrequency;
|
||||
int64_t m_frequencyOffset;
|
||||
uint32_t m_fileSampleRate;
|
||||
quint64 m_samplesCount;
|
||||
uint32_t m_sampleRate;
|
||||
uint32_t m_deviceSampleRate;
|
||||
quint64 m_recordLength; //!< record length in seconds computed from file size
|
||||
quint64 m_startingTimeStamp;
|
||||
QTimer m_masterTimer;
|
||||
bool m_running;
|
||||
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
uint64_t m_frequencyOffset;
|
||||
uint32_t m_basebandSampleRate;
|
||||
double m_linearGain;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
double m_linearGain;
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekMillis);
|
||||
void handleEOF();
|
||||
void applySettings(const FileSourceSettings& settings, bool force = false);
|
||||
static void validateFilterChainHash(FileSourceSettings& settings);
|
||||
void calculateFrequencyOffset();
|
||||
|
234
plugins/channeltx/filesource/filesourcebaseband.cpp
Normal file
234
plugins/channeltx/filesource/filesourcebaseband.cpp
Normal file
@ -0,0 +1,234 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "filesourcebaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceBaseband::MsgConfigureFileSourceBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceBaseband::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceBaseband::MsgConfigureFileSourceName, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceBaseband::MsgConfigureFileSourceWork, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceBaseband::MsgConfigureFileSourceSeek, Message)
|
||||
|
||||
FileSourceBaseband::FileSourceBaseband() :
|
||||
m_avg(0.0),
|
||||
m_peak(0.0),
|
||||
m_nbSamples(1),
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("FileSourceBaseband::FileSourceBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&FileSourceBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
FileSourceBaseband::~FileSourceBaseband()
|
||||
{
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void FileSourceBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void FileSourceBaseband::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 FileSourceBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
|
||||
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.getMagSqLevels(m_avg, m_peak, m_nbSamples);
|
||||
}
|
||||
|
||||
void FileSourceBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void FileSourceBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FileSourceBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureFileSourceBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureFileSourceBaseband& cfg = (MsgConfigureFileSourceBaseband&) cmd;
|
||||
qDebug() << "FileSourceBaseband::handleMessage: MsgConfigureFileSourceBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
m_settings.m_log2Interp = cfg.getLog2Interp();
|
||||
m_settings.m_filterChainHash = cfg.getFilterChainHash();
|
||||
|
||||
qDebug() << "FileSourceBaseband::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " log2Interp: " << m_settings.m_log2Interp
|
||||
<< " filterChainHash: " << m_settings.m_filterChainHash;
|
||||
m_channelizer->setInterpolation(m_settings.m_log2Interp, m_settings.m_filterChainHash);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "FileSourceBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(notif.getSampleRate()));
|
||||
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceName::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
|
||||
qDebug() << "FileSourceBaseband::handleMessage: MsgConfigureFileSourceName: " << conf.getFileName();
|
||||
m_source.openFileStream(conf.getFileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceWork::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureFileSourceWork& conf = (MsgConfigureFileSourceWork&) cmd;
|
||||
qDebug() << "FileSourceBaseband::handleMessage: MsgConfigureFileSourceWork: " << conf.isWorking();
|
||||
m_source.setRunning(conf.isWorking());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceSeek::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
|
||||
m_source.seekFileStream(conf.getMillis());
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceBaseband::applySettings(const FileSourceSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "FileSourceBaseband::applySettings:"
|
||||
<< "m_fileName:" << settings.m_fileName
|
||||
<< "m_loop:" << settings.m_loop
|
||||
<< "m_gainDB:" << settings.m_gainDB
|
||||
<< "m_log2Interp:" << settings.m_log2Interp
|
||||
<< "m_filterChainHash:" << settings.m_filterChainHash
|
||||
<< " force: " << force;
|
||||
|
||||
if ((m_settings.m_filterChainHash != settings.m_filterChainHash)
|
||||
|| (m_settings.m_log2Interp != settings.m_log2Interp) || force)
|
||||
{
|
||||
m_channelizer->setInterpolation(settings.m_log2Interp, settings.m_filterChainHash);
|
||||
}
|
||||
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int FileSourceBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
quint64 FileSourceBaseband::getSamplesCount() const
|
||||
{
|
||||
return m_source.getSamplesCount();
|
||||
}
|
184
plugins/channeltx/filesource/filesourcebaseband.h
Normal file
184
plugins/channeltx/filesource/filesourcebaseband.h
Normal file
@ -0,0 +1,184 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FILESOURCEBASEBAND_H
|
||||
#define INCLUDE_FILESOURCEBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "filesourcesource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class FileSourceBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureFileSourceBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const FileSourceSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureFileSourceBaseband* create(const FileSourceSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureFileSourceBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
FileSourceSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureFileSourceBaseband(const FileSourceSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getLog2Interp() const { return m_log2Interp; }
|
||||
int getFilterChainHash() const { return m_filterChainHash; }
|
||||
|
||||
static MsgConfigureChannelizer* create(unsigned int m_log2Interp, unsigned int m_filterChainHash) {
|
||||
return new MsgConfigureChannelizer(m_log2Interp, m_filterChainHash);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_log2Interp;
|
||||
unsigned int m_filterChainHash;
|
||||
|
||||
MsgConfigureChannelizer(unsigned int log2Interp, unsigned int filterChainHash) :
|
||||
Message(),
|
||||
m_log2Interp(log2Interp),
|
||||
m_filterChainHash(filterChainHash)
|
||||
{ }
|
||||
};
|
||||
|
||||
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 MsgConfigureFileSourceWork : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool isWorking() const { return m_working; }
|
||||
|
||||
static MsgConfigureFileSourceWork* create(bool working)
|
||||
{
|
||||
return new MsgConfigureFileSourceWork(working);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_working;
|
||||
|
||||
MsgConfigureFileSourceWork(bool working) :
|
||||
Message(),
|
||||
m_working(working)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureFileSourceSeek : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getMillis() const { return m_seekMillis; }
|
||||
|
||||
static MsgConfigureFileSourceSeek* create(int seekMillis)
|
||||
{
|
||||
return new MsgConfigureFileSourceSeek(seekMillis);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_seekMillis; //!< millis of seek position from the beginning 0..1000
|
||||
|
||||
MsgConfigureFileSourceSeek(int seekMillis) :
|
||||
Message(),
|
||||
m_seekMillis(seekMillis)
|
||||
{ }
|
||||
};
|
||||
|
||||
FileSourceBaseband();
|
||||
~FileSourceBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_source.setMessageQueueToGUI(messageQueue); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
int getChannelSampleRate() const;
|
||||
quint64 getSamplesCount() const;
|
||||
|
||||
uint32_t getFileSampleRate() const { return m_source.getFileSampleRate(); }
|
||||
quint64 getStartingTimeStamp() const { return m_source.getStartingTimeStamp(); }
|
||||
quint64 getRecordLength() const { return m_source.getRecordLength(); }
|
||||
quint32 getFileSampleSize() const { return m_source.getFileSampleSize(); }
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) const
|
||||
{
|
||||
avg = m_avg;
|
||||
peak = m_peak;
|
||||
nbSamples = m_nbSamples;
|
||||
}
|
||||
|
||||
private:
|
||||
SampleSourceFifo m_sampleFifo;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
FileSourceSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
FileSourceSettings m_settings;
|
||||
double m_avg;
|
||||
double m_peak;
|
||||
int m_nbSamples;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const FileSourceSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_FILESOURCEBASEBAND_H
|
@ -19,8 +19,6 @@
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
#include "filesourcegui.h"
|
||||
|
||||
#include "device/deviceapi.h"
|
||||
#include "device/deviceuiset.h"
|
||||
#include "dsp/hbfilterchainconverter.h"
|
||||
@ -29,6 +27,8 @@
|
||||
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include "filesourcereport.h"
|
||||
#include "filesourcegui.h"
|
||||
#include "filesource.h"
|
||||
#include "ui_filesourcegui.h"
|
||||
|
||||
@ -110,24 +110,24 @@ bool FileSourceGUI::handleMessage(const Message& message)
|
||||
updateWithAcquisition();
|
||||
return true;
|
||||
}
|
||||
else if (FileSource::MsgReportFileSourceStreamData::match(message))
|
||||
else if (FileSourceReport::MsgReportFileSourceStreamData::match(message))
|
||||
{
|
||||
m_fileSampleRate = ((FileSource::MsgReportFileSourceStreamData&)message).getSampleRate();
|
||||
m_fileSampleSize = ((FileSource::MsgReportFileSourceStreamData&)message).getSampleSize();
|
||||
m_startingTimeStamp = ((FileSource::MsgReportFileSourceStreamData&)message).getStartingTimeStamp();
|
||||
m_recordLength = ((FileSource::MsgReportFileSourceStreamData&)message).getRecordLength();
|
||||
m_fileSampleRate = ((FileSourceReport::MsgReportFileSourceStreamData&)message).getSampleRate();
|
||||
m_fileSampleSize = ((FileSourceReport::MsgReportFileSourceStreamData&)message).getSampleSize();
|
||||
m_startingTimeStamp = ((FileSourceReport::MsgReportFileSourceStreamData&)message).getStartingTimeStamp();
|
||||
m_recordLength = ((FileSourceReport::MsgReportFileSourceStreamData&)message).getRecordLength();
|
||||
updateWithStreamData();
|
||||
return true;
|
||||
}
|
||||
else if (FileSource::MsgReportFileSourceStreamTiming::match(message))
|
||||
else if (FileSourceReport::MsgReportFileSourceStreamTiming::match(message))
|
||||
{
|
||||
m_samplesCount = ((FileSource::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
|
||||
m_samplesCount = ((FileSourceReport::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
|
||||
updateWithStreamTime();
|
||||
return true;
|
||||
}
|
||||
else if (FileSource::MsgPlayPause::match(message))
|
||||
else if (FileSourceReport::MsgPlayPause::match(message))
|
||||
{
|
||||
FileSource::MsgPlayPause& notif = (FileSource::MsgPlayPause&) message;
|
||||
FileSourceReport::MsgPlayPause& notif = (FileSourceReport::MsgPlayPause&) message;
|
||||
bool checked = notif.getPlayPause();
|
||||
ui->play->setChecked(checked);
|
||||
ui->navTime->setEnabled(!checked);
|
||||
@ -135,9 +135,9 @@ bool FileSourceGUI::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (FileSource::MsgReportHeaderCRC::match(message))
|
||||
else if (FileSourceReport::MsgReportHeaderCRC::match(message))
|
||||
{
|
||||
FileSource::MsgReportHeaderCRC& notif = (FileSource::MsgReportHeaderCRC&) message;
|
||||
FileSourceReport::MsgReportHeaderCRC& notif = (FileSourceReport::MsgReportHeaderCRC&) message;
|
||||
|
||||
if (notif.isOK()) {
|
||||
ui->crcLabel->setStyleSheet("QLabel { background-color : green; }");
|
||||
@ -181,6 +181,7 @@ FileSourceGUI::FileSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
|
||||
|
||||
m_fileSource = (FileSource*) channelTx;
|
||||
m_fileSource->setMessageQueueToGUI(getInputMessageQueue());
|
||||
m_fileSource->propagateMessageQueueToGUI();
|
||||
|
||||
connect(&(m_deviceUISet->m_deviceAPI->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
|
||||
|
||||
@ -231,17 +232,6 @@ void FileSourceGUI::applySettings(bool force)
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceGUI::applyChannelSettings()
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
FileSource::MsgConfigureChannelizer *msgChan = FileSource::MsgConfigureChannelizer::create(
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
m_fileSource->getInputMessageQueue()->push(msgChan);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceGUI::configureFileName()
|
||||
{
|
||||
qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str();
|
||||
@ -473,7 +463,7 @@ void FileSourceGUI::applyPosition()
|
||||
ui->filterChainText->setText(s);
|
||||
|
||||
displayRateAndShift();
|
||||
applyChannelSettings();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void FileSourceGUI::tick()
|
||||
|
@ -86,7 +86,6 @@ private:
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void applyChannelSettings();
|
||||
void configureFileName();
|
||||
void updateWithAcquisition();
|
||||
void updateWithStreamData();
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor FileSourcePlugin::m_pluginDescriptor = {
|
||||
QString("File channel source"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
29
plugins/channeltx/filesource/filesourcereport.cpp
Normal file
29
plugins/channeltx/filesource/filesourcereport.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "filesourcereport.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceReport::MsgPlayPause, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceReport::MsgReportFileSourceStreamData, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceReport::MsgReportFileSourceStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FileSourceReport::MsgReportHeaderCRC, Message)
|
||||
|
||||
FileSourceReport::FileSourceReport()
|
||||
{}
|
||||
|
||||
FileSourceReport::~FileSourceReport()
|
||||
{}
|
130
plugins/channeltx/filesource/filesourcereport.h
Normal file
130
plugins/channeltx/filesource/filesourcereport.h
Normal file
@ -0,0 +1,130 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEREPORT_H_
|
||||
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEREPORT_H_
|
||||
|
||||
#include <QObject>
|
||||
#include "util/message.h"
|
||||
|
||||
class FileSourceReport : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgReportHeaderCRC : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool isOK() const { return m_ok; }
|
||||
|
||||
static MsgReportHeaderCRC* create(bool ok) {
|
||||
return new MsgReportHeaderCRC(ok);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_ok;
|
||||
|
||||
MsgReportHeaderCRC(bool ok) :
|
||||
Message(),
|
||||
m_ok(ok)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportFileSourceStreamData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
quint32 getSampleSize() const { return m_sampleSize; }
|
||||
quint64 getCenterFrequency() const { return m_centerFrequency; }
|
||||
quint64 getStartingTimeStamp() const { return m_startingTimeStamp; }
|
||||
quint64 getRecordLength() const { return m_recordLength; }
|
||||
|
||||
static MsgReportFileSourceStreamData* create(int sampleRate,
|
||||
quint32 sampleSize,
|
||||
quint64 centerFrequency,
|
||||
quint64 startingTimeStamp,
|
||||
quint64 recordLength)
|
||||
{
|
||||
return new MsgReportFileSourceStreamData(sampleRate, sampleSize, centerFrequency, startingTimeStamp, recordLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
quint32 m_sampleSize;
|
||||
quint64 m_centerFrequency;
|
||||
quint64 m_startingTimeStamp;
|
||||
quint64 m_recordLength;
|
||||
|
||||
MsgReportFileSourceStreamData(int sampleRate,
|
||||
quint32 sampleSize,
|
||||
quint64 centerFrequency,
|
||||
quint64 startingTimeStamp,
|
||||
quint64 recordLength) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_sampleSize(sampleSize),
|
||||
m_centerFrequency(centerFrequency),
|
||||
m_startingTimeStamp(startingTimeStamp),
|
||||
m_recordLength(recordLength)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportFileSourceStreamTiming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
quint64 getSamplesCount() const { return m_samplesCount; }
|
||||
|
||||
static MsgReportFileSourceStreamTiming* create(quint64 samplesCount)
|
||||
{
|
||||
return new MsgReportFileSourceStreamTiming(samplesCount);
|
||||
}
|
||||
|
||||
protected:
|
||||
quint64 m_samplesCount;
|
||||
|
||||
MsgReportFileSourceStreamTiming(quint64 samplesCount) :
|
||||
Message(),
|
||||
m_samplesCount(samplesCount)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgPlayPause : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool getPlayPause() const { return m_playPause; }
|
||||
|
||||
static MsgPlayPause* create(bool playPause) {
|
||||
return new MsgPlayPause(playPause);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool m_playPause;
|
||||
|
||||
MsgPlayPause(bool playPause) :
|
||||
Message(),
|
||||
m_playPause(playPause)
|
||||
{ }
|
||||
};
|
||||
|
||||
FileSourceReport();
|
||||
~FileSourceReport();
|
||||
};
|
||||
|
||||
#endif // PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEREPORT_H_
|
283
plugins/channeltx/filesource/filesourcesource.cpp
Normal file
283
plugins/channeltx/filesource/filesourcesource.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "filesourcesource.h"
|
||||
#include "filesourcereport.h"
|
||||
|
||||
#if (defined _WIN32_) || (defined _MSC_VER)
|
||||
#include "windows_time.h"
|
||||
#include <stdint.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplesink.h"
|
||||
#include "dsp/hbfilterchainconverter.h"
|
||||
#include "dsp/filerecord.h"
|
||||
#include "util/db.h"
|
||||
|
||||
FileSourceSource::FileSourceSource() :
|
||||
m_fileName("..."),
|
||||
m_sampleSize(0),
|
||||
m_centerFrequency(0),
|
||||
m_frequencyOffset(0),
|
||||
m_fileSampleRate(0),
|
||||
m_samplesCount(0),
|
||||
m_sampleRate(0),
|
||||
m_deviceSampleRate(0),
|
||||
m_recordLength(0),
|
||||
m_startingTimeStamp(0),
|
||||
m_running(false),
|
||||
m_guiMessageQueue(nullptr)
|
||||
{
|
||||
m_linearGain = 1.0f;
|
||||
m_magsq = 0.0f;
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
|
||||
FileSourceSource::~FileSourceSource()
|
||||
{
|
||||
}
|
||||
|
||||
void FileSourceSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FileSourceSource::pullOne(Sample& sample)
|
||||
{
|
||||
Real re;
|
||||
Real im;
|
||||
|
||||
struct Sample16
|
||||
{
|
||||
int16_t real;
|
||||
int16_t imag;
|
||||
};
|
||||
|
||||
struct Sample24
|
||||
{
|
||||
int32_t real;
|
||||
int32_t imag;
|
||||
};
|
||||
|
||||
if (!m_running)
|
||||
{
|
||||
re = 0;
|
||||
im = 0;
|
||||
}
|
||||
else if (m_sampleSize == 16)
|
||||
{
|
||||
Sample16 sample16;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&sample16), sizeof(Sample16));
|
||||
|
||||
if (m_ifstream.eof()) {
|
||||
handleEOF();
|
||||
} else {
|
||||
m_samplesCount++;
|
||||
}
|
||||
|
||||
// scale to +/-1.0
|
||||
re = (sample16.real * m_linearGain) / 32760.0f;
|
||||
im = (sample16.imag * m_linearGain) / 32760.0f;
|
||||
}
|
||||
else if (m_sampleSize == 24)
|
||||
{
|
||||
Sample24 sample24;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&sample24), sizeof(Sample24));
|
||||
|
||||
if (m_ifstream.eof()) {
|
||||
handleEOF();
|
||||
} else {
|
||||
m_samplesCount++;
|
||||
}
|
||||
|
||||
// scale to +/-1.0
|
||||
re = (sample24.real * m_linearGain) / 8388608.0f;
|
||||
im = (sample24.imag * m_linearGain) / 8388608.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
re = 0;
|
||||
im = 0;
|
||||
}
|
||||
|
||||
|
||||
if (SDR_TX_SAMP_SZ == 16)
|
||||
{
|
||||
sample.setReal(re * 32768.0f);
|
||||
sample.setImag(im * 32768.0f);
|
||||
}
|
||||
else if (SDR_TX_SAMP_SZ == 24)
|
||||
{
|
||||
sample.setReal(re * 8388608.0f);
|
||||
sample.setImag(im * 8388608.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.setReal(0);
|
||||
sample.setImag(0);
|
||||
}
|
||||
|
||||
Real magsq = re*re + im*im;
|
||||
m_movingAverage(magsq);
|
||||
m_magsq = m_movingAverage.asDouble();
|
||||
m_magsqSum += magsq;
|
||||
|
||||
if (magsq > m_magsqPeak) {
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
|
||||
m_magsqCount++;
|
||||
}
|
||||
|
||||
void FileSourceSource::openFileStream(const QString& fileName)
|
||||
{
|
||||
m_fileName = fileName;
|
||||
|
||||
if (m_ifstream.is_open()) {
|
||||
m_ifstream.close();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
m_ifstream.open(m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
|
||||
#else
|
||||
m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
|
||||
#endif
|
||||
quint64 fileSize = m_ifstream.tellg();
|
||||
m_samplesCount = 0;
|
||||
|
||||
if (fileSize > sizeof(FileRecord::Header))
|
||||
{
|
||||
FileRecord::Header header;
|
||||
m_ifstream.seekg(0,std::ios_base::beg);
|
||||
bool crcOK = FileRecord::readHeader(m_ifstream, header);
|
||||
m_fileSampleRate = header.sampleRate;
|
||||
m_centerFrequency = header.centerFrequency;
|
||||
m_startingTimeStamp = header.startTimeStamp;
|
||||
m_sampleSize = header.sampleSize;
|
||||
QString crcHex = QString("%1").arg(header.crc32 , 0, 16);
|
||||
|
||||
if (crcOK)
|
||||
{
|
||||
qDebug("FileSourceSource::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
|
||||
m_recordLength = (fileSize - sizeof(FileRecord::Header)) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical("FileSourceSource::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
|
||||
m_recordLength = 0;
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
FileSourceReport::MsgReportHeaderCRC *report = FileSourceReport::MsgReportHeaderCRC::create(crcOK);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recordLength = 0;
|
||||
}
|
||||
|
||||
qDebug() << "FileSourceSource::openFileStream: " << m_fileName.toStdString().c_str()
|
||||
<< " fileSize: " << fileSize << " bytes"
|
||||
<< " length: " << m_recordLength << " seconds"
|
||||
<< " sample rate: " << m_fileSampleRate << " S/s"
|
||||
<< " center frequency: " << m_centerFrequency << " Hz"
|
||||
<< " sample size: " << m_sampleSize << " bits"
|
||||
<< " starting TS: " << m_startingTimeStamp << "s";
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
FileSourceReport::MsgReportFileSourceStreamData *report = FileSourceReport::MsgReportFileSourceStreamData::create(m_fileSampleRate,
|
||||
m_sampleSize,
|
||||
m_centerFrequency,
|
||||
m_startingTimeStamp,
|
||||
m_recordLength); // file stream data
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
|
||||
if (m_recordLength == 0) {
|
||||
m_ifstream.close();
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceSource::seekFileStream(int seekMillis)
|
||||
{
|
||||
if ((m_ifstream.is_open()) && !m_running)
|
||||
{
|
||||
quint64 seekPoint = ((m_recordLength * seekMillis) / 1000) * m_fileSampleRate;
|
||||
m_samplesCount = seekPoint;
|
||||
seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileSink::Header)
|
||||
m_ifstream.clear();
|
||||
m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceSource::handleEOF()
|
||||
{
|
||||
if (!m_ifstream.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
FileSourceReport::MsgReportFileSourceStreamTiming *report = FileSourceReport::MsgReportFileSourceStreamTiming::create(getSamplesCount());
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
|
||||
if (m_settings.m_loop)
|
||||
{
|
||||
m_ifstream.clear();
|
||||
m_ifstream.seekg(0, std::ios::beg);
|
||||
m_samplesCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
FileSourceReport::MsgPlayPause *report = FileSourceReport::MsgPlayPause::create(false);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileSourceSource::applySettings(const FileSourceSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "FileSourceSource::applySettings:"
|
||||
<< "m_fileName:" << settings.m_fileName
|
||||
<< "m_loop:" << settings.m_loop
|
||||
<< "m_gainDB:" << settings.m_gainDB
|
||||
<< "m_log2Interp:" << settings.m_log2Interp
|
||||
<< "m_filterChainHash:" << settings.m_filterChainHash
|
||||
<< " force: " << force;
|
||||
|
||||
m_settings = settings;
|
||||
}
|
127
plugins/channeltx/filesource/filesourcesource.h
Normal file
127
plugins/channeltx/filesource/filesourcesource.h
Normal file
@ -0,0 +1,127 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESOURCE_H_
|
||||
#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESOURCE_H_
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QTimer>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "filesourcesettings.h"
|
||||
|
||||
class MessageQueue;
|
||||
|
||||
class FileSourceSource : public ChannelSampleSource {
|
||||
public:
|
||||
FileSourceSource();
|
||||
~FileSourceSource();
|
||||
|
||||
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
|
||||
virtual void pullOne(Sample& sample);
|
||||
virtual void prefetch(unsigned int nbSamples) {}
|
||||
|
||||
/** Set center frequency given in Hz */
|
||||
void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; }
|
||||
|
||||
/** Set sample rate given in Hz */
|
||||
void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; }
|
||||
|
||||
quint64 getSamplesCount() const { return m_samplesCount; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||
{
|
||||
if (m_magsqCount > 0)
|
||||
{
|
||||
m_magsq = m_magsqSum / m_magsqCount;
|
||||
m_magSqLevelStore.m_magsq = m_magsq;
|
||||
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
|
||||
}
|
||||
|
||||
avg = m_magSqLevelStore.m_magsq;
|
||||
peak = m_magSqLevelStore.m_magsqPeak;
|
||||
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
|
||||
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
|
||||
void applySettings(const FileSourceSettings& settings, bool force = false);
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_guiMessageQueue = messageQueue; }
|
||||
|
||||
void openFileStream(const QString& fileName);
|
||||
void seekFileStream(int seekMillis);
|
||||
void setRunning(bool running) { m_running = running; }
|
||||
|
||||
uint32_t getFileSampleRate() const { return m_fileSampleRate; }
|
||||
quint64 getStartingTimeStamp() const { return m_startingTimeStamp; }
|
||||
quint64 getRecordLength() const { return m_recordLength; }
|
||||
quint32 getFileSampleSize() const { return m_sampleSize; }
|
||||
|
||||
private:
|
||||
struct MagSqLevelsStore
|
||||
{
|
||||
MagSqLevelsStore() :
|
||||
m_magsq(1e-12),
|
||||
m_magsqPeak(1e-12)
|
||||
{}
|
||||
double m_magsq;
|
||||
double m_magsqPeak;
|
||||
};
|
||||
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
FileSourceSettings m_settings;
|
||||
|
||||
std::ifstream m_ifstream;
|
||||
QString m_fileName;
|
||||
quint32 m_sampleSize;
|
||||
quint64 m_centerFrequency;
|
||||
int64_t m_frequencyOffset;
|
||||
uint32_t m_fileSampleRate;
|
||||
quint64 m_samplesCount;
|
||||
uint32_t m_sampleRate;
|
||||
uint32_t m_deviceSampleRate;
|
||||
quint64 m_recordLength; //!< record length in seconds computed from file size
|
||||
quint64 m_startingTimeStamp;
|
||||
QTimer m_masterTimer;
|
||||
bool m_running;
|
||||
|
||||
double m_linearGain;
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
|
||||
MessageQueue *m_guiMessageQueue;
|
||||
|
||||
void handleEOF();
|
||||
MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
|
||||
};
|
||||
|
||||
#endif // PLUGINS_CHANNELTX_FILESOURCE_FILESOURCE_H_
|
@ -1,7 +1,9 @@
|
||||
project(localsource)
|
||||
|
||||
set(localsource_SOURCES
|
||||
localsource.cpp
|
||||
localsource.cpp
|
||||
localsourcebaseband.cpp
|
||||
localsourcesource.cpp
|
||||
localsourcethread.cpp
|
||||
localsourceplugin.cpp
|
||||
localsourcesettings.cpp
|
||||
@ -9,7 +11,9 @@ set(localsource_SOURCES
|
||||
)
|
||||
|
||||
set(localsource_HEADERS
|
||||
localsource.h
|
||||
localsource.h
|
||||
localsourcebaseband.h
|
||||
localsourcesource.h
|
||||
localsourcethread.h
|
||||
localsourceplugin.h
|
||||
localsourcesettings.h
|
||||
|
@ -17,18 +17,14 @@
|
||||
|
||||
#include "localsource.h"
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
#include <boost/cstdint.hpp>
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
|
||||
#include "util/simpleserializer.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/dspdevicesinkengine.h"
|
||||
#include "dsp/dspengine.h"
|
||||
@ -36,11 +32,10 @@
|
||||
#include "dsp/hbfilterchainconverter.h"
|
||||
#include "device/deviceapi.h"
|
||||
|
||||
#include "localsourcethread.h"
|
||||
#include "localsourcebaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(LocalSource::MsgConfigureLocalSource, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSource::MsgSampleRateNotification, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSource::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSource::MsgBasebandSampleRateNotification, Message)
|
||||
|
||||
const QString LocalSource::m_channelIdURI = "sdrangel.channel.localsource";
|
||||
const QString LocalSource::m_channelId = "LocalSource";
|
||||
@ -48,23 +43,20 @@ const QString LocalSource::m_channelId = "LocalSource";
|
||||
LocalSource::LocalSource(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_running(false),
|
||||
m_sinkThread(nullptr),
|
||||
m_localSampleSourceFifo(nullptr),
|
||||
m_chunkSize(0),
|
||||
m_localSamplesIndex(0),
|
||||
m_localSamplesIndexOffset(0),
|
||||
m_centerFrequency(0),
|
||||
m_frequencyOffset(0),
|
||||
m_sampleRate(48000),
|
||||
m_deviceSampleRate(48000),
|
||||
m_basebandSampleRate(48000),
|
||||
m_settingsMutex(QMutex::Recursive)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new LocalSourceBaseband();
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -76,167 +68,57 @@ LocalSource::~LocalSource()
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void LocalSource::pull(Sample& sample)
|
||||
{
|
||||
if (m_localSampleSourceFifo)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_settingsMutex);
|
||||
sample = m_localSamples[m_localSamplesIndex + m_localSamplesIndexOffset];
|
||||
|
||||
if (m_localSamplesIndex < m_chunkSize - 1)
|
||||
{
|
||||
m_localSamplesIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_localSamplesIndex = 0;
|
||||
|
||||
if (m_localSamplesIndexOffset == 0) {
|
||||
m_localSamplesIndexOffset = m_chunkSize;
|
||||
} else {
|
||||
m_localSamplesIndexOffset = 0;
|
||||
}
|
||||
|
||||
emit pullSamples(m_chunkSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = Sample{0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSource::processSamples(int offset)
|
||||
{
|
||||
if (m_localSampleSourceFifo)
|
||||
{
|
||||
int destOffset = (m_localSamplesIndexOffset == 0 ? m_chunkSize : 0);
|
||||
SampleVector::iterator beginSource;
|
||||
SampleVector::iterator beginDestination = m_localSamples.begin() + destOffset;
|
||||
m_localSampleSourceFifo->setIteratorFromOffset(beginSource, offset);
|
||||
std::copy(beginSource, beginSource + m_chunkSize, beginDestination);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSource::pullAudio(int nbSamples)
|
||||
{
|
||||
(void) nbSamples;
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void LocalSource::start()
|
||||
{
|
||||
qDebug("LocalSource::start");
|
||||
|
||||
if (m_running) {
|
||||
stop();
|
||||
}
|
||||
|
||||
m_sinkThread = new LocalSourceThread();
|
||||
DeviceSampleSink *deviceSink = getLocalDevice(m_settings.m_localDeviceIndex);
|
||||
|
||||
if (deviceSink)
|
||||
{
|
||||
m_localSampleSourceFifo = deviceSink->getSampleFifo();
|
||||
m_chunkSize = m_localSampleSourceFifo->size() / 16;
|
||||
m_localSamples.resize(2*m_chunkSize);
|
||||
m_localSamplesIndex = 0;
|
||||
m_sinkThread->setSampleFifo(m_localSampleSourceFifo);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_localSampleSourceFifo = nullptr;
|
||||
}
|
||||
|
||||
connect(this,
|
||||
SIGNAL(pullSamples(unsigned int)),
|
||||
m_sinkThread,
|
||||
SLOT(pullSamples(unsigned int)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_sinkThread,
|
||||
SIGNAL(samplesAvailable(int)),
|
||||
this,
|
||||
SLOT(processSamples(int)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_sinkThread->startStop(true);
|
||||
m_running = true;
|
||||
qDebug("LocalSource::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void LocalSource::stop()
|
||||
{
|
||||
qDebug("LocalSource::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
if (m_sinkThread != 0)
|
||||
{
|
||||
m_sinkThread->startStop(false);
|
||||
m_sinkThread->deleteLater();
|
||||
m_sinkThread = 0;
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
void LocalSource::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool LocalSource::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
int sampleRate = notif.getSampleRate();
|
||||
|
||||
qDebug() << "LocalSource::handleMessage: MsgChannelizerNotification:"
|
||||
<< " channelSampleRate: " << sampleRate
|
||||
<< " offsetFrequency: " << notif.getFrequencyOffset();
|
||||
|
||||
if (sampleRate > 0)
|
||||
{
|
||||
if (m_localSampleSourceFifo)
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_settingsMutex);
|
||||
m_localSampleSourceFifo->resize(sampleRate);
|
||||
m_chunkSize = sampleRate / 8;
|
||||
m_localSamplesIndex = 0;
|
||||
m_localSamplesIndexOffset = 0;
|
||||
m_localSamples.resize(2*m_chunkSize);
|
||||
}
|
||||
|
||||
setSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification& cfg = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "LocalSource::handleMessage: DSPSignalNotification: "
|
||||
<< "basband sample rate: " << cfg.getSampleRate()
|
||||
<< "center frequency: " << cfg.getCenterFrequency();
|
||||
|
||||
qDebug() << "LocalSource::handleMessage: DSPSignalNotification:"
|
||||
<< " inputSampleRate: " << notif.getSampleRate()
|
||||
<< " centerFrequency: " << notif.getCenterFrequency();
|
||||
m_basebandSampleRate = cfg.getSampleRate();
|
||||
m_centerFrequency = cfg.getCenterFrequency();
|
||||
|
||||
setCenterFrequency(notif.getCenterFrequency());
|
||||
m_deviceSampleRate = notif.getSampleRate();
|
||||
calculateFrequencyOffset(); // This is when device sample rate changes
|
||||
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex);
|
||||
calculateFrequencyOffset(m_settings.m_log2Interp, m_settings.m_filterChainHash);
|
||||
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, m_settings.m_log2Interp);
|
||||
|
||||
// Redo the channelizer stuff with the new sample rate to re-synchronize everything
|
||||
m_channelizer->set(m_channelizer->getInputMessageQueue(),
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(cfg.getSampleRate());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (m_guiMessageQueue)
|
||||
{
|
||||
MsgSampleRateNotification *msg = MsgSampleRateNotification::create(notif.getSampleRate());
|
||||
MsgBasebandSampleRateNotification *msg = MsgBasebandSampleRateNotification::create(cfg.getSampleRate());
|
||||
m_guiMessageQueue->push(msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureLocalSource::match(cmd))
|
||||
if (MsgConfigureLocalSource::match(cmd))
|
||||
{
|
||||
MsgConfigureLocalSource& cfg = (MsgConfigureLocalSource&) cmd;
|
||||
qDebug() << "LocalSource::handleMessage: MsgConfigureLocalSink";
|
||||
@ -244,25 +126,6 @@ bool LocalSource::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
m_settings.m_log2Interp = cfg.getLog2Interp();
|
||||
m_settings.m_filterChainHash = cfg.getFilterChainHash();
|
||||
|
||||
qDebug() << "LocalSource::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " log2Interp: " << m_settings.m_log2Interp
|
||||
<< " filterChainHash: " << m_settings.m_filterChainHash;
|
||||
|
||||
m_channelizer->set(m_channelizer->getInputMessageQueue(),
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
|
||||
calculateFrequencyOffset(); // This is when decimation or filter chain changes
|
||||
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -302,9 +165,15 @@ void LocalSource::getLocalDevices(std::vector<uint32_t>& indexes)
|
||||
DSPDeviceSinkEngine *deviceSinkEngine = dspEngine->getDeviceSinkEngineByIndex(i);
|
||||
DeviceSampleSink *deviceSink = deviceSinkEngine->getSink();
|
||||
|
||||
if (deviceSink->getDeviceDescription() == "LocalOutput") {
|
||||
if (deviceSink->getDeviceDescription() == "LocalOutput")
|
||||
{
|
||||
qDebug("LocalSource::getLocalDevices: index: %u: LocalOutput found", i);
|
||||
indexes.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug("LocalSource::getLocalDevices: index: %u: %s", i, qPrintable(deviceSink->getDeviceDescription()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,42 +209,81 @@ DeviceSampleSink *LocalSource::getLocalDevice(uint32_t index)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void LocalSource::propagateSampleRateAndFrequency(uint32_t index)
|
||||
void LocalSource::propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Interp)
|
||||
{
|
||||
qDebug() << "LocalSource::propagateSampleRateAndFrequency:"
|
||||
<< " index: " << index
|
||||
<< " baseband_freq: " << m_basebandSampleRate
|
||||
<< " log2interp: " << log2Interp
|
||||
<< " frequency: " << m_centerFrequency + m_frequencyOffset;
|
||||
|
||||
DeviceSampleSink *deviceSink = getLocalDevice(index);
|
||||
|
||||
if (deviceSink)
|
||||
{
|
||||
deviceSink->setSampleRate(m_deviceSampleRate / (1<<m_settings.m_log2Interp));
|
||||
deviceSink->setSampleRate(m_basebandSampleRate / (1 << log2Interp));
|
||||
deviceSink->setCenterFrequency(m_centerFrequency + m_frequencyOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug("LocalSource::propagateSampleRateAndFrequency: no suitable device at index %u", index);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSource::applySettings(const LocalSourceSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LocalSource::applySettings:"
|
||||
<< " m_localDeviceIndex: " << settings.m_localDeviceIndex
|
||||
<< " force: " << force;
|
||||
<< "m_localDeviceIndex:" << settings.m_localDeviceIndex
|
||||
<< "m_log2Interp:" << settings.m_log2Interp
|
||||
<< "m_filterChainHash:" << settings.m_filterChainHash
|
||||
<< "m_play:" << settings.m_play
|
||||
<< "m_rgbColor:" << settings.m_rgbColor
|
||||
<< "m_title:" << settings.m_title
|
||||
<< "m_useReverseAPI:" << settings.m_useReverseAPI
|
||||
<< "m_reverseAPIAddress:" << settings.m_reverseAPIAddress
|
||||
<< "m_reverseAPIChannelIndex:" << settings.m_reverseAPIChannelIndex
|
||||
<< "m_reverseAPIDeviceIndex:" << settings.m_reverseAPIDeviceIndex
|
||||
<< "m_reverseAPIPort:" << settings.m_reverseAPIPort
|
||||
<< " force: " << force;
|
||||
|
||||
QList<QString> reverseAPIKeys;
|
||||
|
||||
if ((settings.m_log2Interp != m_settings.m_log2Interp) || force) {
|
||||
reverseAPIKeys.append("log2Interp");
|
||||
}
|
||||
if ((settings.m_filterChainHash != m_settings.m_filterChainHash) || force) {
|
||||
reverseAPIKeys.append("filterChainHash");
|
||||
}
|
||||
if ((settings.m_localDeviceIndex != m_settings.m_localDeviceIndex) || force)
|
||||
{
|
||||
reverseAPIKeys.append("localDeviceIndex");
|
||||
DeviceSampleSink *deviceSink = getLocalDevice(settings.m_localDeviceIndex);
|
||||
calculateFrequencyOffset(settings.m_log2Interp, settings.m_filterChainHash);
|
||||
propagateSampleRateAndFrequency(settings.m_localDeviceIndex, settings.m_log2Interp);
|
||||
DeviceSampleSink *deviceSampleSink = getLocalDevice(settings.m_localDeviceIndex);
|
||||
LocalSourceBaseband::MsgConfigureLocalDeviceSampleSink *msg =
|
||||
LocalSourceBaseband::MsgConfigureLocalDeviceSampleSink::create(deviceSampleSink);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
if (deviceSink)
|
||||
{
|
||||
if (m_sinkThread) {
|
||||
m_sinkThread->setSampleFifo(deviceSink->getSampleFifo());
|
||||
}
|
||||
if ((settings.m_log2Interp != m_settings.m_log2Interp)
|
||||
|| (settings.m_filterChainHash != m_settings.m_filterChainHash) || force)
|
||||
{
|
||||
calculateFrequencyOffset(settings.m_log2Interp, settings.m_filterChainHash);
|
||||
propagateSampleRateAndFrequency(m_settings.m_localDeviceIndex, settings.m_log2Interp);
|
||||
LocalSourceBaseband::MsgConfigureChannelizer *msg = LocalSourceBaseband::MsgConfigureChannelizer::create(
|
||||
settings.m_log2Interp,
|
||||
settings.m_filterChainHash
|
||||
);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
propagateSampleRateAndFrequency(settings.m_localDeviceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning("LocalSource::applySettings: invalid local device for index %u", settings.m_localDeviceIndex);
|
||||
}
|
||||
if ((settings.m_play != m_settings.m_play) || force)
|
||||
{
|
||||
reverseAPIKeys.append("play");
|
||||
LocalSourceBaseband::MsgConfigureLocalSourceWork *msg = LocalSourceBaseband::MsgConfigureLocalSourceWork::create(
|
||||
settings.m_play
|
||||
);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
if ((settings.m_useReverseAPI) && (reverseAPIKeys.size() != 0))
|
||||
@ -402,10 +310,10 @@ void LocalSource::validateFilterChainHash(LocalSourceSettings& settings)
|
||||
settings.m_filterChainHash = settings.m_filterChainHash >= s ? s-1 : settings.m_filterChainHash;
|
||||
}
|
||||
|
||||
void LocalSource::calculateFrequencyOffset()
|
||||
void LocalSource::calculateFrequencyOffset(uint32_t log2Interp, uint32_t filterChainHash)
|
||||
{
|
||||
double shiftFactor = HBFilterChainConverter::getShiftFactor(m_settings.m_log2Interp, m_settings.m_filterChainHash);
|
||||
m_frequencyOffset = m_deviceSampleRate * shiftFactor;
|
||||
double shiftFactor = HBFilterChainConverter::getShiftFactor(log2Interp, filterChainHash);
|
||||
m_frequencyOffset = m_basebandSampleRate * shiftFactor;
|
||||
}
|
||||
|
||||
int LocalSource::webapiSettingsGet(
|
||||
@ -432,12 +340,6 @@ int LocalSource::webapiSettingsPutPatch(
|
||||
MsgConfigureLocalSource *msg = MsgConfigureLocalSource::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
if ((settings.m_log2Interp != m_settings.m_log2Interp) || (settings.m_filterChainHash != m_settings.m_filterChainHash) || force)
|
||||
{
|
||||
MsgConfigureChannelizer *msg = MsgConfigureChannelizer::create(settings.m_log2Interp, settings.m_filterChainHash);
|
||||
m_inputMessageQueue.push(msg);
|
||||
}
|
||||
|
||||
qDebug("LocalSource::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -553,13 +455,14 @@ void LocalSource::webapiReverseSendSettings(QList<QString>& channelSettingsKeys,
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -574,10 +477,13 @@ void LocalSource::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("LocalSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("LocalSource::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
@ -27,13 +27,13 @@
|
||||
#include "channel/channelapi.h"
|
||||
#include "localsourcesettings.h"
|
||||
|
||||
class DeviceAPI;
|
||||
class DeviceSampleSink;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class LocalSourceThread;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
|
||||
class DeviceAPI;
|
||||
class DeviceSampleSink;
|
||||
class LocalSourceBaseband;
|
||||
|
||||
class LocalSource : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -61,19 +61,19 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSampleRateNotification : public Message {
|
||||
class MsgBasebandSampleRateNotification : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
static MsgSampleRateNotification* create(int sampleRate) {
|
||||
return new MsgSampleRateNotification(sampleRate);
|
||||
static MsgBasebandSampleRateNotification* create(int sampleRate) {
|
||||
return new MsgBasebandSampleRateNotification(sampleRate);
|
||||
}
|
||||
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getBasebandSampleRate() const { return m_sampleRate; }
|
||||
|
||||
private:
|
||||
|
||||
MsgSampleRateNotification(int sampleRate) :
|
||||
MsgBasebandSampleRateNotification(int sampleRate) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate)
|
||||
{ }
|
||||
@ -81,36 +81,13 @@ public:
|
||||
int m_sampleRate;
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getLog2Interp() const { return m_log2Interp; }
|
||||
int getFilterChainHash() const { return m_filterChainHash; }
|
||||
|
||||
static MsgConfigureChannelizer* create(unsigned int m_log2Interp, unsigned int m_filterChainHash) {
|
||||
return new MsgConfigureChannelizer(m_log2Interp, m_filterChainHash);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_log2Interp;
|
||||
unsigned int m_filterChainHash;
|
||||
|
||||
MsgConfigureChannelizer(unsigned int log2Interp, unsigned int filterChainHash) :
|
||||
Message(),
|
||||
m_log2Interp(log2Interp),
|
||||
m_filterChainHash(filterChainHash)
|
||||
{ }
|
||||
};
|
||||
|
||||
LocalSource(DeviceAPI *deviceAPI);
|
||||
virtual ~LocalSource();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -149,39 +126,20 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
/** Set center frequency given in Hz */
|
||||
void setCenterFrequency(uint64_t centerFrequency) { m_centerFrequency = centerFrequency; }
|
||||
|
||||
/** Set sample rate given in Hz */
|
||||
void setSampleRate(uint32_t sampleRate) { m_sampleRate = sampleRate; }
|
||||
|
||||
void setChannelizer(unsigned int log2Interp, unsigned int filterChainHash);
|
||||
void getLocalDevices(std::vector<uint32_t>& indexes);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
signals:
|
||||
void pullSamples(unsigned int count);
|
||||
|
||||
private:
|
||||
DeviceAPI *m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
bool m_running;
|
||||
|
||||
QThread *m_thread;
|
||||
LocalSourceBaseband *m_basebandSource;
|
||||
LocalSourceSettings m_settings;
|
||||
LocalSourceThread *m_sinkThread;
|
||||
SampleSourceFifoDB *m_localSampleSourceFifo;
|
||||
int m_chunkSize;
|
||||
SampleVector m_localSamples;
|
||||
int m_localSamplesIndex;
|
||||
int m_localSamplesIndexOffset;
|
||||
|
||||
uint64_t m_centerFrequency;
|
||||
int64_t m_frequencyOffset;
|
||||
uint32_t m_sampleRate;
|
||||
uint32_t m_deviceSampleRate;
|
||||
uint32_t m_basebandSampleRate;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
@ -189,15 +147,15 @@ private:
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
void applySettings(const LocalSourceSettings& settings, bool force = false);
|
||||
DeviceSampleSink *getLocalDevice(uint32_t index);
|
||||
void propagateSampleRateAndFrequency(uint32_t index);
|
||||
void propagateSampleRateAndFrequency(uint32_t index, uint32_t log2Interp);
|
||||
static void validateFilterChainHash(LocalSourceSettings& settings);
|
||||
void calculateFrequencyOffset();
|
||||
void calculateFrequencyOffset(uint32_t log2Interp, uint32_t filterChainHash);
|
||||
DeviceSampleSink *getLocalDevice(uint32_t index);
|
||||
|
||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const LocalSourceSettings& settings, bool force);
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
void processSamples(int offset);
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_LOCALSOURCE_H_ */
|
||||
|
@ -1,52 +0,0 @@
|
||||
#--------------------------------------------------------
|
||||
#
|
||||
# Pro file for Android and Windows builds with Qt Creator
|
||||
#
|
||||
#--------------------------------------------------------
|
||||
|
||||
TEMPLATE = lib
|
||||
CONFIG += plugin
|
||||
|
||||
QT += core gui widgets multimedia network opengl
|
||||
|
||||
TARGET = localsource
|
||||
|
||||
INCLUDEPATH += $$PWD
|
||||
INCLUDEPATH += ../../../exports
|
||||
INCLUDEPATH += ../../../sdrbase
|
||||
INCLUDEPATH += ../../../sdrgui
|
||||
INCLUDEPATH += ../../../swagger/sdrangel/code/qt5/client
|
||||
macx:INCLUDEPATH += /opt/local/include
|
||||
|
||||
CONFIG(Release):build_subdir = release
|
||||
CONFIG(Debug):build_subdir = debug
|
||||
|
||||
CONFIG(MINGW32):INCLUDEPATH += "C:\softs\boost_1_66_0"
|
||||
CONFIG(MSVC):INCLUDEPATH += "C:\softs\boost_1_66_0"
|
||||
CONFIG(macx):INCLUDEPATH += "../../../../../boost_1_69_0"
|
||||
|
||||
SOURCES += localsource.cpp\
|
||||
localsourcegui.cpp\
|
||||
localsourcesettings.cpp\
|
||||
localsourceplugin.cpp\
|
||||
localsourcethread.cpp
|
||||
|
||||
HEADERS += localsource.h\
|
||||
localsourcegui.h\
|
||||
localsourcesettings.h\
|
||||
localsourceplugin.h\
|
||||
localsourcethread.h
|
||||
|
||||
FORMS += localsourcegui.ui
|
||||
|
||||
LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
|
||||
LIBS += -L../../../sdrgui/$${build_subdir} -lsdrgui
|
||||
LIBS += -L../../../swagger/$${build_subdir} -lswagger
|
||||
|
||||
macx {
|
||||
QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/
|
||||
}
|
||||
|
||||
RESOURCES = ../../../sdrgui/resources/res.qrc
|
||||
|
||||
CONFIG(MINGW32):DEFINES += USE_INTERNAL_TIMER=1
|
219
plugins/channeltx/localsource/localsourcebaseband.cpp
Normal file
219
plugins/channeltx/localsource/localsourcebaseband.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "localsourcebaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(LocalSourceBaseband::MsgConfigureLocalSourceBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSourceBaseband::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSourceBaseband::MsgConfigureLocalSourceWork, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSourceBaseband::MsgBasebandSampleRateNotification, Message)
|
||||
MESSAGE_CLASS_DEFINITION(LocalSourceBaseband::MsgConfigureLocalDeviceSampleSink, Message)
|
||||
|
||||
LocalSourceBaseband::LocalSourceBaseband() :
|
||||
m_localSampleSink(nullptr),
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("FileSourceBaseband::FileSourceBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&LocalSourceBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
LocalSourceBaseband::~LocalSourceBaseband()
|
||||
{
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void LocalSourceBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void LocalSourceBaseband::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 LocalSourceBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSourceBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void LocalSourceBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LocalSourceBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureLocalSourceBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureLocalSourceBaseband& cfg = (MsgConfigureLocalSourceBaseband&) cmd;
|
||||
qDebug() << "LocalSourceBaseband::handleMessage: MsgConfigureLocalSourceBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
m_settings.m_log2Interp = cfg.getLog2Interp();
|
||||
m_settings.m_filterChainHash = cfg.getFilterChainHash();
|
||||
|
||||
qDebug() << "LocalSourceBaseband::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " log2Interp: " << m_settings.m_log2Interp
|
||||
<< " filterChainHash: " << m_settings.m_filterChainHash;
|
||||
m_channelizer->setInterpolation(m_settings.m_log2Interp, m_settings.m_filterChainHash);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgBasebandSampleRateNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgBasebandSampleRateNotification& notif = (MsgBasebandSampleRateNotification&) cmd;
|
||||
qDebug() << "LocalSourceBaseband::handleMessage: MsgBasebandSampleRateNotification: basebandSampleRate: " << notif.getBasebandSampleRate();
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(notif.getBasebandSampleRate()));
|
||||
m_channelizer->setBasebandSampleRate(notif.getBasebandSampleRate());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureLocalSourceWork::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureLocalSourceWork& conf = (MsgConfigureLocalSourceWork&) cmd;
|
||||
qDebug() << "LocalSourceBaseband::handleMessage: MsgConfigureLocalSourceWork: " << conf.isWorking();
|
||||
|
||||
if (conf.isWorking()) {
|
||||
m_source.start(m_localSampleSink);
|
||||
} else {
|
||||
m_source.stop();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureLocalDeviceSampleSink::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureLocalDeviceSampleSink& notif = (MsgConfigureLocalDeviceSampleSink&) cmd;
|
||||
qDebug() << "LocalSourceBaseband::handleMessage: MsgConfigureLocalDeviceSampleSink: " << notif.getDeviceSampleSink();
|
||||
m_localSampleSink = notif.getDeviceSampleSink();
|
||||
|
||||
if (m_source.isRunning()) {
|
||||
m_source.start(m_localSampleSink);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSourceBaseband::applySettings(const LocalSourceSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "LocalSourceBaseband::applySettings:"
|
||||
<< "m_localDeviceIndex:" << settings.m_localDeviceIndex
|
||||
<< "m_log2Interp:" << settings.m_log2Interp
|
||||
<< "m_filterChainHash:" << settings.m_filterChainHash
|
||||
<< "m_play:" << settings.m_play
|
||||
<< " force: " << force;
|
||||
|
||||
//m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int LocalSourceBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
170
plugins/channeltx/localsource/localsourcebaseband.h
Normal file
170
plugins/channeltx/localsource/localsourcebaseband.h
Normal file
@ -0,0 +1,170 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FILESOURCEBASEBAND_H
|
||||
#define INCLUDE_FILESOURCEBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "localsourcesource.h"
|
||||
#include "localsourcesettings.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class LocalSourceBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureLocalSourceBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const LocalSourceSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureLocalSourceBaseband* create(const LocalSourceSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureLocalSourceBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
LocalSourceSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureLocalSourceBaseband(const LocalSourceSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getLog2Interp() const { return m_log2Interp; }
|
||||
int getFilterChainHash() const { return m_filterChainHash; }
|
||||
|
||||
static MsgConfigureChannelizer* create(unsigned int m_log2Interp, unsigned int m_filterChainHash) {
|
||||
return new MsgConfigureChannelizer(m_log2Interp, m_filterChainHash);
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int m_log2Interp;
|
||||
unsigned int m_filterChainHash;
|
||||
|
||||
MsgConfigureChannelizer(unsigned int log2Interp, unsigned int filterChainHash) :
|
||||
Message(),
|
||||
m_log2Interp(log2Interp),
|
||||
m_filterChainHash(filterChainHash)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureLocalSourceWork : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
bool isWorking() const { return m_working; }
|
||||
|
||||
static MsgConfigureLocalSourceWork* create(bool working)
|
||||
{
|
||||
return new MsgConfigureLocalSourceWork(working);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_working;
|
||||
|
||||
MsgConfigureLocalSourceWork(bool working) :
|
||||
Message(),
|
||||
m_working(working)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgBasebandSampleRateNotification : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
static MsgBasebandSampleRateNotification* create(int sampleRate) {
|
||||
return new MsgBasebandSampleRateNotification(sampleRate);
|
||||
}
|
||||
|
||||
int getBasebandSampleRate() const { return m_sampleRate; }
|
||||
|
||||
private:
|
||||
|
||||
MsgBasebandSampleRateNotification(int sampleRate) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate)
|
||||
{ }
|
||||
|
||||
int m_sampleRate;
|
||||
};
|
||||
|
||||
class MsgConfigureLocalDeviceSampleSink : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
static MsgConfigureLocalDeviceSampleSink* create(DeviceSampleSink *deviceSampleSink) {
|
||||
return new MsgConfigureLocalDeviceSampleSink(deviceSampleSink);
|
||||
}
|
||||
|
||||
DeviceSampleSink *getDeviceSampleSink() const { return m_deviceSampleSink; }
|
||||
|
||||
private:
|
||||
|
||||
MsgConfigureLocalDeviceSampleSink(DeviceSampleSink *deviceSampleSink) :
|
||||
Message(),
|
||||
m_deviceSampleSink(deviceSampleSink)
|
||||
{ }
|
||||
|
||||
DeviceSampleSink *m_deviceSampleSink;
|
||||
};
|
||||
|
||||
LocalSourceBaseband();
|
||||
~LocalSourceBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
int getChannelSampleRate() const;
|
||||
void startSource() { m_source.start(m_localSampleSink); }
|
||||
void stopSource() { m_source.stop(); }
|
||||
|
||||
private:
|
||||
SampleSourceFifo m_sampleFifo;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
LocalSourceSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
LocalSourceSettings m_settings;
|
||||
DeviceSampleSink *m_localSampleSink;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const LocalSourceSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_FILESOURCEBASEBAND_H
|
@ -82,11 +82,10 @@ bool LocalSourceGUI::deserialize(const QByteArray& data)
|
||||
|
||||
bool LocalSourceGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (LocalSource::MsgSampleRateNotification::match(message))
|
||||
if (LocalSource::MsgBasebandSampleRateNotification::match(message))
|
||||
{
|
||||
LocalSource::MsgSampleRateNotification& notif = (LocalSource::MsgSampleRateNotification&) message;
|
||||
//m_channelMarker.setBandwidth(notif.getSampleRate());
|
||||
m_sampleRate = notif.getSampleRate();
|
||||
LocalSource::MsgBasebandSampleRateNotification& notif = (LocalSource::MsgBasebandSampleRateNotification&) message;
|
||||
m_basebandSampleRate = notif.getBasebandSampleRate();
|
||||
displayRateAndShift();
|
||||
return true;
|
||||
}
|
||||
@ -110,7 +109,7 @@ LocalSourceGUI::LocalSourceGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, B
|
||||
ui(new Ui::LocalSourceGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_sampleRate(0),
|
||||
m_basebandSampleRate(0),
|
||||
m_tickCount(0)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@ -168,23 +167,12 @@ void LocalSourceGUI::applySettings(bool force)
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSourceGUI::applyChannelSettings()
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
LocalSource::MsgConfigureChannelizer *msgChan = LocalSource::MsgConfigureChannelizer::create(
|
||||
m_settings.m_log2Interp,
|
||||
m_settings.m_filterChainHash);
|
||||
m_localSource->getInputMessageQueue()->push(msgChan);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSourceGUI::displaySettings()
|
||||
{
|
||||
m_channelMarker.blockSignals(true);
|
||||
m_channelMarker.setCenterFrequency(0);
|
||||
m_channelMarker.setTitle(m_settings.m_title);
|
||||
m_channelMarker.setBandwidth(m_sampleRate); // TODO
|
||||
m_channelMarker.setBandwidth(m_basebandSampleRate / (1<<m_settings.m_log2Interp)); // TODO
|
||||
m_channelMarker.setMovable(false); // do not let user move the center arbitrarily
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
||||
@ -200,8 +188,8 @@ void LocalSourceGUI::displaySettings()
|
||||
|
||||
void LocalSourceGUI::displayRateAndShift()
|
||||
{
|
||||
int shift = m_shiftFrequencyFactor * m_sampleRate;
|
||||
double channelSampleRate = ((double) m_sampleRate) / (1<<m_settings.m_log2Interp);
|
||||
int shift = m_shiftFrequencyFactor * m_basebandSampleRate;
|
||||
double channelSampleRate = ((double) m_basebandSampleRate) / (1<<m_settings.m_log2Interp);
|
||||
QLocale loc;
|
||||
ui->offsetFrequencyText->setText(tr("%1 Hz").arg(loc.toString(shift)));
|
||||
ui->channelRateText->setText(tr("%1k").arg(QString::number(channelSampleRate / 1000.0, 'g', 5)));
|
||||
@ -305,6 +293,12 @@ void LocalSourceGUI::on_localDevicesRefresh_clicked(bool checked)
|
||||
updateLocalDevices();
|
||||
}
|
||||
|
||||
void LocalSourceGUI::on_localDevicePlay_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_play = checked;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LocalSourceGUI::applyInterpolation()
|
||||
{
|
||||
uint32_t maxHash = 1;
|
||||
@ -327,7 +321,7 @@ void LocalSourceGUI::applyPosition()
|
||||
ui->filterChainText->setText(s);
|
||||
|
||||
displayRateAndShift();
|
||||
applyChannelSettings();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void LocalSourceGUI::tick()
|
||||
|
@ -62,7 +62,7 @@ private:
|
||||
DeviceUISet* m_deviceUISet;
|
||||
ChannelMarker m_channelMarker;
|
||||
LocalSourceSettings m_settings;
|
||||
int m_sampleRate;
|
||||
int m_basebandSampleRate;
|
||||
quint64 m_deviceCenterFrequency; //!< Center frequency in device
|
||||
double m_shiftFrequencyFactor; //!< Channel frequency shift factor
|
||||
bool m_doApplySettings;
|
||||
@ -78,7 +78,6 @@ private:
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void applyChannelSettings();
|
||||
void displaySettings();
|
||||
void displayRateAndShift();
|
||||
void updateLocalDevices();
|
||||
@ -95,6 +94,7 @@ private slots:
|
||||
void on_position_valueChanged(int value);
|
||||
void on_localDevice_currentIndexChanged(int index);
|
||||
void on_localDevicesRefresh_clicked(bool checked);
|
||||
void on_localDevicePlay_toggled(bool checked);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void onMenuDialogCalled(const QPoint& p);
|
||||
void tick();
|
||||
|
@ -298,6 +298,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="localDevicePlay">
|
||||
<property name="toolTip">
|
||||
<string>Start/Stop source</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/play.png</normaloff>
|
||||
<normalon>:/pause.png</normalon>:/play.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
@ -323,6 +338,11 @@
|
||||
<header>gui/rollupwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
const PluginDescriptor LocalSourcePlugin::m_pluginDescriptor = {
|
||||
QString("Local channel source"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
@ -36,6 +36,7 @@ void LocalSourceSettings::resetToDefaults()
|
||||
m_log2Interp = 0;
|
||||
m_filterChainHash = 0;
|
||||
m_channelMarker = nullptr;
|
||||
m_play = false;
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
|
@ -30,6 +30,7 @@ struct LocalSourceSettings
|
||||
QString m_title;
|
||||
uint32_t m_log2Interp;
|
||||
uint32_t m_filterChainHash;
|
||||
bool m_play;
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
|
156
plugins/channeltx/localsource/localsourcesource.cpp
Normal file
156
plugins/channeltx/localsource/localsourcesource.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/devicesamplesink.h"
|
||||
#include "localsourcethread.h"
|
||||
#include "localsourcesource.h"
|
||||
|
||||
LocalSourceSource::LocalSourceSource() :
|
||||
m_running(false),
|
||||
m_sinkThread(nullptr)
|
||||
{}
|
||||
|
||||
LocalSourceSource::~LocalSourceSource()
|
||||
{}
|
||||
|
||||
void LocalSourceSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void LocalSourceSource::start(DeviceSampleSink *deviceSink)
|
||||
{
|
||||
qDebug("LocalSourceSource::start: %p", deviceSink);
|
||||
|
||||
if (m_running) {
|
||||
stop();
|
||||
}
|
||||
|
||||
if (!deviceSink) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_sinkThread = new LocalSourceThread();
|
||||
|
||||
if (deviceSink)
|
||||
{
|
||||
m_localSampleSourceFifo = deviceSink->getSampleFifo();
|
||||
m_chunkSize = deviceSink->getSampleRate() / 10;
|
||||
m_localSamples.resize(2*m_chunkSize);
|
||||
m_localSamplesIndex = 0;
|
||||
m_localSamplesIndexOffset = m_chunkSize;
|
||||
m_sinkThread->setSampleFifo(m_localSampleSourceFifo);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_localSampleSourceFifo = nullptr;
|
||||
}
|
||||
|
||||
connect(this,
|
||||
SIGNAL(pullSamples(unsigned int)),
|
||||
m_sinkThread,
|
||||
SLOT(pullSamples(unsigned int)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(m_sinkThread,
|
||||
SIGNAL(samplesAvailable(unsigned int, unsigned int, unsigned int, unsigned int)),
|
||||
this,
|
||||
SLOT(processSamples(unsigned int, unsigned int, unsigned int, unsigned int)),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_sinkThread->startStop(true);
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
void LocalSourceSource::stop()
|
||||
{
|
||||
qDebug("LocalSourceSource::stop");
|
||||
|
||||
if (m_sinkThread)
|
||||
{
|
||||
m_sinkThread->startStop(false);
|
||||
m_sinkThread->deleteLater();
|
||||
m_sinkThread = nullptr;
|
||||
}
|
||||
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
void LocalSourceSource::pullOne(Sample& sample)
|
||||
{
|
||||
if (m_localSampleSourceFifo)
|
||||
{
|
||||
sample = m_localSamples[m_localSamplesIndex + m_localSamplesIndexOffset];
|
||||
|
||||
if (m_localSamplesIndex < m_chunkSize - 1)
|
||||
{
|
||||
m_localSamplesIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_localSamplesIndex = 0;
|
||||
|
||||
if (m_localSamplesIndexOffset == 0) {
|
||||
m_localSamplesIndexOffset = m_chunkSize;
|
||||
} else {
|
||||
m_localSamplesIndexOffset = 0;
|
||||
}
|
||||
|
||||
emit pullSamples(m_chunkSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = Sample{0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
void LocalSourceSource::processSamples(unsigned int iPart1Begin, unsigned int iPart1End, unsigned int iPart2Begin, unsigned int iPart2End)
|
||||
{
|
||||
int destOffset = (m_localSamplesIndexOffset == 0 ? m_chunkSize : 0);
|
||||
SampleVector::iterator beginDestination = m_localSamples.begin() + destOffset;
|
||||
SampleVector& data = m_localSampleSourceFifo->getData();
|
||||
|
||||
// qDebug("LocalSourceSource::processSamples: m_localSamplesIndexOffset: %d m_chunkSize: %d part1: %u part2: %u",
|
||||
// m_localSamplesIndexOffset, m_chunkSize, (iPart1End - iPart1Begin), (iPart2End - iPart2Begin));
|
||||
|
||||
if (iPart1Begin != iPart1End)
|
||||
{
|
||||
std::copy(
|
||||
data.begin() + iPart1Begin,
|
||||
data.begin() + iPart1End,
|
||||
beginDestination
|
||||
);
|
||||
}
|
||||
|
||||
beginDestination += (iPart1End - iPart1Begin);
|
||||
|
||||
if (iPart2Begin != iPart2End)
|
||||
{
|
||||
std::copy(
|
||||
data.begin() + iPart2Begin,
|
||||
data.begin() + iPart2End,
|
||||
beginDestination
|
||||
);
|
||||
}
|
||||
}
|
61
plugins/channeltx/localsource/localsourcesource.h
Normal file
61
plugins/channeltx/localsource/localsourcesource.h
Normal file
@ -0,0 +1,61 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_CHANNELTX_LOCALSOURCE_LOCALSOURCESOURCE_H_
|
||||
#define PLUGINS_CHANNELTX_LOCALSOURCE_LOCALSOURCESOURCE_H_
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "localsourcesettings.h"
|
||||
|
||||
class DeviceSampleSink;
|
||||
class SampleSourceFifo;
|
||||
class LocalSourceThread;
|
||||
|
||||
class LocalSourceSource : public QObject, public ChannelSampleSource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalSourceSource();
|
||||
~LocalSourceSource();
|
||||
|
||||
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
|
||||
virtual void pullOne(Sample& sample);
|
||||
virtual void prefetch(unsigned int nbSamples) {}
|
||||
|
||||
void start(DeviceSampleSink *deviceSink);
|
||||
void stop();
|
||||
bool isRunning() const { return m_running; }
|
||||
|
||||
signals:
|
||||
void pullSamples(unsigned int count);
|
||||
|
||||
private:
|
||||
bool m_running;
|
||||
LocalSourceThread *m_sinkThread;
|
||||
SampleSourceFifo *m_localSampleSourceFifo;
|
||||
int m_chunkSize;
|
||||
SampleVector m_localSamples;
|
||||
int m_localSamplesIndex;
|
||||
int m_localSamplesIndexOffset;
|
||||
|
||||
private slots:
|
||||
void processSamples(unsigned int iPart1Begin, unsigned int iPart1End, unsigned int iPart2Begin, unsigned int iPart2End);
|
||||
};
|
||||
|
||||
|
||||
#endif // PLUGINS_CHANNELTX_LOCALSOURCE_LOCALSOURCESOURCE_H_
|
@ -15,7 +15,7 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "dsp/samplesourcefifodb.h"
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
|
||||
#include "localsourcethread.h"
|
||||
|
||||
@ -24,7 +24,7 @@ MESSAGE_CLASS_DEFINITION(LocalSourceThread::MsgStartStop, Message)
|
||||
LocalSourceThread::LocalSourceThread(QObject* parent) :
|
||||
QThread(parent),
|
||||
m_running(false),
|
||||
m_sampleFifo(0)
|
||||
m_sampleFifo(nullptr)
|
||||
{
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
|
||||
}
|
||||
@ -40,16 +40,16 @@ void LocalSourceThread::startStop(bool start)
|
||||
m_inputMessageQueue.push(msg);
|
||||
}
|
||||
|
||||
void LocalSourceThread::setSampleFifo(SampleSourceFifoDB *sampleFifo)
|
||||
void LocalSourceThread::setSampleFifo(SampleSourceFifo *sampleFifo)
|
||||
{
|
||||
m_sampleFifo = sampleFifo;
|
||||
}
|
||||
|
||||
void LocalSourceThread::pullSamples(unsigned int count)
|
||||
{
|
||||
SampleVector::iterator beginRead;
|
||||
m_sampleFifo->readAdvance(beginRead, count);
|
||||
emit samplesAvailable(m_sampleFifo->getIteratorOffset(beginRead));
|
||||
unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End;
|
||||
m_sampleFifo->read(count, iPart1Begin, iPart1End, iPart2Begin, iPart2End);
|
||||
emit samplesAvailable(iPart1Begin, iPart1End, iPart2Begin, iPart2End);
|
||||
}
|
||||
|
||||
void LocalSourceThread::startWork()
|
||||
|
@ -26,7 +26,7 @@
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
class SampleSourceFifoDB;
|
||||
class SampleSourceFifo;
|
||||
|
||||
class LocalSourceThread : public QThread {
|
||||
Q_OBJECT
|
||||
@ -51,23 +51,23 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
LocalSourceThread(QObject* parent = 0);
|
||||
LocalSourceThread(QObject* parent = nullptr);
|
||||
~LocalSourceThread();
|
||||
|
||||
void startStop(bool start);
|
||||
void setSampleFifo(SampleSourceFifoDB *sampleFifo);
|
||||
void setSampleFifo(SampleSourceFifo *sampleFifo);
|
||||
|
||||
public slots:
|
||||
void pullSamples(unsigned int count);
|
||||
|
||||
signals:
|
||||
void samplesAvailable(int offset);
|
||||
void samplesAvailable(unsigned int iPart1Begin, unsigned int iPart1End, unsigned int iPart2Begin, unsigned int iPart2End);
|
||||
|
||||
private:
|
||||
QMutex m_startWaitMutex;
|
||||
QWaitCondition m_startWaiter;
|
||||
volatile bool m_running;
|
||||
SampleSourceFifoDB *m_sampleFifo;
|
||||
SampleSourceFifo *m_sampleFifo;
|
||||
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
||||
|
@ -4,6 +4,8 @@ set(modam_SOURCES
|
||||
ammod.cpp
|
||||
ammodplugin.cpp
|
||||
ammodsettings.cpp
|
||||
ammodbaseband.cpp
|
||||
ammodsource.cpp
|
||||
ammodwebapiadapter.cpp
|
||||
)
|
||||
|
||||
@ -11,6 +13,8 @@ set(modam_HEADERS
|
||||
ammod.h
|
||||
ammodplugin.h
|
||||
ammodsettings.h
|
||||
ammodbaseband.h
|
||||
ammodsource.h
|
||||
ammodwebapiadapter.h
|
||||
)
|
||||
|
||||
|
@ -24,21 +24,22 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGAMModReport.h"
|
||||
|
||||
#include "ammod.h"
|
||||
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "ammodbaseband.h"
|
||||
#include "ammod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureAMMod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(AMMod::MsgConfigureFileSourceName, Message)
|
||||
@ -49,51 +50,25 @@ MESSAGE_CLASS_DEFINITION(AMMod::MsgReportFileSourceStreamTiming, Message)
|
||||
|
||||
const QString AMMod::m_channelIdURI = "sdrangel.channeltx.modam";
|
||||
const QString AMMod::m_channelId ="AMMod";
|
||||
const int AMMod::m_levelNbSamples = 480; // every 10ms
|
||||
|
||||
AMMod::AMMod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(48000),
|
||||
m_outputSampleRate(48000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_fileSize(0),
|
||||
m_recordLength(0),
|
||||
m_sampleRate(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f)
|
||||
m_sampleRate(48000)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new AMModBaseband();
|
||||
m_basebandSource->setInputFileStream(&m_ifstream);
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
|
||||
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
|
||||
m_toneNco.setFreq(1000.0, m_audioSampleRate);
|
||||
m_cwKeyer.setSampleRate(m_audioSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -105,11 +80,9 @@ AMMod::~AMMod()
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_feedbackAudioFifo);
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
uint32_t AMMod::getNumberOfDeviceStreams() const
|
||||
@ -117,249 +90,37 @@ uint32_t AMMod::getNumberOfDeviceStreams() const
|
||||
return m_deviceAPI->getNbSinkStreams();
|
||||
}
|
||||
|
||||
void AMMod::pull(Sample& sample)
|
||||
{
|
||||
if (m_settings.m_channelMute)
|
||||
{
|
||||
sample.m_real = 0.0f;
|
||||
sample.m_imag = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
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 AMMod::pullAudio(int nbSamples)
|
||||
{
|
||||
// qDebug("AMMod::pullAudio: %d", nbSamples);
|
||||
unsigned int nbAudioSamples = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate);
|
||||
|
||||
if (nbAudioSamples > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbAudioSamples);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbAudioSamples);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void AMMod::modulateSample()
|
||||
{
|
||||
Real t;
|
||||
|
||||
pullAF(t);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(t);
|
||||
m_audioBufferFill++;
|
||||
|
||||
m_modSample.real((t*m_settings.m_modFactor + 1.0f) * 16384.0f); // modulate and scale zero frequency carrier
|
||||
m_modSample.imag(0.0f);
|
||||
}
|
||||
|
||||
void AMMod::pullAF(Real& sample)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case AMModSettings::AMModInputTone:
|
||||
sample = m_toneNco.next();
|
||||
break;
|
||||
case AMModSettings::AMModInputFile:
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
|
||||
if (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 AMModSettings::AMModInputAudio:
|
||||
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
|
||||
break;
|
||||
case AMModSettings::AMModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = 0.0f;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AMModSettings::AMModInputNone:
|
||||
default:
|
||||
sample = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AMMod::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 AMMod::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("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AMMod::calculateLevel(Real& sample)
|
||||
{
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
|
||||
m_levelSum += sample * sample;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AMMod::start()
|
||||
{
|
||||
qDebug() << "AMMod::start: m_outputSampleRate: " << m_outputSampleRate
|
||||
<< " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset;
|
||||
|
||||
m_audioFifo.clear();
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug("AMMod::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void AMMod::stop()
|
||||
{
|
||||
qDebug("AMMod::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
void AMMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool AMMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "AMMod::handleMessage: MsgChannelizerNotification:"
|
||||
<< " basebandSampleRate: " << notif.getBasebandSampleRate()
|
||||
<< " outputSampleRate: " << notif.getSampleRate()
|
||||
<< " inputFrequencyOffset: " << notif.getFrequencyOffset();
|
||||
|
||||
applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "AMMod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " getSampleRate: " << cfg.getSampleRate()
|
||||
<< " getCenterFrequency: " << cfg.getCenterFrequency();
|
||||
<< " getSourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
AMModBaseband::MsgConfigureChannelizer *msg
|
||||
= AMModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -376,6 +137,7 @@ bool AMMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
|
||||
m_fileName = conf.getFileName();
|
||||
qDebug() << "AMMod::handleMessage: MsgConfigureFileSourceName";
|
||||
openFileStream();
|
||||
return true;
|
||||
}
|
||||
@ -383,6 +145,7 @@ bool AMMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
|
||||
int seekPercentage = conf.getPercentage();
|
||||
qDebug() << "AMMod::handleMessage: MsgConfigureFileSourceSeek";
|
||||
seekFileStream(seekPercentage);
|
||||
|
||||
return true;
|
||||
@ -406,6 +169,7 @@ bool AMMod::handleMessage(const Message& cmd)
|
||||
else if (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
qDebug() << "AMMod::handleMessage: MsgConfigureCWKeyer";
|
||||
|
||||
if (m_settings.m_useReverseAPI) {
|
||||
webapiReverseSendCWSettings(cfg.getSettings());
|
||||
@ -413,33 +177,14 @@ bool AMMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "AMMod::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
// Forward to the source
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "AMMod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSource->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -483,76 +228,6 @@ void AMMod::seekFileStream(int seekPercentage)
|
||||
}
|
||||
}
|
||||
|
||||
void AMMod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("AMMod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
sampleRate, m_settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void AMMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("AMMod::applyFeedbackAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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_settingsMutex.unlock();
|
||||
|
||||
m_feedbackAudioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void AMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "AMMod::applyChannelSettings:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate
|
||||
<< " outputSampleRate: " << outputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
m_outputSampleRate = outputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void AMMod::applySettings(const AMModSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "AMMod::applySettings:"
|
||||
@ -599,23 +274,12 @@ void AMMod::applySettings(const AMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("modAFInput");
|
||||
}
|
||||
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
|
||||
reverseAPIKeys.append("rfBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force)
|
||||
{
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
reverseAPIKeys.append("toneFrequency");
|
||||
m_settingsMutex.lock();
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
||||
@ -623,12 +287,14 @@ void AMMod::applySettings(const AMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("audioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSource(m_basebandSource->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
if (m_basebandSource->getAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("audioSampleRate");
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -637,12 +303,14 @@ void AMMod::applySettings(const AMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("feedbackAudioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
|
||||
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSink(m_basebandSource->getFeedbackAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_feedbackAudioSampleRate != audioSampleRate) {
|
||||
if (m_basebandSource->getFeedbackAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("feedbackAudioSampleRate");
|
||||
applyFeedbackAudioSampleRate(audioSampleRate);
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioOutput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -651,20 +319,17 @@ void AMMod::applySettings(const AMModSettings& settings, bool force)
|
||||
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
||||
{
|
||||
m_deviceAPI->removeChannelSourceAPI(this, m_settings.m_streamIndex);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer, m_settings.m_streamIndex);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer, settings.m_streamIndex);
|
||||
m_deviceAPI->removeChannelSource(this, m_settings.m_streamIndex);
|
||||
m_deviceAPI->addChannelSource(this, settings.m_streamIndex);
|
||||
m_deviceAPI->addChannelSourceAPI(this, settings.m_streamIndex);
|
||||
// apply stream sample rate to itself
|
||||
applyChannelSettings(
|
||||
m_basebandSampleRate,
|
||||
m_deviceAPI->getSampleMIMO()->getSinkSampleRate(settings.m_streamIndex),
|
||||
m_inputFrequencyOffset
|
||||
);
|
||||
}
|
||||
|
||||
reverseAPIKeys.append("streamIndex");
|
||||
}
|
||||
|
||||
AMModBaseband::MsgConfigureAMModBaseband *msg = AMModBaseband::MsgConfigureAMModBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -710,7 +375,7 @@ int AMMod::webapiSettingsGet(
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
return 200;
|
||||
@ -729,11 +394,11 @@ int AMMod::webapiSettingsPutPatch(
|
||||
if (channelSettingsKeys.contains("cwKeyer"))
|
||||
{
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getAmModSettings()->getCwKeyer();
|
||||
CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
CWKeyerSettings cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiSettingsPutPatch(channelSettingsKeys, cwKeyerSettings, apiCwKeyerSettings);
|
||||
|
||||
CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force);
|
||||
m_cwKeyer.getInputMessageQueue()->push(msgCwKeyer);
|
||||
m_basebandSource->getCWKeyer().getInputMessageQueue()->push(msgCwKeyer);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -744,8 +409,8 @@ int AMMod::webapiSettingsPutPatch(
|
||||
|
||||
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
|
||||
{
|
||||
AMMod::MsgConfigureChannelizer *msgChan = AMMod::MsgConfigureChannelizer::create(
|
||||
m_audioSampleRate, settings.m_inputFrequencyOffset);
|
||||
AMModBaseband::MsgConfigureChannelizer *msgChan = AMModBaseband::MsgConfigureChannelizer::create(
|
||||
m_basebandSource->getAudioSampleRate(), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(msgChan);
|
||||
}
|
||||
|
||||
@ -874,8 +539,8 @@ void AMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respons
|
||||
void AMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
||||
{
|
||||
response.getAmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
response.getAmModReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getAmModReport()->setChannelSampleRate(m_outputSampleRate);
|
||||
response.getAmModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
|
||||
response.getAmModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void AMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AMModSettings& settings, bool force)
|
||||
@ -926,10 +591,10 @@ void AMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const
|
||||
|
||||
if (force)
|
||||
{
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
@ -940,13 +605,14 @@ void AMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -961,7 +627,7 @@ void AMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
|
||||
swgAMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgAMModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
@ -971,13 +637,14 @@ void AMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -992,10 +659,28 @@ void AMMod::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("AMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("AMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
double AMMod::getMagSq() const
|
||||
{
|
||||
return m_basebandSource->getMagSq();
|
||||
}
|
||||
|
||||
CWKeyer *AMMod::getCWKeyer()
|
||||
{
|
||||
return &m_basebandSource->getCWKeyer();
|
||||
}
|
||||
|
||||
void AMMod::setLevelMeter(QObject *levelMeter)
|
||||
{
|
||||
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
}
|
@ -27,22 +27,16 @@
|
||||
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "ammodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class QThread;
|
||||
class AMModBaseband;
|
||||
class DeviceAPI;
|
||||
class CWKeyer;
|
||||
|
||||
class AMMod : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -71,26 +65,33 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -205,10 +206,9 @@ public:
|
||||
~AMMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -251,24 +251,14 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
|
||||
CWKeyer *getCWKeyer() { return &m_cwKeyer; }
|
||||
double getMagSq() const;
|
||||
CWKeyer *getCWKeyer();
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
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:
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
@ -276,41 +266,10 @@ private:
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_basebandSampleRate;
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
AMModBaseband* m_basebandSource;
|
||||
AMModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
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;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
@ -320,25 +279,10 @@ private:
|
||||
quint32 m_recordLength; //!< record length in seconds computed from file size
|
||||
int m_sampleRate;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
void processOneSample(Complex& ci);
|
||||
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const AMModSettings& settings, bool force = false);
|
||||
void pullAF(Real& sample);
|
||||
void pushFeedback(Real sample);
|
||||
void calculateLevel(Real& sample);
|
||||
void modulateSample();
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekPercentage);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
229
plugins/channeltx/modam/ammodbaseband.cpp
Normal file
229
plugins/channeltx/modam/ammodbaseband.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "ammodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(AMModBaseband::MsgConfigureAMModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(AMModBaseband::MsgConfigureChannelizer, Message)
|
||||
|
||||
AMModBaseband::AMModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("AMModBaseband::AMModBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&AMModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(m_source.getAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate());
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_source.getFeedbackAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyFeedbackAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
AMModBaseband::~AMModBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_source.getFeedbackAudioFifo());
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void AMModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void AMModBaseband::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 AMModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
Real rmsLevel, peakLevel, 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 AMModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void AMModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AMModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "AMModBaseband::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_source.getAudioSampleRate()) {
|
||||
m_source.applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_source.getFeedbackAudioSampleRate()) {
|
||||
m_source.applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureAMModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureAMModBaseband& cfg = (MsgConfigureAMModBaseband&) cmd;
|
||||
qDebug() << "AMModBaseband::handleMessage: MsgConfigureAMModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "AMModBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< "(requested) sourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "AMModBaseband::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());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
qDebug() << "AMModBaseband::handleMessage: MsgConfigureCWKeyer";
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
CWKeyer::MsgConfigureCWKeyer *notif = new CWKeyer::MsgConfigureCWKeyer(cfg);
|
||||
CWKeyer& cwKeyer = m_source.getCWKeyer();
|
||||
cwKeyer.getInputMessageQueue()->push(notif);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AMModBaseband::applySettings(const AMModSettings& settings, bool force)
|
||||
{
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int AMModBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
123
plugins/channeltx/modam/ammodbaseband.h
Normal file
123
plugins/channeltx/modam/ammodbaseband.h
Normal file
@ -0,0 +1,123 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_AMMODBASEBAND_H
|
||||
#define INCLUDE_AMMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "ammodsource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class AMModBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureAMModBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const AMModSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureAMModBaseband* create(const AMModSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureAMModBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
AMModSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureAMModBaseband(const AMModSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
AMModBaseband();
|
||||
~AMModBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
CWKeyer& getCWKeyer() { return m_source.getCWKeyer(); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
unsigned int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
|
||||
unsigned 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(); }
|
||||
|
||||
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;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
AMModSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
AMModSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const AMModSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_AMMODBASEBAND_H
|
@ -22,12 +22,12 @@
|
||||
#include <QDebug>
|
||||
|
||||
#include "device/deviceuiset.h"
|
||||
#include "dsp/upchannelizer.h"
|
||||
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "util/db.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/audioselectdialog.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
@ -396,7 +396,7 @@ AMModGUI::AMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampl
|
||||
ui->cwKeyerGUI->setCWKeyer(m_amMod->getCWKeyer());
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
connect(m_amMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
m_amMod->setLevelMeter(ui->volumeMeter);
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor AMModPlugin::m_pluginDescriptor = {
|
||||
QString("AM Modulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
330
plugins/channeltx/modam/ammodsource.cpp
Normal file
330
plugins/channeltx/modam/ammodsource.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "ammodsource.h"
|
||||
|
||||
const int AMModSource::m_levelNbSamples = 480; // every 10ms
|
||||
|
||||
AMModSource::AMModSource() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_ifstream(nullptr),
|
||||
m_audioSampleRate(48000)
|
||||
{
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
AMModSource::~AMModSource()
|
||||
{
|
||||
}
|
||||
|
||||
void AMModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void AMModSource::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 AMModSource::prefetch(unsigned int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
|
||||
pullAudio(nbSamplesAudio);
|
||||
}
|
||||
|
||||
void AMModSource::pullAudio(unsigned int nbSamples)
|
||||
{
|
||||
|
||||
if (nbSamples > m_audioBuffer.size()) {
|
||||
m_audioBuffer.resize(nbSamples);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamples);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void AMModSource::modulateSample()
|
||||
{
|
||||
Real t;
|
||||
|
||||
pullAF(t);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(t);
|
||||
m_audioBufferFill++;
|
||||
|
||||
m_modSample.real((t*m_settings.m_modFactor + 1.0f) * 16384.0f); // modulate and scale zero frequency carrier
|
||||
m_modSample.imag(0.0f);
|
||||
}
|
||||
|
||||
void AMModSource::pullAF(Real& sample)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case AMModSettings::AMModInputTone:
|
||||
sample = m_toneNco.next();
|
||||
break;
|
||||
case AMModSettings::AMModInputFile:
|
||||
// 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 AMModSettings::AMModInputAudio:
|
||||
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
|
||||
break;
|
||||
case AMModSettings::AMModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = 0.0f;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AMModSettings::AMModInputNone:
|
||||
default:
|
||||
sample = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AMModSource::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 AMModSource::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("AMModChannelSource::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AMModSource::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 AMModSource::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("AMModSource::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_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
m_cwKeyer.reset();
|
||||
m_audioSampleRate = sampleRate;
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void AMModSource::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("AMModSource::applyFeedbackAudioSampleRate: %u", 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 AMModSource::applySettings(const AMModSettings& 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);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
void AMModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "AMModSource::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;
|
||||
}
|
117
plugins/channeltx/modam/ammodsource.h
Normal file
117
plugins/channeltx/modam/ammodsource.h
Normal file
@ -0,0 +1,117 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_AMMODSOURCE_H
|
||||
#define INCLUDE_AMMODSOURCE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
#include "ammodsettings.h"
|
||||
|
||||
class AMModSource : public ChannelSampleSource
|
||||
{
|
||||
public:
|
||||
AMModSource();
|
||||
virtual ~AMModSource();
|
||||
|
||||
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(unsigned int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
unsigned int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
|
||||
CWKeyer& getCWKeyer() { return m_cwKeyer; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
void getLevels(Real& rmsLevel, Real& peakLevel, Real& numSamples) const
|
||||
{
|
||||
rmsLevel = m_rmsLevel;
|
||||
peakLevel = m_peakLevel;
|
||||
numSamples = m_levelNbSamples;
|
||||
}
|
||||
void applySettings(const AMModSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
|
||||
private:
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
AMModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
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;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_rmsLevel;
|
||||
Real m_peakLevelOut;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
std::ifstream *m_ifstream;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void processOneSample(Complex& ci);
|
||||
void pullAF(Real& sample);
|
||||
void pullAudio(unsigned int nbSamples);
|
||||
void pushFeedback(Real sample);
|
||||
void calculateLevel(Real& sample);
|
||||
void modulateSample();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // INCLUDE_AMMODSOURCE_H
|
@ -16,6 +16,7 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "ammod.h"
|
||||
#include "ammodwebapiadapter.h"
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
project(modatv)
|
||||
|
||||
set(modatv_SOURCES
|
||||
atvmod.cpp
|
||||
atvmod.cpp
|
||||
atvmodbaseband.cpp
|
||||
atvmodreport.cpp
|
||||
atvmodsource.cpp
|
||||
atvmodplugin.cpp
|
||||
atvmodsettings.cpp
|
||||
atvmodwebapiadapter.cpp
|
||||
@ -9,6 +12,9 @@ set(modatv_SOURCES
|
||||
|
||||
set(modatv_HEADERS
|
||||
atvmod.h
|
||||
atvmodbaseband.h
|
||||
atvmodreport.h
|
||||
atvmodsource.h
|
||||
atvmodplugin.h
|
||||
atvmodsettings.h
|
||||
atvmodwebapiadapter.h
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -19,32 +19,23 @@
|
||||
#define PLUGINS_CHANNELTX_MODATV_ATVMOD_H_
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "atvmodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class ATVModBaseband;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
|
||||
class ATVMod : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -73,23 +64,50 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int centerFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(centerFrequency);
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency) {
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureSourceCenterFrequency : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
public:
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureSourceCenterFrequency *create(int sourceCenterFrequency) {
|
||||
return new MsgConfigureSourceCenterFrequency(sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureSourceCenterFrequency(int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -173,52 +191,6 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportVideoFileSourceStreamTiming : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getFrameCount() const { return m_frameCount; }
|
||||
|
||||
static MsgReportVideoFileSourceStreamTiming* create(int frameCount)
|
||||
{
|
||||
return new MsgReportVideoFileSourceStreamTiming(frameCount);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_frameCount;
|
||||
|
||||
MsgReportVideoFileSourceStreamTiming(int frameCount) :
|
||||
Message(),
|
||||
m_frameCount(frameCount)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportVideoFileSourceStreamData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getFrameRate() const { return m_frameRate; }
|
||||
quint32 getVideoLength() const { return m_videoLength; }
|
||||
|
||||
static MsgReportVideoFileSourceStreamData* create(int frameRate,
|
||||
quint32 recordLength)
|
||||
{
|
||||
return new MsgReportVideoFileSourceStreamData(frameRate, recordLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_frameRate;
|
||||
int m_videoLength; //!< Video length in frames
|
||||
|
||||
MsgReportVideoFileSourceStreamData(int frameRate,
|
||||
int videoLength) :
|
||||
Message(),
|
||||
m_frameRate(frameRate),
|
||||
m_videoLength(videoLength)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureCameraIndex : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
@ -270,99 +242,15 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportCameraData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getdeviceNumber() const { return m_deviceNumber; }
|
||||
float getFPS() const { return m_fps; }
|
||||
float getFPSManual() const { return m_fpsManual; }
|
||||
bool getFPSManualEnable() const { return m_fpsManualEnable; }
|
||||
int getWidth() const { return m_width; }
|
||||
int getHeight() const { return m_height; }
|
||||
int getStatus() const { return m_status; }
|
||||
|
||||
static MsgReportCameraData* create(
|
||||
int deviceNumber,
|
||||
float fps,
|
||||
float fpsManual,
|
||||
bool fpsManualEnable,
|
||||
int width,
|
||||
int height,
|
||||
int status)
|
||||
{
|
||||
return new MsgReportCameraData(
|
||||
deviceNumber,
|
||||
fps,
|
||||
fpsManual,
|
||||
fpsManualEnable,
|
||||
width,
|
||||
height,
|
||||
status);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_deviceNumber;
|
||||
float m_fps;
|
||||
float m_fpsManual;
|
||||
bool m_fpsManualEnable;
|
||||
int m_width;
|
||||
int m_height;
|
||||
int m_status;
|
||||
|
||||
MsgReportCameraData(
|
||||
int deviceNumber,
|
||||
float fps,
|
||||
float fpsManual,
|
||||
bool fpsManualEnable,
|
||||
int width,
|
||||
int height,
|
||||
int status) :
|
||||
Message(),
|
||||
m_deviceNumber(deviceNumber),
|
||||
m_fps(fps),
|
||||
m_fpsManual(fpsManual),
|
||||
m_fpsManualEnable(fpsManualEnable),
|
||||
m_width(width),
|
||||
m_height(height),
|
||||
m_status(status)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportEffectiveSampleRate : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
uint32_t gatNbPointsPerLine() const { return m_nbPointsPerLine; }
|
||||
|
||||
static MsgReportEffectiveSampleRate* create(int sampleRate, uint32_t nbPointsPerLine)
|
||||
{
|
||||
return new MsgReportEffectiveSampleRate(sampleRate, nbPointsPerLine);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
uint32_t m_nbPointsPerLine;
|
||||
|
||||
MsgReportEffectiveSampleRate(
|
||||
int sampleRate,
|
||||
uint32_t nbPointsPerLine) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_nbPointsPerLine(nbPointsPerLine)
|
||||
{ }
|
||||
};
|
||||
//=================================================================
|
||||
|
||||
ATVMod(DeviceAPI *deviceAPI);
|
||||
~ATVMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples); // this is used for video signal actually
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -405,441 +293,32 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
int getEffectiveSampleRate() const { return m_tvSampleRate; };
|
||||
double getMagSq() const { return m_movingAverage.asDouble(); }
|
||||
void getCameraNumbers(std::vector<int>& numbers);
|
||||
|
||||
static void getBaseValues(int outputSampleRate, int linesPerSecond, int& sampleRateUnits, uint32_t& nbPointsPerRateUnit);
|
||||
static float getRFBandwidthDivisor(ATVModSettings::ATVModulation modulation);
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
double getMagSq() const;
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
int getEffectiveSampleRate() const;
|
||||
void getCameraNumbers(std::vector<int>& numbers);
|
||||
void propagateMessageQueueToGUI();
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
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 slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
struct ATVCamera
|
||||
{
|
||||
cv::VideoCapture m_camera; //!< camera object
|
||||
cv::Mat m_videoframeOriginal; //!< camera non resized image
|
||||
cv::Mat m_videoFrame; //!< displayable camera frame
|
||||
int m_cameraNumber; //!< camera device number
|
||||
float m_videoFPS; //!< camera FPS rate
|
||||
float m_videoFPSManual; //!< camera FPS rate manually set
|
||||
bool m_videoFPSManualEnable; //!< Enable camera FPS rate manual set value
|
||||
int m_videoWidth; //!< camera frame width
|
||||
int m_videoHeight; //!< camera frame height
|
||||
float m_videoFx; //!< camera horizontal scaling factor
|
||||
float m_videoFy; //!< camera vertictal scaling factor
|
||||
float m_videoFPSq; //!< camera FPS sacaling factor
|
||||
float m_videoFPSqManual; //!< camera FPS sacaling factor manually set
|
||||
float m_videoFPSCount; //!< camera FPS fractional counter
|
||||
int m_videoPrevFPSCount; //!< camera FPS previous integer counter
|
||||
|
||||
ATVCamera() :
|
||||
m_cameraNumber(-1),
|
||||
m_videoFPS(25.0f),
|
||||
m_videoFPSManual(20.0f),
|
||||
m_videoFPSManualEnable(false),
|
||||
m_videoWidth(1),
|
||||
m_videoHeight(1),
|
||||
m_videoFx(1.0f),
|
||||
m_videoFy(1.0f),
|
||||
m_videoFPSq(1.0f),
|
||||
m_videoFPSqManual(1.0f),
|
||||
m_videoFPSCount(0.0f),
|
||||
m_videoPrevFPSCount(0)
|
||||
{}
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
ATVModBaseband* m_basebandSource;
|
||||
ATVModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
Complex m_modSample;
|
||||
float m_modPhasor; //!< For FM modulation
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
int m_tvSampleRate; //!< sample rate for generating signal
|
||||
uint32_t m_pointsPerLine; //!< Number of points per full line
|
||||
int m_pointsPerSync; //!< number of line points for the horizontal sync
|
||||
int m_pointsPerBP; //!< number of line points for the back porch
|
||||
int m_pointsPerImgLine; //!< number of line points for the image line
|
||||
uint32_t m_pointsPerFP; //!< number of line points for the front porch
|
||||
int m_pointsPerFSync; //!< number of line points for the field first sync
|
||||
uint32_t m_pointsPerHBar; //!< number of line points for a bar of the bar chart
|
||||
uint32_t m_linesPerVBar; //!< number of lines for a bar of the bar chart
|
||||
uint32_t m_pointsPerTU; //!< number of line points per time unit
|
||||
int m_nbLines; //!< number of lines per complete frame
|
||||
int m_nbLines2; //!< same number as above (non interlaced) or half the number above (interlaced)
|
||||
uint32_t m_nbImageLines; //!< number of image lines excluding synchronization lines
|
||||
uint32_t m_nbImageLines2; //!< same number as above (non interlaced) or half the number above (interlaced)
|
||||
int m_nbHorizPoints; //!< number of line points per horizontal line
|
||||
int m_nbSyncLinesHeadE; //!< number of header sync lines on even frame
|
||||
int m_nbSyncLinesHeadO; //!< number of header sync lines on odd frame
|
||||
int m_nbSyncLinesBottom;//!< number of sync lines at bottom
|
||||
int m_nbLongSyncLines; //!< number of whole long sync lines for vertical synchronization
|
||||
int m_nbHalfLongSync; //!< number of half long sync / equalization lines
|
||||
int m_nbWholeEqLines; //!< number of whole equalizing lines
|
||||
bool m_singleLongSync; //!< single or double long sync per long sync line
|
||||
int m_nbBlankLines; //!< number of lines in a frame (full or half) that are blanked (black) at the top of the image
|
||||
float m_blankLineLvel; //!< video level of blank lines
|
||||
float m_hBarIncrement; //!< video level increment at each horizontal bar increment
|
||||
float m_vBarIncrement; //!< video level increment at each vertical bar increment
|
||||
bool m_interleaved; //!< true if image is interlaced (2 half frames per frame)
|
||||
bool m_evenImage; //!< in interlaced mode true if this is an even image
|
||||
QMutex m_settingsMutex;
|
||||
int m_horizontalCount; //!< current point index on line
|
||||
int m_lineCount; //!< current line index in frame
|
||||
float m_fps; //!< resulting frames per second
|
||||
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
cv::Mat m_imageFromFile; //!< original image not resized not overlaid by text
|
||||
cv::Mat m_imageOriginal; //!< original not resized image
|
||||
cv::Mat m_image; //!< resized image for transmission at given rate
|
||||
bool m_imageOK;
|
||||
|
||||
cv::VideoCapture m_video; //!< current video capture
|
||||
cv::Mat m_videoframeOriginal; //!< current frame from video
|
||||
cv::Mat m_videoFrame; //!< current displayable video frame
|
||||
float m_videoFPS; //!< current video FPS rate
|
||||
int m_videoWidth; //!< current video frame width
|
||||
int m_videoHeight; //!< current video frame height
|
||||
float m_videoFx; //!< current video horizontal scaling factor
|
||||
float m_videoFy; //!< current video vertictal scaling factor
|
||||
float m_videoFPSq; //!< current video FPS sacaling factor
|
||||
float m_videoFPSCount; //!< current video FPS fractional counter
|
||||
int m_videoPrevFPSCount; //!< current video FPS previous integer counter
|
||||
int m_videoLength; //!< current video length in frames
|
||||
bool m_videoEOF; //!< current video has reached end of file
|
||||
bool m_videoOK;
|
||||
|
||||
std::vector<ATVCamera> m_cameras; //!< vector of available cameras
|
||||
int m_cameraIndex; //!< curent camera index in list of available cameras
|
||||
|
||||
std::string m_overlayText;
|
||||
QString m_imageFileName;
|
||||
QString m_videoFileName;
|
||||
|
||||
// Used for standard SSB
|
||||
fftfilt* m_SSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
|
||||
// Used for vestigial SSB with asymmetrical filtering (needs double sideband scheme)
|
||||
fftfilt* m_DSBFilter;
|
||||
Complex* m_DSBFilterBuffer;
|
||||
int m_DSBFilterBufferIndex;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
static const int m_ssbFftLen;
|
||||
static const float m_blackLevel;
|
||||
static const float m_spanLevel;
|
||||
static const int m_levelNbSamples;
|
||||
static const int m_nbBars; //!< number of bars in bar or chessboard patterns
|
||||
static const int m_cameraFPSTestNbFrames; //!< number of frames for camera FPS test
|
||||
|
||||
void applyChannelSettings(int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const ATVModSettings& settings, bool force = false);
|
||||
void pullFinalize(Complex& ci, Sample& sample);
|
||||
void pullVideo(Real& sample);
|
||||
void calculateLevel(Real& sample);
|
||||
void modulateSample();
|
||||
Complex& modulateSSB(Real& sample);
|
||||
Complex& modulateVestigialSSB(Real& sample);
|
||||
void applyStandard();
|
||||
void openImage(const QString& fileName);
|
||||
void openVideo(const QString& fileName);
|
||||
void resizeImage();
|
||||
void calculateVideoSizes();
|
||||
void resizeVideo();
|
||||
void seekVideoFileStream(int seekPercentage);
|
||||
void scanCameras();
|
||||
void releaseCameras();
|
||||
void calculateCamerasSizes();
|
||||
void resizeCameras();
|
||||
void resizeCamera();
|
||||
void mixImageAndText(cv::Mat& image);
|
||||
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const ATVModSettings& settings, bool force);
|
||||
|
||||
inline void pullImageLine(Real& sample, bool noHSync = false)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync) // sync pulse
|
||||
{
|
||||
sample = noHSync ? m_blackLevel : 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP) // back porch
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP + m_pointsPerImgLine)
|
||||
{
|
||||
int pointIndex = m_horizontalCount - (m_pointsPerSync + m_pointsPerBP);
|
||||
int oddity = m_lineCount < m_nbLines2 + 1 ? 0 : 1;
|
||||
int iLine = oddity == 0 ? m_lineCount : m_lineCount - m_nbLines2 - 1;
|
||||
int iLineImage = iLine - m_nbBlankLines - (oddity == 0 ? m_nbSyncLinesHeadE : m_nbSyncLinesHeadO);
|
||||
|
||||
switch(m_settings.m_atvModInput)
|
||||
{
|
||||
case ATVModSettings::ATVModInputHBars:
|
||||
sample = (((float)pointIndex) / m_pointsPerHBar) * m_hBarIncrement + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVBars:
|
||||
sample = (((float)iLine) / m_linesPerVBar) * m_vBarIncrement + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputChessboard:
|
||||
sample = (((iLine / m_linesPerVBar)*5 + (pointIndex / m_pointsPerHBar)) % 2) * m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputHGradient:
|
||||
sample = (pointIndex / (float) m_pointsPerImgLine) * m_spanLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVGradient:
|
||||
sample = ((iLine -5) / (float) m_nbImageLines2) * m_spanLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputImage:
|
||||
if (!m_imageOK || (iLineImage < -oddity) || m_image.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = m_image.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = m_image.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVideo:
|
||||
if (!m_videoOK || (iLineImage < -oddity) || m_videoFrame.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = m_videoFrame.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = m_videoFrame.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputCamera:
|
||||
if ((iLineImage < -oddity) || (m_cameraIndex < 0))
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATVCamera& camera = m_cameras[m_cameraIndex];
|
||||
|
||||
if (camera.m_videoFrame.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = camera.m_videoFrame.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = camera.m_videoFrame.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputUniform:
|
||||
default:
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
}
|
||||
else // front porch
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineLongPulses(Real& sample)
|
||||
{
|
||||
int halfIndex = m_horizontalCount % (m_nbHorizPoints/2);
|
||||
|
||||
if (halfIndex < (m_nbHorizPoints/2) - m_pointsPerSync) // ultra-black
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else // black
|
||||
{
|
||||
if (m_singleLongSync && (m_horizontalCount < m_nbHorizPoints/2)) {
|
||||
sample = 0.0f;
|
||||
} else {
|
||||
sample = m_blackLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineEqualizingPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineEqualizingThenLongPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < m_nbHorizPoints - m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineLongThenEqualizingPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < (m_nbHorizPoints/2) - m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLine(Real& sample)
|
||||
{
|
||||
if (m_lineCount < m_nbLines2 + 1) // even
|
||||
{
|
||||
int fieldLine = m_lineCount;
|
||||
|
||||
if (fieldLine < m_nbLongSyncLines) // 0,1: Whole line "long" pulses
|
||||
{
|
||||
pullVSyncLineLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbHalfLongSync) // long pulse then equalizing pulse
|
||||
{
|
||||
pullVSyncLineLongThenEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbHalfLongSync + m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - m_nbHalfLongSync) // equalizing pulse then long pulse
|
||||
{
|
||||
pullVSyncLineEqualizingThenLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - m_nbHalfLongSync - m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else // black images
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blankLineLvel;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // odd
|
||||
{
|
||||
int fieldLine = m_lineCount - m_nbLines2 - 1;
|
||||
|
||||
if (fieldLine < m_nbLongSyncLines) // 0,1: Whole line "long" pulses
|
||||
{
|
||||
pullVSyncLineLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - 1 - m_nbWholeEqLines - m_nbHalfLongSync) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else // black images
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blankLineLvel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
};
|
||||
|
||||
|
||||
#endif /* PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ */
|
||||
#endif /* PLUGINS_CHANNELTX_MODAM_AMMOD_H_ */
|
||||
|
255
plugins/channeltx/modatv/atvmodbaseband.cpp
Normal file
255
plugins/channeltx/modatv/atvmodbaseband.cpp
Normal file
@ -0,0 +1,255 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "atvmodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureATVModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureImageFileName, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureVideoFileName, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureVideoFileSourceSeek, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureVideoFileSourceStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureCameraIndex, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModBaseband::MsgConfigureCameraData, Message)
|
||||
|
||||
ATVModBaseband::ATVModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("AMModBaseband::AMModBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&ATVModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
ATVModBaseband::~ATVModBaseband()
|
||||
{
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void ATVModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void ATVModBaseband::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 ATVModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
Real rmsLevel, peakLevel, 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 ATVModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void ATVModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ATVModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureATVModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureATVModBaseband& cfg = (MsgConfigureATVModBaseband&) cmd;
|
||||
qDebug() << "AMModBaseband::handleMessage: MsgConfigureATVModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "ATVModBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< "(requested) sourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "ATVModBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
|
||||
m_sampleFifo.resize(4*SampleSourceFifo::getSizePolicy(notif.getSampleRate()));
|
||||
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureImageFileName::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureImageFileName& cfg = (MsgConfigureImageFileName&) cmd;
|
||||
qDebug() << "ATVModBaseband::handleMessage: MsgConfigureImageFileName: fileNaem: " << cfg.getFileName();
|
||||
m_source.openImage(cfg.getFileName());
|
||||
}
|
||||
else if (MsgConfigureVideoFileName::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureVideoFileName& cfg = (MsgConfigureVideoFileName&) cmd;
|
||||
qDebug() << "ATVModBaseband::handleMessage: MsgConfigureVideoFileName: fileName: " << cfg.getFileName();
|
||||
m_source.openVideo( cfg.getFileName());
|
||||
}
|
||||
else if (MsgConfigureVideoFileSourceSeek::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureVideoFileSourceSeek& cfg = (MsgConfigureVideoFileSourceSeek&) cmd;
|
||||
qDebug() << "ATVModBaseband::handleMessage: MsgConfigureVideoFileName: precnetage: " << cfg.getPercentage();
|
||||
m_source.seekVideoFileStream(cfg.getPercentage());
|
||||
}
|
||||
else if (MsgConfigureVideoFileSourceStreamTiming::match(cmd))
|
||||
{
|
||||
m_source.reportVideoFileSourceStreamTiming();
|
||||
}
|
||||
else if (MsgConfigureCameraIndex::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureCameraIndex& cfg = (MsgConfigureCameraIndex&) cmd;
|
||||
uint32_t index = cfg.getIndex() & 0x7FFFFFF;
|
||||
m_source.configureCameraIndex(index);
|
||||
}
|
||||
else if (MsgConfigureCameraData::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureCameraData& cfg = (MsgConfigureCameraData&) cmd;
|
||||
m_source.configureCameraData(cfg.getIndex(), cfg.getManualFPS(), cfg.getManualFPSEnable());
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ATVModBaseband::applySettings(const ATVModSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "ATVModBaseband::applySettings:"
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_rfBandwidth: " << settings.m_rfBandwidth
|
||||
<< " m_rfOppBandwidth: " << settings.m_rfOppBandwidth
|
||||
<< " m_atvStd: " << (int) settings.m_atvStd
|
||||
<< " m_nbLines: " << settings.m_nbLines
|
||||
<< " m_fps: " << settings.m_fps
|
||||
<< " m_atvModInput: " << (int) settings.m_atvModInput
|
||||
<< " m_uniformLevel: " << settings.m_uniformLevel
|
||||
<< " m_atvModulation: " << (int) settings.m_atvModulation
|
||||
<< " m_videoPlayLoop: " << settings.m_videoPlayLoop
|
||||
<< " m_videoPlay: " << settings.m_videoPlay
|
||||
<< " m_cameraPlay: " << settings.m_cameraPlay
|
||||
<< " m_channelMute: " << settings.m_channelMute
|
||||
<< " m_invertedVideo: " << settings.m_invertedVideo
|
||||
<< " m_rfScalingFactor: " << settings.m_rfScalingFactor
|
||||
<< " m_fmExcursion: " << settings.m_fmExcursion
|
||||
<< " m_forceDecimator: " << settings.m_forceDecimator
|
||||
<< " m_showOverlayText: " << settings.m_showOverlayText
|
||||
<< " m_overlayText: " << settings.m_overlayText
|
||||
<< " force: " << force;
|
||||
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int ATVModBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
||||
|
||||
void ATVModBaseband::getCameraNumbers(std::vector<int>& numbers)
|
||||
{
|
||||
m_source.getCameraNumbers(numbers);
|
||||
}
|
252
plugins/channeltx/modatv/atvmodbaseband.h
Normal file
252
plugins/channeltx/modatv/atvmodbaseband.h
Normal file
@ -0,0 +1,252 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_ATVMODBASEBAND_H
|
||||
#define INCLUDE_ATVMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "atvmodsource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class ATVModBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureATVModBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const ATVModSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureATVModBaseband* create(const ATVModSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureATVModBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
ATVModSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureATVModBaseband(const ATVModSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureImageFileName : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getFileName() const { return m_fileName; }
|
||||
|
||||
static MsgConfigureImageFileName* create(const QString& fileName)
|
||||
{
|
||||
return new MsgConfigureImageFileName(fileName);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_fileName;
|
||||
|
||||
MsgConfigureImageFileName(const QString& fileName) :
|
||||
Message(),
|
||||
m_fileName(fileName)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureVideoFileName : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getFileName() const { return m_fileName; }
|
||||
|
||||
static MsgConfigureVideoFileName* create(const QString& fileName)
|
||||
{
|
||||
return new MsgConfigureVideoFileName(fileName);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_fileName;
|
||||
|
||||
MsgConfigureVideoFileName(const QString& fileName) :
|
||||
Message(),
|
||||
m_fileName(fileName)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureVideoFileSourceSeek : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getPercentage() const { return m_seekPercentage; }
|
||||
|
||||
static MsgConfigureVideoFileSourceSeek* create(int seekPercentage)
|
||||
{
|
||||
return new MsgConfigureVideoFileSourceSeek(seekPercentage);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
|
||||
|
||||
MsgConfigureVideoFileSourceSeek(int seekPercentage) :
|
||||
Message(),
|
||||
m_seekPercentage(seekPercentage)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureVideoFileSourceStreamTiming : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
|
||||
static MsgConfigureVideoFileSourceStreamTiming* create()
|
||||
{
|
||||
return new MsgConfigureVideoFileSourceStreamTiming();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
MsgConfigureVideoFileSourceStreamTiming() :
|
||||
Message()
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureCameraIndex : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getIndex() const { return m_index; }
|
||||
|
||||
static MsgConfigureCameraIndex* create(int index)
|
||||
{
|
||||
return new MsgConfigureCameraIndex(index);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_index;
|
||||
|
||||
MsgConfigureCameraIndex(int index) :
|
||||
Message(),
|
||||
m_index(index)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureCameraData : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getIndex() const { return m_index; }
|
||||
float getManualFPS() const { return m_manualFPS; }
|
||||
bool getManualFPSEnable() const { return m_manualFPSEnable; }
|
||||
|
||||
static MsgConfigureCameraData* create(
|
||||
int index,
|
||||
float manualFPS,
|
||||
bool manualFPSEnable)
|
||||
{
|
||||
return new MsgConfigureCameraData(index, manualFPS, manualFPSEnable);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_index;
|
||||
float m_manualFPS;
|
||||
bool m_manualFPSEnable;
|
||||
|
||||
MsgConfigureCameraData(int index, float manualFPS, bool manualFPSEnable) :
|
||||
Message(),
|
||||
m_index(index),
|
||||
m_manualFPS(manualFPS),
|
||||
m_manualFPSEnable(manualFPSEnable)
|
||||
{ }
|
||||
};
|
||||
|
||||
ATVModBaseband();
|
||||
~ATVModBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_source.setMessageQueueToGUI(messageQueue); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
int getChannelSampleRate() const;
|
||||
void getCameraNumbers(std::vector<int>& numbers);
|
||||
|
||||
int getEffectiveSampleRate() const { return m_source.getEffectiveSampleRate(); }
|
||||
|
||||
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;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
ATVModSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
ATVModSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const ATVModSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_ATVMODBASEBAND_H
|
@ -34,6 +34,7 @@
|
||||
|
||||
#include "ui_atvmodgui.h"
|
||||
#include "atvmodgui.h"
|
||||
#include "atvmodreport.h"
|
||||
|
||||
ATVModGUI* ATVModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx)
|
||||
{
|
||||
@ -46,6 +47,80 @@ void ATVModGUI::destroy()
|
||||
delete this;
|
||||
}
|
||||
|
||||
ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
|
||||
RollupWidget(parent),
|
||||
ui(new Ui::ATVModGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_channelMarker(this),
|
||||
m_doApplySettings(true),
|
||||
m_videoLength(0),
|
||||
m_videoFrameRate(48000),
|
||||
m_frameCount(0),
|
||||
m_tickCount(0),
|
||||
m_enableNavTime(false),
|
||||
m_camBusyFPSMessageBox(0),
|
||||
m_rfSliderDivisor(100000)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
|
||||
|
||||
m_atvMod = (ATVMod*) channelTx; //new ATVMod(m_deviceUISet->m_deviceSinkAPI);
|
||||
m_atvMod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
m_atvMod->propagateMessageQueueToGUI();
|
||||
|
||||
connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
|
||||
|
||||
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(m_settings.m_rgbColor);
|
||||
m_channelMarker.setBandwidth(5000);
|
||||
m_channelMarker.setCenterFrequency(0);
|
||||
m_channelMarker.setTitle("ATV Modulator");
|
||||
m_channelMarker.setSourceOrSinkStream(false);
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setVisible(true); // activate signal on the last setting only
|
||||
|
||||
setTitleColor(m_channelMarker.getColor());
|
||||
m_settings.setChannelMarker(&m_channelMarker);
|
||||
|
||||
m_deviceUISet->registerTxChannelInstance(ATVMod::m_channelIdURI, this);
|
||||
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
||||
m_deviceUISet->addRollupWidget(this);
|
||||
|
||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
||||
|
||||
resetToDefaults();
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
m_atvMod->setLevelMeter(ui->volumeMeter);
|
||||
|
||||
std::vector<int> cameraNumbers;
|
||||
m_atvMod->getCameraNumbers(cameraNumbers);
|
||||
|
||||
for (std::vector<int>::iterator it = cameraNumbers.begin(); it != cameraNumbers.end(); ++it) {
|
||||
ui->camSelect->addItem(tr("%1").arg(*it));
|
||||
}
|
||||
|
||||
QChar delta = QChar(0x94, 0x03);
|
||||
ui->fmExcursionLabel->setText(delta);
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
ATVModGUI::~ATVModGUI()
|
||||
{
|
||||
m_deviceUISet->removeTxChannelInstance(this);
|
||||
delete m_atvMod; // TODO: check this: when the GUI closes it has to delete the modulator
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ATVModGUI::setName(const QString& name)
|
||||
{
|
||||
setObjectName(name);
|
||||
@ -95,23 +170,23 @@ bool ATVModGUI::deserialize(const QByteArray& data)
|
||||
|
||||
bool ATVModGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (ATVMod::MsgReportVideoFileSourceStreamData::match(message))
|
||||
if (ATVModReport::MsgReportVideoFileSourceStreamData::match(message))
|
||||
{
|
||||
m_videoFrameRate = ((ATVMod::MsgReportVideoFileSourceStreamData&)message).getFrameRate();
|
||||
m_videoLength = ((ATVMod::MsgReportVideoFileSourceStreamData&)message).getVideoLength();
|
||||
m_videoFrameRate = ((ATVModReport::MsgReportVideoFileSourceStreamData&)message).getFrameRate();
|
||||
m_videoLength = ((ATVModReport::MsgReportVideoFileSourceStreamData&)message).getVideoLength();
|
||||
m_frameCount = 0;
|
||||
updateWithStreamData();
|
||||
return true;
|
||||
}
|
||||
else if (ATVMod::MsgReportVideoFileSourceStreamTiming::match(message))
|
||||
else if (ATVModReport::MsgReportVideoFileSourceStreamTiming::match(message))
|
||||
{
|
||||
m_frameCount = ((ATVMod::MsgReportVideoFileSourceStreamTiming&)message).getFrameCount();
|
||||
m_frameCount = ((ATVModReport::MsgReportVideoFileSourceStreamTiming&)message).getFrameCount();
|
||||
updateWithStreamTime();
|
||||
return true;
|
||||
}
|
||||
else if (ATVMod::MsgReportCameraData::match(message))
|
||||
else if (ATVModReport::MsgReportCameraData::match(message))
|
||||
{
|
||||
ATVMod::MsgReportCameraData& rpt = (ATVMod::MsgReportCameraData&) message;
|
||||
ATVModReport::MsgReportCameraData& rpt = (ATVModReport::MsgReportCameraData&) message;
|
||||
ui->cameraDeviceNumber->setText(tr("#%1").arg(rpt.getdeviceNumber()));
|
||||
ui->camerFPS->setText(tr("%1 FPS").arg(rpt.getFPS(), 0, 'f', 2));
|
||||
ui->cameraImageSize->setText(tr("%1x%2").arg(rpt.getWidth()).arg(rpt.getHeight()));
|
||||
@ -141,10 +216,10 @@ bool ATVModGUI::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (ATVMod::MsgReportEffectiveSampleRate::match(message))
|
||||
else if (ATVModReport::MsgReportEffectiveSampleRate::match(message))
|
||||
{
|
||||
int sampleRate = ((ATVMod::MsgReportEffectiveSampleRate&)message).getSampleRate();
|
||||
uint32_t nbPointsPerLine = ((ATVMod::MsgReportEffectiveSampleRate&)message).gatNbPointsPerLine();
|
||||
int sampleRate = ((ATVModReport::MsgReportEffectiveSampleRate&)message).getSampleRate();
|
||||
uint32_t nbPointsPerLine = ((ATVModReport::MsgReportEffectiveSampleRate&)message).gatNbPointsPerLine();
|
||||
ui->channelSampleRateText->setText(tr("%1k").arg(sampleRate/1000.0f, 0, 'f', 2));
|
||||
ui->nbPointsPerLineText->setText(tr("%1p").arg(nbPointsPerLine));
|
||||
setRFFiltersSlidersRange(sampleRate);
|
||||
@ -644,79 +719,6 @@ void ATVModGUI::onMenuDialogCalled(const QPoint &p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
|
||||
RollupWidget(parent),
|
||||
ui(new Ui::ATVModGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_channelMarker(this),
|
||||
m_doApplySettings(true),
|
||||
m_videoLength(0),
|
||||
m_videoFrameRate(48000),
|
||||
m_frameCount(0),
|
||||
m_tickCount(0),
|
||||
m_enableNavTime(false),
|
||||
m_camBusyFPSMessageBox(0),
|
||||
m_rfSliderDivisor(100000)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
|
||||
|
||||
m_atvMod = (ATVMod*) channelTx; //new ATVMod(m_deviceUISet->m_deviceSinkAPI);
|
||||
m_atvMod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
|
||||
connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
|
||||
|
||||
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(m_settings.m_rgbColor);
|
||||
m_channelMarker.setBandwidth(5000);
|
||||
m_channelMarker.setCenterFrequency(0);
|
||||
m_channelMarker.setTitle("ATV Modulator");
|
||||
m_channelMarker.setSourceOrSinkStream(false);
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setVisible(true); // activate signal on the last setting only
|
||||
|
||||
setTitleColor(m_channelMarker.getColor());
|
||||
m_settings.setChannelMarker(&m_channelMarker);
|
||||
|
||||
m_deviceUISet->registerTxChannelInstance(ATVMod::m_channelIdURI, this);
|
||||
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
||||
m_deviceUISet->addRollupWidget(this);
|
||||
|
||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
||||
|
||||
resetToDefaults();
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
connect(m_atvMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
|
||||
std::vector<int> cameraNumbers;
|
||||
m_atvMod->getCameraNumbers(cameraNumbers);
|
||||
|
||||
for (std::vector<int>::iterator it = cameraNumbers.begin(); it != cameraNumbers.end(); ++it) {
|
||||
ui->camSelect->addItem(tr("%1").arg(*it));
|
||||
}
|
||||
|
||||
QChar delta = QChar(0x94, 0x03);
|
||||
ui->fmExcursionLabel->setText(delta);
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
ATVModGUI::~ATVModGUI()
|
||||
{
|
||||
m_deviceUISet->removeTxChannelInstance(this);
|
||||
delete m_atvMod; // TODO: check this: when the GUI closes it has to delete the modulator
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ATVModGUI::blockApplySettings(bool block)
|
||||
{
|
||||
m_doApplySettings = !block;
|
||||
@ -726,7 +728,7 @@ void ATVModGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
ATVMod::MsgConfigureChannelizer *msgChan = ATVMod::MsgConfigureChannelizer::create(
|
||||
ATVMod::MsgConfigureSourceCenterFrequency *msgChan = ATVMod::MsgConfigureSourceCenterFrequency::create(
|
||||
m_channelMarker.getCenterFrequency());
|
||||
m_atvMod->getInputMessageQueue()->push(msgChan);
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor ATVModPlugin::m_pluginDescriptor = {
|
||||
QString("ATV Modulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
29
plugins/channeltx/modatv/atvmodreport.cpp
Normal file
29
plugins/channeltx/modatv/atvmodreport.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "atvmodreport.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ATVModReport::MsgReportVideoFileSourceStreamTiming, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModReport::MsgReportVideoFileSourceStreamData, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModReport::MsgReportCameraData, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ATVModReport::MsgReportEffectiveSampleRate, Message)
|
||||
|
||||
ATVModReport::ATVModReport()
|
||||
{ }
|
||||
|
||||
ATVModReport::~ATVModReport()
|
||||
{ }
|
165
plugins/channeltx/modatv/atvmodreport.h
Normal file
165
plugins/channeltx/modatv/atvmodreport.h
Normal file
@ -0,0 +1,165 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef PLUGINS_CHANNELTX_MODATV_ATVMODREPORT_H_
|
||||
#define PLUGINS_CHANNELTX_MODATV_ATVMODREPORT_H_
|
||||
|
||||
#include <vector>
|
||||
#include <QObject>
|
||||
#include "util/message.h"
|
||||
|
||||
class ATVModReport : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgReportVideoFileSourceStreamTiming : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getFrameCount() const { return m_frameCount; }
|
||||
|
||||
static MsgReportVideoFileSourceStreamTiming* create(int frameCount)
|
||||
{
|
||||
return new MsgReportVideoFileSourceStreamTiming(frameCount);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_frameCount;
|
||||
|
||||
MsgReportVideoFileSourceStreamTiming(int frameCount) :
|
||||
Message(),
|
||||
m_frameCount(frameCount)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportVideoFileSourceStreamData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getFrameRate() const { return m_frameRate; }
|
||||
quint32 getVideoLength() const { return m_videoLength; }
|
||||
|
||||
static MsgReportVideoFileSourceStreamData* create(int frameRate,
|
||||
quint32 recordLength)
|
||||
{
|
||||
return new MsgReportVideoFileSourceStreamData(frameRate, recordLength);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_frameRate;
|
||||
int m_videoLength; //!< Video length in frames
|
||||
|
||||
MsgReportVideoFileSourceStreamData(int frameRate,
|
||||
int videoLength) :
|
||||
Message(),
|
||||
m_frameRate(frameRate),
|
||||
m_videoLength(videoLength)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportCameraData : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getdeviceNumber() const { return m_deviceNumber; }
|
||||
float getFPS() const { return m_fps; }
|
||||
float getFPSManual() const { return m_fpsManual; }
|
||||
bool getFPSManualEnable() const { return m_fpsManualEnable; }
|
||||
int getWidth() const { return m_width; }
|
||||
int getHeight() const { return m_height; }
|
||||
int getStatus() const { return m_status; }
|
||||
|
||||
static MsgReportCameraData* create(
|
||||
int deviceNumber,
|
||||
float fps,
|
||||
float fpsManual,
|
||||
bool fpsManualEnable,
|
||||
int width,
|
||||
int height,
|
||||
int status)
|
||||
{
|
||||
return new MsgReportCameraData(
|
||||
deviceNumber,
|
||||
fps,
|
||||
fpsManual,
|
||||
fpsManualEnable,
|
||||
width,
|
||||
height,
|
||||
status);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_deviceNumber;
|
||||
float m_fps;
|
||||
float m_fpsManual;
|
||||
bool m_fpsManualEnable;
|
||||
int m_width;
|
||||
int m_height;
|
||||
int m_status;
|
||||
|
||||
MsgReportCameraData(
|
||||
int deviceNumber,
|
||||
float fps,
|
||||
float fpsManual,
|
||||
bool fpsManualEnable,
|
||||
int width,
|
||||
int height,
|
||||
int status) :
|
||||
Message(),
|
||||
m_deviceNumber(deviceNumber),
|
||||
m_fps(fps),
|
||||
m_fpsManual(fpsManual),
|
||||
m_fpsManualEnable(fpsManualEnable),
|
||||
m_width(width),
|
||||
m_height(height),
|
||||
m_status(status)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportEffectiveSampleRate : public Message
|
||||
{
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
uint32_t gatNbPointsPerLine() const { return m_nbPointsPerLine; }
|
||||
|
||||
static MsgReportEffectiveSampleRate* create(int sampleRate, uint32_t nbPointsPerLine)
|
||||
{
|
||||
return new MsgReportEffectiveSampleRate(sampleRate, nbPointsPerLine);
|
||||
}
|
||||
|
||||
protected:
|
||||
int m_sampleRate;
|
||||
uint32_t m_nbPointsPerLine;
|
||||
|
||||
MsgReportEffectiveSampleRate(
|
||||
int sampleRate,
|
||||
uint32_t nbPointsPerLine) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_nbPointsPerLine(nbPointsPerLine)
|
||||
{ }
|
||||
};
|
||||
|
||||
public:
|
||||
ATVModReport();
|
||||
~ATVModReport();
|
||||
};
|
||||
|
||||
#endif // PLUGINS_CHANNELTX_MODATV_ATVMODREPORT_H_
|
1073
plugins/channeltx/modatv/atvmodsource.cpp
Normal file
1073
plugins/channeltx/modatv/atvmodsource.cpp
Normal file
File diff suppressed because it is too large
Load Diff
502
plugins/channeltx/modatv/atvmodsource.h
Normal file
502
plugins/channeltx/modatv/atvmodsource.h
Normal file
@ -0,0 +1,502 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 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_MODATV_ATVMODSOURCE_H_
|
||||
#define PLUGINS_CHANNELTX_MODATV_ATVMODSOURCE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
#include <opencv2/videoio.hpp>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "atvmodsettings.h"
|
||||
|
||||
class MessageQueue;
|
||||
|
||||
class ATVModSource : public ChannelSampleSource
|
||||
{
|
||||
public:
|
||||
ATVModSource();
|
||||
~ATVModSource();
|
||||
|
||||
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
|
||||
virtual void pullOne(Sample& sample);
|
||||
virtual void prefetch(unsigned int nbSamples);
|
||||
|
||||
int getEffectiveSampleRate() const { return m_tvSampleRate; };
|
||||
double getMagSq() const { return m_movingAverage.asDouble(); }
|
||||
void getCameraNumbers(std::vector<int>& numbers);
|
||||
|
||||
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
|
||||
void getLevels(Real& rmsLevel, Real& peakLevel, Real& numSamples) const
|
||||
{
|
||||
rmsLevel = m_rmsLevel;
|
||||
peakLevel = m_peakLevel;
|
||||
numSamples = m_levelNbSamples;
|
||||
}
|
||||
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
void applySettings(const ATVModSettings& settings, bool force = false);
|
||||
void openImage(const QString& fileName);
|
||||
void openVideo(const QString& fileName);
|
||||
void seekVideoFileStream(int seekPercentage);
|
||||
void reportVideoFileSourceStreamTiming();
|
||||
void configureCameraIndex(int index);
|
||||
void configureCameraData(uint32_t index, float mnaualFPS, bool manualFPSEnable);
|
||||
|
||||
static void getBaseValues(int outputSampleRate, int linesPerSecond, int& sampleRateUnits, uint32_t& nbPointsPerRateUnit);
|
||||
static float getRFBandwidthDivisor(ATVModSettings::ATVModulation modulation);
|
||||
|
||||
private:
|
||||
class ATVCamera
|
||||
{
|
||||
public:
|
||||
cv::VideoCapture m_camera; //!< camera object
|
||||
cv::Mat m_videoframeOriginal; //!< camera non resized image
|
||||
cv::Mat m_videoFrame; //!< displayable camera frame
|
||||
int m_cameraNumber; //!< camera device number
|
||||
float m_videoFPS; //!< camera FPS rate
|
||||
float m_videoFPSManual; //!< camera FPS rate manually set
|
||||
bool m_videoFPSManualEnable; //!< Enable camera FPS rate manual set value
|
||||
int m_videoWidth; //!< camera frame width
|
||||
int m_videoHeight; //!< camera frame height
|
||||
float m_videoFx; //!< camera horizontal scaling factor
|
||||
float m_videoFy; //!< camera vertictal scaling factor
|
||||
float m_videoFPSq; //!< camera FPS sacaling factor
|
||||
float m_videoFPSqManual; //!< camera FPS sacaling factor manually set
|
||||
float m_videoFPSCount; //!< camera FPS fractional counter
|
||||
int m_videoPrevFPSCount; //!< camera FPS previous integer counter
|
||||
|
||||
ATVCamera() :
|
||||
m_cameraNumber(-1),
|
||||
m_videoFPS(25.0f),
|
||||
m_videoFPSManual(20.0f),
|
||||
m_videoFPSManualEnable(false),
|
||||
m_videoWidth(1),
|
||||
m_videoHeight(1),
|
||||
m_videoFx(1.0f),
|
||||
m_videoFy(1.0f),
|
||||
m_videoFPSq(1.0f),
|
||||
m_videoFPSqManual(1.0f),
|
||||
m_videoFPSCount(0.0f),
|
||||
m_videoPrevFPSCount(0)
|
||||
{}
|
||||
|
||||
ATVCamera(const ATVCamera& camera) :
|
||||
m_camera(camera.m_camera),
|
||||
m_videoframeOriginal(camera.m_videoframeOriginal),
|
||||
m_videoFrame(camera.m_videoFrame),
|
||||
m_cameraNumber(camera.m_cameraNumber),
|
||||
m_videoFPS(camera.m_videoFPS),
|
||||
m_videoFPSManual(camera.m_videoFPSManual),
|
||||
m_videoFPSManualEnable(camera.m_videoFPSManualEnable),
|
||||
m_videoWidth(camera.m_videoWidth),
|
||||
m_videoHeight(camera.m_videoHeight),
|
||||
m_videoFx(camera.m_videoFx),
|
||||
m_videoFy(camera.m_videoFy),
|
||||
m_videoFPSq(camera.m_videoFPSq),
|
||||
m_videoFPSqManual(camera.m_videoFPSqManual),
|
||||
m_videoFPSCount(camera.m_videoFPSCount),
|
||||
m_videoPrevFPSCount(camera.m_videoPrevFPSCount)
|
||||
{}
|
||||
};
|
||||
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
ATVModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
Complex m_modSample;
|
||||
float m_modPhasor; //!< For FM modulation
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
int m_tvSampleRate; //!< sample rate for generating signal
|
||||
uint32_t m_pointsPerLine; //!< Number of points per full line
|
||||
int m_pointsPerSync; //!< number of line points for the horizontal sync
|
||||
int m_pointsPerBP; //!< number of line points for the back porch
|
||||
int m_pointsPerImgLine; //!< number of line points for the image line
|
||||
uint32_t m_pointsPerFP; //!< number of line points for the front porch
|
||||
int m_pointsPerFSync; //!< number of line points for the field first sync
|
||||
uint32_t m_pointsPerHBar; //!< number of line points for a bar of the bar chart
|
||||
uint32_t m_linesPerVBar; //!< number of lines for a bar of the bar chart
|
||||
uint32_t m_pointsPerTU; //!< number of line points per time unit
|
||||
int m_nbLines; //!< number of lines per complete frame
|
||||
int m_nbLines2; //!< same number as above (non interlaced) or half the number above (interlaced)
|
||||
uint32_t m_nbImageLines; //!< number of image lines excluding synchronization lines
|
||||
uint32_t m_nbImageLines2; //!< same number as above (non interlaced) or half the number above (interlaced)
|
||||
int m_nbHorizPoints; //!< number of line points per horizontal line
|
||||
int m_nbSyncLinesHeadE; //!< number of header sync lines on even frame
|
||||
int m_nbSyncLinesHeadO; //!< number of header sync lines on odd frame
|
||||
int m_nbSyncLinesBottom;//!< number of sync lines at bottom
|
||||
int m_nbLongSyncLines; //!< number of whole long sync lines for vertical synchronization
|
||||
int m_nbHalfLongSync; //!< number of half long sync / equalization lines
|
||||
int m_nbWholeEqLines; //!< number of whole equalizing lines
|
||||
bool m_singleLongSync; //!< single or double long sync per long sync line
|
||||
int m_nbBlankLines; //!< number of lines in a frame (full or half) that are blanked (black) at the top of the image
|
||||
float m_blankLineLvel; //!< video level of blank lines
|
||||
float m_hBarIncrement; //!< video level increment at each horizontal bar increment
|
||||
float m_vBarIncrement; //!< video level increment at each vertical bar increment
|
||||
bool m_interleaved; //!< true if image is interlaced (2 half frames per frame)
|
||||
bool m_evenImage; //!< in interlaced mode true if this is an even image
|
||||
QMutex m_settingsMutex;
|
||||
int m_horizontalCount; //!< current point index on line
|
||||
int m_lineCount; //!< current line index in frame
|
||||
float m_fps; //!< resulting frames per second
|
||||
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_rmsLevel;
|
||||
Real m_peakLevelOut;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
cv::Mat m_imageFromFile; //!< original image not resized not overlaid by text
|
||||
cv::Mat m_imageOriginal; //!< original not resized image
|
||||
cv::Mat m_image; //!< resized image for transmission at given rate
|
||||
bool m_imageOK;
|
||||
|
||||
cv::VideoCapture m_video; //!< current video capture
|
||||
cv::Mat m_videoframeOriginal; //!< current frame from video
|
||||
cv::Mat m_videoFrame; //!< current displayable video frame
|
||||
float m_videoFPS; //!< current video FPS rate
|
||||
int m_videoWidth; //!< current video frame width
|
||||
int m_videoHeight; //!< current video frame height
|
||||
float m_videoFx; //!< current video horizontal scaling factor
|
||||
float m_videoFy; //!< current video vertictal scaling factor
|
||||
float m_videoFPSq; //!< current video FPS sacaling factor
|
||||
float m_videoFPSCount; //!< current video FPS fractional counter
|
||||
int m_videoPrevFPSCount; //!< current video FPS previous integer counter
|
||||
int m_videoLength; //!< current video length in frames
|
||||
bool m_videoEOF; //!< current video has reached end of file
|
||||
bool m_videoOK;
|
||||
|
||||
std::vector<ATVCamera> m_cameras; //!< vector of available cameras
|
||||
int m_cameraIndex; //!< curent camera index in list of available cameras
|
||||
|
||||
std::string m_overlayText;
|
||||
QString m_imageFileName;
|
||||
QString m_videoFileName;
|
||||
|
||||
// Used for standard SSB
|
||||
fftfilt* m_SSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
|
||||
// Used for vestigial SSB with asymmetrical filtering (needs double sideband scheme)
|
||||
fftfilt* m_DSBFilter;
|
||||
Complex* m_DSBFilterBuffer;
|
||||
int m_DSBFilterBufferIndex;
|
||||
|
||||
MessageQueue *m_messageQueueToGUI;
|
||||
|
||||
static const int m_ssbFftLen;
|
||||
static const float m_blackLevel;
|
||||
static const float m_spanLevel;
|
||||
static const int m_levelNbSamples;
|
||||
static const int m_nbBars; //!< number of bars in bar or chessboard patterns
|
||||
static const int m_cameraFPSTestNbFrames; //!< number of frames for camera FPS test
|
||||
|
||||
void pullFinalize(Complex& ci, Sample& sample);
|
||||
void pullVideo(Real& sample);
|
||||
void calculateLevel(Real& sample);
|
||||
void modulateSample();
|
||||
Complex& modulateSSB(Real& sample);
|
||||
Complex& modulateVestigialSSB(Real& sample);
|
||||
void applyStandard();
|
||||
void resizeImage();
|
||||
void calculateVideoSizes();
|
||||
void resizeVideo();
|
||||
void scanCameras();
|
||||
void releaseCameras();
|
||||
void calculateCamerasSizes();
|
||||
void resizeCameras();
|
||||
void resizeCamera();
|
||||
void mixImageAndText(cv::Mat& image);
|
||||
|
||||
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
|
||||
|
||||
inline void pullImageLine(Real& sample, bool noHSync = false)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync) // sync pulse
|
||||
{
|
||||
sample = noHSync ? m_blackLevel : 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP) // back porch
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP + m_pointsPerImgLine)
|
||||
{
|
||||
int pointIndex = m_horizontalCount - (m_pointsPerSync + m_pointsPerBP);
|
||||
int oddity = m_lineCount < m_nbLines2 + 1 ? 0 : 1;
|
||||
int iLine = oddity == 0 ? m_lineCount : m_lineCount - m_nbLines2 - 1;
|
||||
int iLineImage = iLine - m_nbBlankLines - (oddity == 0 ? m_nbSyncLinesHeadE : m_nbSyncLinesHeadO);
|
||||
|
||||
switch(m_settings.m_atvModInput)
|
||||
{
|
||||
case ATVModSettings::ATVModInputHBars:
|
||||
sample = (((float)pointIndex) / m_pointsPerHBar) * m_hBarIncrement + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVBars:
|
||||
sample = (((float)iLine) / m_linesPerVBar) * m_vBarIncrement + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputChessboard:
|
||||
sample = (((iLine / m_linesPerVBar)*5 + (pointIndex / m_pointsPerHBar)) % 2) * m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputHGradient:
|
||||
sample = (pointIndex / (float) m_pointsPerImgLine) * m_spanLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVGradient:
|
||||
sample = ((iLine -5) / (float) m_nbImageLines2) * m_spanLevel + m_blackLevel;
|
||||
break;
|
||||
case ATVModSettings::ATVModInputImage:
|
||||
if (!m_imageOK || (iLineImage < -oddity) || m_image.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = m_image.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = m_image.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputVideo:
|
||||
if (!m_videoOK || (iLineImage < -oddity) || m_videoFrame.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = m_videoFrame.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = m_videoFrame.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputCamera:
|
||||
if ((iLineImage < -oddity) || (m_cameraIndex < 0))
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
ATVCamera& camera = m_cameras[m_cameraIndex];
|
||||
|
||||
if (camera.m_videoFrame.empty())
|
||||
{
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char pixv;
|
||||
|
||||
if (m_interleaved) {
|
||||
pixv = camera.m_videoFrame.at<unsigned char>(2*iLineImage + oddity, pointIndex); // row (y), col (x)
|
||||
} else {
|
||||
pixv = camera.m_videoFrame.at<unsigned char>(iLineImage, pointIndex); // row (y), col (x)
|
||||
}
|
||||
|
||||
sample = (pixv / 256.0f) * m_spanLevel + m_blackLevel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ATVModSettings::ATVModInputUniform:
|
||||
default:
|
||||
sample = m_spanLevel * m_settings.m_uniformLevel + m_blackLevel;
|
||||
}
|
||||
}
|
||||
else // front porch
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineLongPulses(Real& sample)
|
||||
{
|
||||
int halfIndex = m_horizontalCount % (m_nbHorizPoints/2);
|
||||
|
||||
if (halfIndex < (m_nbHorizPoints/2) - m_pointsPerSync) // ultra-black
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else // black
|
||||
{
|
||||
if (m_singleLongSync && (m_horizontalCount < m_nbHorizPoints/2)) {
|
||||
sample = 0.0f;
|
||||
} else {
|
||||
sample = m_blackLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineEqualizingPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineEqualizingThenLongPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < m_nbHorizPoints - m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLineLongThenEqualizingPulses(Real& sample)
|
||||
{
|
||||
if (m_horizontalCount < (m_nbHorizPoints/2) - m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2))
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync)
|
||||
{
|
||||
sample = 0.0f; // ultra-black
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blackLevel; // black
|
||||
}
|
||||
}
|
||||
|
||||
inline void pullVSyncLine(Real& sample)
|
||||
{
|
||||
if (m_lineCount < m_nbLines2 + 1) // even
|
||||
{
|
||||
int fieldLine = m_lineCount;
|
||||
|
||||
if (fieldLine < m_nbLongSyncLines) // 0,1: Whole line "long" pulses
|
||||
{
|
||||
pullVSyncLineLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbHalfLongSync) // long pulse then equalizing pulse
|
||||
{
|
||||
pullVSyncLineLongThenEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbHalfLongSync + m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - m_nbHalfLongSync) // equalizing pulse then long pulse
|
||||
{
|
||||
pullVSyncLineEqualizingThenLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - m_nbHalfLongSync - m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else // black images
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blankLineLvel;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // odd
|
||||
{
|
||||
int fieldLine = m_lineCount - m_nbLines2 - 1;
|
||||
|
||||
if (fieldLine < m_nbLongSyncLines) // 0,1: Whole line "long" pulses
|
||||
{
|
||||
pullVSyncLineLongPulses(sample);
|
||||
}
|
||||
else if (fieldLine < m_nbLongSyncLines + m_nbWholeEqLines) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else if (fieldLine > m_nbLines2 - 1 - m_nbWholeEqLines - m_nbHalfLongSync) // Whole line equalizing pulses
|
||||
{
|
||||
pullVSyncLineEqualizingPulses(sample);
|
||||
}
|
||||
else // black images
|
||||
{
|
||||
if (m_horizontalCount < m_pointsPerSync)
|
||||
{
|
||||
sample = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = m_blankLineLvel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif /* PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ */
|
@ -2,6 +2,8 @@ project(modfreedv)
|
||||
|
||||
set(modfreedv_SOURCES
|
||||
freedvmod.cpp
|
||||
freedvmodbaseband.cpp
|
||||
freedvmodsource.cpp
|
||||
freedvmodplugin.cpp
|
||||
freedvmodsettings.cpp
|
||||
freedvmodwebapiadapter.cpp
|
||||
@ -9,6 +11,8 @@ set(modfreedv_SOURCES
|
||||
|
||||
set(modfreedv_HEADERS
|
||||
freedvmod.h
|
||||
freedvmodbaseband.h
|
||||
freedvmodsource.h
|
||||
freedvmodplugin.h
|
||||
freedvmodsettings.h
|
||||
freedvmodwebapiadapter.h
|
||||
|
@ -15,32 +15,30 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "freedvmod.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "codec2/freedv_api.h"
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGFreeDVModReport.h"
|
||||
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "freedvmodbaseband.h"
|
||||
#include "freedvmod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFreeDVMod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgConfigureFileSourceName, Message)
|
||||
@ -51,69 +49,26 @@ MESSAGE_CLASS_DEFINITION(FreeDVMod::MsgReportFileSourceStreamTiming, Message)
|
||||
|
||||
const QString FreeDVMod::m_channelIdURI = "sdrangel.channeltx.freedvmod";
|
||||
const QString FreeDVMod::m_channelId = "FreeDVMod";
|
||||
const int FreeDVMod::m_levelNbSamples = 80; // every 10ms
|
||||
const int FreeDVMod::m_ssbFftLen = 1024;
|
||||
|
||||
FreeDVMod::FreeDVMod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(48000),
|
||||
m_outputSampleRate(48000),
|
||||
m_modemSampleRate(48000), // // default 2400A mode
|
||||
m_inputFrequencyOffset(0),
|
||||
m_lowCutoff(0.0),
|
||||
m_hiCutoff(6000.0),
|
||||
m_SSBFilter(0),
|
||||
m_SSBFilterBuffer(0),
|
||||
m_SSBFilterBufferIndex(0),
|
||||
m_sampleSink(0),
|
||||
m_audioFifo(4800),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_fileSize(0),
|
||||
m_recordLength(0),
|
||||
m_inputSampleRate(8000), // all modes take 8000 S/s input
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_freeDV(0),
|
||||
m_nSpeechSamples(0),
|
||||
m_nNomModemSamples(0),
|
||||
m_iSpeech(0),
|
||||
m_iModem(0),
|
||||
m_speechIn(0),
|
||||
m_modOut(0),
|
||||
m_scaleFactor(SDR_TX_SCALEF)
|
||||
m_fileSampleRate(8000) // all modes take 8000 S/s input
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
|
||||
|
||||
m_SSBFilter = new fftfilt(m_lowCutoff / m_audioSampleRate, m_hiCutoff / m_audioSampleRate, m_ssbFftLen);
|
||||
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_sum.real(0.0f);
|
||||
m_sum.imag(0.0f);
|
||||
m_undersampleCount = 0;
|
||||
m_sumCount = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
m_toneNco.setFreq(1000.0, m_inputSampleRate);
|
||||
m_cwKeyer.setSampleRate(m_inputSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new FreeDVModBaseband();
|
||||
m_basebandSource->setInputFileStream(&m_ifstream);
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
@ -123,330 +78,43 @@ FreeDVMod::~FreeDVMod()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
|
||||
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
|
||||
delete m_SSBFilter;
|
||||
delete[] m_SSBFilterBuffer;
|
||||
|
||||
if (m_freeDV) {
|
||||
freedv_close(m_freeDV);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVMod::pull(Sample& sample)
|
||||
{
|
||||
Complex ci;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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
|
||||
ci *= 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
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 FreeDVMod::pullAudio(int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_modemSampleRate);
|
||||
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void FreeDVMod::modulateSample()
|
||||
{
|
||||
pullAF(m_modSample);
|
||||
if (!m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_modSample);
|
||||
}
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
void FreeDVMod::pullAF(Complex& sample)
|
||||
{
|
||||
if (m_settings.m_audioMute)
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
fftfilt::cmplx *filtered;
|
||||
int n_out = 0;
|
||||
|
||||
int decim = 1<<(m_settings.m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
if (m_iModem >= m_nNomModemSamples)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case FreeDVModSettings::FreeDVModInputTone:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * m_settings.m_volumeFactor;
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputFile:
|
||||
if (m_iModem >= m_nNomModemSamples)
|
||||
{
|
||||
if (m_ifstream.is_open())
|
||||
{
|
||||
std::fill(m_speechIn, m_speechIn + m_nSpeechSamples, 0);
|
||||
|
||||
if (m_ifstream.eof())
|
||||
{
|
||||
if (m_settings.m_playLoop)
|
||||
{
|
||||
m_ifstream.clear();
|
||||
m_ifstream.seekg(0, std::ios::beg);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ifstream.eof())
|
||||
{
|
||||
std::fill(m_modOut, m_modOut + m_nNomModemSamples, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
m_ifstream.read(reinterpret_cast<char*>(m_speechIn), sizeof(int16_t) * m_nSpeechSamples);
|
||||
|
||||
if ((m_settings.m_volumeFactor != 1.0) || m_settings.m_gaugeInputElseModem)
|
||||
{
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
if (m_settings.m_volumeFactor != 1.0) {
|
||||
m_speechIn[i] *= m_settings.m_volumeFactor;
|
||||
}
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(m_modOut, m_modOut + m_nNomModemSamples, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputAudio:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
qint16 audioSample = (m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) * (m_settings.m_volumeFactor / 2.0f);
|
||||
m_audioBufferFill++;
|
||||
|
||||
while (!m_audioResampler.downSample(audioSample, m_speechIn[i]))
|
||||
{
|
||||
audioSample = (m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) * (m_settings.m_volumeFactor / 2.0f);
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputCWTone:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * fadeFactor * m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * fadeFactor * m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_speechIn[i] = 0;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputNone:
|
||||
default:
|
||||
std::fill(m_speechIn, m_speechIn + m_nSpeechSamples, 0);
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
}
|
||||
|
||||
m_iModem = 0;
|
||||
}
|
||||
|
||||
ci.real(m_modOut[m_iModem++] / m_scaleFactor);
|
||||
ci.imag(0.0f);
|
||||
|
||||
n_out = m_SSBFilter->runSSB(ci, &filtered, true); // USB
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += filtered[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_sampleSink != 0)
|
||||
{
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); // SSB
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
sample = m_SSBFilterBuffer[m_SSBFilterBufferIndex++];
|
||||
}
|
||||
|
||||
void FreeDVMod::calculateLevel(Complex& sample)
|
||||
{
|
||||
Real t = sample.real(); // TODO: possibly adjust depending on sample type
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVMod::calculateLevel(qint16& sample)
|
||||
{
|
||||
Real t = sample / SDR_TX_SCALEF;
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("FreeDVMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void FreeDVMod::start()
|
||||
{
|
||||
qDebug() << "FreeDVMod::start: m_outputSampleRate: " << m_outputSampleRate
|
||||
<< " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset;
|
||||
|
||||
m_audioFifo.clear();
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug("FreeDVMod::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void FreeDVMod::stop()
|
||||
{
|
||||
qDebug("FreeDVMod::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
void FreeDVMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool FreeDVMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "FreeDVMod::handleMessage: MsgChannelizerNotification";
|
||||
|
||||
applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "FreeDVMod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
qDebug() << "FreeDVMod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " getSourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
FreeDVModBaseband::MsgConfigureChannelizer *msg
|
||||
= FreeDVModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -503,22 +171,14 @@ bool FreeDVMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
|
||||
qDebug() << "FreeDVMod::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate;
|
||||
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
// Forward to the source
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "FreeDVMod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSource->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -537,7 +197,7 @@ void FreeDVMod::openFileStream()
|
||||
m_fileSize = m_ifstream.tellg();
|
||||
m_ifstream.seekg(0,std::ios_base::beg);
|
||||
|
||||
m_recordLength = m_fileSize / (sizeof(int16_t) * m_inputSampleRate);
|
||||
m_recordLength = m_fileSize / (sizeof(int16_t) * m_fileSampleRate);
|
||||
|
||||
qDebug() << "FreeDVMod::openFileStream: " << m_fileName.toStdString().c_str()
|
||||
<< " fileSize: " << m_fileSize << "bytes"
|
||||
@ -546,7 +206,7 @@ void FreeDVMod::openFileStream()
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportFileSourceStreamData *report;
|
||||
report = MsgReportFileSourceStreamData::create(m_inputSampleRate, m_recordLength);
|
||||
report = MsgReportFileSourceStreamData::create(m_fileSampleRate, m_recordLength);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
@ -557,184 +217,13 @@ void FreeDVMod::seekFileStream(int seekPercentage)
|
||||
|
||||
if (m_ifstream.is_open())
|
||||
{
|
||||
int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_inputSampleRate;
|
||||
int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_fileSampleRate;
|
||||
seekPoint *= sizeof(Real);
|
||||
m_ifstream.clear();
|
||||
m_ifstream.seekg(seekPoint, std::ios::beg);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVMod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("FreeDVMod::applyAudioSampleRate: %d", sampleRate);
|
||||
// TODO: put up simple IIR interpolator when sampleRate < m_modemSampleRate
|
||||
|
||||
m_settingsMutex.lock();
|
||||
m_audioResampler.setDecimation(sampleRate / m_inputSampleRate);
|
||||
m_audioResampler.setAudioFilters(sampleRate, sampleRate, 250, 3300);
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void FreeDVMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "FreeDVMod::applyChannelSettings:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate
|
||||
<< " outputSampleRate: " << outputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_modemSampleRate / (Real) outputSampleRate;
|
||||
m_interpolator.create(48, m_modemSampleRate, m_hiCutoff, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
m_outputSampleRate = outputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void FreeDVMod::applyFreeDVMode(FreeDVModSettings::FreeDVMode mode)
|
||||
{
|
||||
m_hiCutoff = FreeDVModSettings::getHiCutoff(mode);
|
||||
m_lowCutoff = FreeDVModSettings::getLowCutoff(mode);
|
||||
int modemSampleRate = FreeDVModSettings::getModSampleRate(mode);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
m_SSBFilter->create_filter(m_lowCutoff / modemSampleRate, m_hiCutoff / modemSampleRate);
|
||||
|
||||
// baseband interpolator and filter
|
||||
if (modemSampleRate != m_modemSampleRate)
|
||||
{
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
modemSampleRate, m_settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) modemSampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, modemSampleRate, m_hiCutoff, 3.0);
|
||||
m_modemSampleRate = modemSampleRate;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
DSPConfigureAudio *cfg = new DSPConfigureAudio(m_modemSampleRate, DSPConfigureAudio::AudioInput);
|
||||
getMessageQueueToGUI()->push(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
// FreeDV object
|
||||
|
||||
if (m_freeDV) {
|
||||
freedv_close(m_freeDV);
|
||||
}
|
||||
|
||||
int fdv_mode = -1;
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
case FreeDVModSettings::FreeDVMode700C:
|
||||
fdv_mode = FREEDV_MODE_700C;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode700D:
|
||||
fdv_mode = FREEDV_MODE_700D;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode800XA:
|
||||
fdv_mode = FREEDV_MODE_800XA;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 8.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode1600:
|
||||
fdv_mode = FREEDV_MODE_1600;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode2400A:
|
||||
default:
|
||||
fdv_mode = FREEDV_MODE_2400A;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 8.2f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fdv_mode == FREEDV_MODE_700D)
|
||||
{
|
||||
struct freedv_advanced adv;
|
||||
adv.interleave_frames = 1;
|
||||
m_freeDV = freedv_open_advanced(fdv_mode, &adv);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_freeDV = freedv_open(fdv_mode);
|
||||
}
|
||||
|
||||
if (m_freeDV)
|
||||
{
|
||||
freedv_set_test_frames(m_freeDV, 0);
|
||||
freedv_set_snr_squelch_thresh(m_freeDV, -100.0);
|
||||
freedv_set_squelch_en(m_freeDV, 1);
|
||||
freedv_set_clip(m_freeDV, 0);
|
||||
freedv_set_tx_bpf(m_freeDV, 1);
|
||||
freedv_set_ext_vco(m_freeDV, 0);
|
||||
|
||||
freedv_set_callback_txt(m_freeDV, nullptr, nullptr, nullptr);
|
||||
freedv_set_callback_protocol(m_freeDV, nullptr, nullptr, nullptr);
|
||||
freedv_set_callback_data(m_freeDV, nullptr, nullptr, nullptr);
|
||||
|
||||
int nSpeechSamples = freedv_get_n_speech_samples(m_freeDV);
|
||||
int nNomModemSamples = freedv_get_n_nom_modem_samples(m_freeDV);
|
||||
int Fs = freedv_get_modem_sample_rate(m_freeDV);
|
||||
int Rs = freedv_get_modem_symbol_rate(m_freeDV);
|
||||
|
||||
if (nSpeechSamples != m_nSpeechSamples)
|
||||
{
|
||||
if (m_speechIn) {
|
||||
delete[] m_speechIn;
|
||||
}
|
||||
|
||||
m_speechIn = new int16_t[nSpeechSamples];
|
||||
m_nSpeechSamples = nSpeechSamples;
|
||||
}
|
||||
|
||||
if (nNomModemSamples != m_nNomModemSamples)
|
||||
{
|
||||
if (m_modOut) {
|
||||
delete[] m_modOut;
|
||||
}
|
||||
|
||||
m_modOut = new int16_t[nNomModemSamples];
|
||||
m_nNomModemSamples = nNomModemSamples;
|
||||
}
|
||||
|
||||
m_iSpeech = 0;
|
||||
m_iModem = 0;
|
||||
|
||||
qDebug() << "FreeDVMod::applyFreeDVMode:"
|
||||
<< " fdv_mode: " << fdv_mode
|
||||
<< " m_modemSampleRate: " << m_modemSampleRate
|
||||
<< " m_lowCutoff: " << m_lowCutoff
|
||||
<< " m_hiCutoff: " << m_hiCutoff
|
||||
<< " Fs: " << Fs
|
||||
<< " Rs: " << Rs
|
||||
<< " m_nSpeechSamples: " << m_nSpeechSamples
|
||||
<< " m_nNomModemSamples: " << m_nNomModemSamples;
|
||||
}
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
void FreeDVMod::applySettings(const FreeDVModSettings& settings, bool force)
|
||||
{
|
||||
QList<QString> reverseAPIKeys;
|
||||
@ -775,29 +264,30 @@ void FreeDVMod::applySettings(const FreeDVModSettings& settings, bool force)
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
|
||||
reverseAPIKeys.append("audioDeviceName");
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_inputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
reverseAPIKeys.append("toneFrequency");
|
||||
}
|
||||
if ((m_settings.m_freeDVMode != settings.m_freeDVMode) || force) {
|
||||
reverseAPIKeys.append("freeDVMode");
|
||||
}
|
||||
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
||||
{
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSource(m_basebandSource->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
if (m_basebandSource->getAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("audioSampleRate");
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_settings.m_freeDVMode != settings.m_freeDVMode) || force) {
|
||||
applyFreeDVMode(settings.m_freeDVMode);
|
||||
}
|
||||
FreeDVModBaseband::MsgConfigureFreeDVModBaseband *msg = FreeDVModBaseband::MsgConfigureFreeDVModBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
@ -844,7 +334,7 @@ int FreeDVMod::webapiSettingsGet(
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getFreeDvModSettings()->getCwKeyer();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
return 200;
|
||||
@ -863,11 +353,11 @@ int FreeDVMod::webapiSettingsPutPatch(
|
||||
if (channelSettingsKeys.contains("cwKeyer"))
|
||||
{
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getFreeDvModSettings()->getCwKeyer();
|
||||
CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
CWKeyerSettings cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiSettingsPutPatch(channelSettingsKeys, cwKeyerSettings, apiCwKeyerSettings);
|
||||
|
||||
CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force);
|
||||
m_cwKeyer.getInputMessageQueue()->push(msgCwKeyer);
|
||||
m_basebandSource->getCWKeyer().getInputMessageQueue()->push(msgCwKeyer);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -879,7 +369,7 @@ int FreeDVMod::webapiSettingsPutPatch(
|
||||
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
|
||||
{
|
||||
FreeDVMod::MsgConfigureChannelizer *msgChan = FreeDVMod::MsgConfigureChannelizer::create(
|
||||
m_audioSampleRate, settings.m_inputFrequencyOffset);
|
||||
m_basebandSource->getAudioSampleRate(), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(msgChan);
|
||||
}
|
||||
|
||||
@ -1012,8 +502,8 @@ void FreeDVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res
|
||||
void FreeDVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
||||
{
|
||||
response.getFreeDvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
response.getFreeDvModReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getFreeDvModReport()->setChannelSampleRate(m_outputSampleRate);
|
||||
response.getFreeDvModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
|
||||
response.getFreeDvModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void FreeDVMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const FreeDVModSettings& settings, bool force)
|
||||
@ -1067,10 +557,10 @@ void FreeDVMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, c
|
||||
|
||||
if (force)
|
||||
{
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
swgFreeDVModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgFreeDVModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
@ -1081,13 +571,14 @@ void FreeDVMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, c
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -1102,7 +593,7 @@ void FreeDVMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettin
|
||||
|
||||
swgFreeDVModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgFreeDVModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
@ -1112,13 +603,14 @@ void FreeDVMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettin
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -1133,10 +625,53 @@ void FreeDVMod::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("FreeDVMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("FreeDVMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void FreeDVMod::setSpectrumSampleSink(BasebandSampleSink* sampleSink)
|
||||
{
|
||||
m_basebandSource->setSpectrumSampleSink(sampleSink);
|
||||
}
|
||||
|
||||
uint32_t FreeDVMod::getAudioSampleRate() const
|
||||
{
|
||||
return m_basebandSource->getAudioSampleRate();
|
||||
}
|
||||
|
||||
uint32_t FreeDVMod::getModemSampleRate() const
|
||||
{
|
||||
return m_basebandSource->getModemSampleRate();
|
||||
}
|
||||
|
||||
Real FreeDVMod::getLowCutoff() const
|
||||
{
|
||||
return m_basebandSource->getLowCutoff();
|
||||
}
|
||||
|
||||
Real FreeDVMod::getHiCutoff() const
|
||||
{
|
||||
return m_basebandSource->getHiCutoff();
|
||||
}
|
||||
|
||||
double FreeDVMod::getMagSq() const
|
||||
{
|
||||
return m_basebandSource->getMagSq();
|
||||
}
|
||||
|
||||
CWKeyer *FreeDVMod::getCWKeyer()
|
||||
{
|
||||
return &m_basebandSource->getCWKeyer();
|
||||
}
|
||||
|
||||
void FreeDVMod::setLevelMeter(QObject *levelMeter)
|
||||
{
|
||||
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
}
|
||||
|
@ -28,23 +28,16 @@
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "audio/audioresampler.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "freedvmodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class CWKeyer;
|
||||
class FreeDVModBaseband;
|
||||
|
||||
struct freedv;
|
||||
|
||||
@ -75,26 +68,33 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -209,12 +209,9 @@ public:
|
||||
~FreeDVMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -257,27 +254,18 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
uint32_t getModemSampleRate() const { return m_modemSampleRate; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
Real getLowCutoff() const { return m_lowCutoff; }
|
||||
Real getHiCutoff() const { return m_hiCutoff; }
|
||||
|
||||
CWKeyer *getCWKeyer() { return &m_cwKeyer; }
|
||||
uint32_t getAudioSampleRate() const;
|
||||
uint32_t getModemSampleRate() const;
|
||||
Real getLowCutoff() const;
|
||||
Real getHiCutoff() const;
|
||||
double getMagSq() const;
|
||||
CWKeyer *getCWKeyer();
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
void setSpectrumSampleSink(BasebandSampleSink* sampleSink);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
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:
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
@ -285,80 +273,23 @@ private:
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_basebandSampleRate;
|
||||
int m_outputSampleRate;
|
||||
int m_modemSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
Real m_lowCutoff;
|
||||
Real m_hiCutoff;
|
||||
QThread *m_thread;
|
||||
FreeDVModBaseband* m_basebandSource;
|
||||
FreeDVModSettings m_settings;
|
||||
|
||||
NCOF m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
Complex m_modSample;
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
bool m_interpolatorConsumed;
|
||||
fftfilt* m_SSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
static const int m_ssbFftLen;
|
||||
|
||||
BasebandSampleSink* m_sampleSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_sumCount;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
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_inputSampleRate; //!< speech (input) sample rate (fixed 8000 S/s)
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
CWKeyer m_cwKeyer;
|
||||
int m_fileSampleRate; //!< speech (input) sample rate (fixed 8000 S/s)
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
struct freedv *m_freeDV;
|
||||
int m_nSpeechSamples;
|
||||
int m_nNomModemSamples;
|
||||
int m_iSpeech;
|
||||
int m_iModem;
|
||||
int16_t *m_speechIn;
|
||||
int16_t *m_modOut;
|
||||
float m_scaleFactor; //!< divide by this amount to scale from int16 to float in [-1.0, 1.0] interval
|
||||
AudioResampler m_audioResampler;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const FreeDVModSettings& settings, bool force = false);
|
||||
void applyFreeDVMode(FreeDVModSettings::FreeDVMode mode);
|
||||
void pullAF(Complex& sample);
|
||||
void calculateLevel(Complex& sample);
|
||||
void calculateLevel(qint16& sample);
|
||||
void modulateSample();
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekPercentage);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
218
plugins/channeltx/modfreedv/freedvmodbaseband.cpp
Normal file
218
plugins/channeltx/modfreedv/freedvmodbaseband.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "freedvmodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(FreeDVModBaseband::MsgConfigureFreeDVModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(FreeDVModBaseband::MsgConfigureChannelizer, Message)
|
||||
|
||||
FreeDVModBaseband::FreeDVModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("FreeDVModBaseband::FreeDVModBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&FreeDVModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(m_source.getAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate());
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
FreeDVModBaseband::~FreeDVModBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void FreeDVModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void FreeDVModBaseband::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 FreeDVModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
Real rmsLevel, peakLevel, 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 FreeDVModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void FreeDVModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FreeDVModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "FreeDVModBaseband::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_source.getAudioSampleRate()) {
|
||||
m_source.applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFreeDVModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureFreeDVModBaseband& cfg = (MsgConfigureFreeDVModBaseband&) cmd;
|
||||
qDebug() << "FreeDVModBaseband::handleMessage: MsgConfigureFreeDVModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "FreeDVModBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< "(requested) sourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "FreeDVModBaseband::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());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
CWKeyer::MsgConfigureCWKeyer *notif = new CWKeyer::MsgConfigureCWKeyer(cfg);
|
||||
CWKeyer& cwKeyer = m_source.getCWKeyer();
|
||||
cwKeyer.getInputMessageQueue()->push(notif);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVModBaseband::applySettings(const FreeDVModSettings& settings, bool force)
|
||||
{
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int FreeDVModBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
124
plugins/channeltx/modfreedv/freedvmodbaseband.h
Normal file
124
plugins/channeltx/modfreedv/freedvmodbaseband.h
Normal file
@ -0,0 +1,124 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FREEDVMODBASEBAND_H
|
||||
#define INCLUDE_FREEDVMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "freedvmodsource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class FreeDVModBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureFreeDVModBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const FreeDVModSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureFreeDVModBaseband* create(const FreeDVModSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureFreeDVModBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
FreeDVModSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureFreeDVModBaseband(const FreeDVModSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
FreeDVModBaseband();
|
||||
~FreeDVModBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
CWKeyer& getCWKeyer() { return m_source.getCWKeyer(); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
int getChannelSampleRate() const;
|
||||
void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
|
||||
AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
|
||||
void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_source.setSpectrumSink(sampleSink); }
|
||||
unsigned int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
|
||||
unsigned int getModemSampleRate() const { return m_source.getModemSampleRate(); }
|
||||
Real getLowCutoff() const { return m_source.getLowCutoff(); }
|
||||
Real getHiCutoff() const { return m_source.getHiCutoff(); }
|
||||
|
||||
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;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
FreeDVModSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
FreeDVModSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const FreeDVModSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FREEDVMODBASEBAND_H
|
@ -30,6 +30,7 @@
|
||||
#include "util/db.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/audioselectdialog.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
@ -393,7 +394,7 @@ FreeDVModGUI::FreeDVModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
|
||||
m_settings.setCWKeyerGUI(ui->cwKeyerGUI);
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
connect(m_freeDVMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
m_freeDVMod->setLevelMeter(ui->volumeMeter);
|
||||
|
||||
displaySettings();
|
||||
applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true)
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor FreeDVModPlugin::m_pluginDescriptor = {
|
||||
QString("FreeDV Modulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
525
plugins/channeltx/modfreedv/freedvmodsource.cpp
Normal file
525
plugins/channeltx/modfreedv/freedvmodsource.cpp
Normal file
@ -0,0 +1,525 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "codec2/freedv_api.h"
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "freedvmodsource.h"
|
||||
|
||||
const int FreeDVModSource::m_levelNbSamples = 80; // every 10ms
|
||||
const int FreeDVModSource::m_ssbFftLen = 1024;
|
||||
|
||||
FreeDVModSource::FreeDVModSource() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_modemSampleRate(48000), // // default 2400A mode
|
||||
m_lowCutoff(0.0),
|
||||
m_hiCutoff(6000.0),
|
||||
m_SSBFilter(nullptr),
|
||||
m_SSBFilterBuffer(0),
|
||||
m_SSBFilterBufferIndex(0),
|
||||
m_audioFifo(4800),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_freeDV(nullptr),
|
||||
m_nSpeechSamples(0),
|
||||
m_nNomModemSamples(0),
|
||||
m_iSpeech(0),
|
||||
m_iModem(0),
|
||||
m_speechIn(nullptr),
|
||||
m_modOut(0),
|
||||
m_scaleFactor(SDR_TX_SCALEF)
|
||||
{
|
||||
m_SSBFilter = new fftfilt(m_lowCutoff / m_audioSampleRate, m_hiCutoff / m_audioSampleRate, m_ssbFftLen);
|
||||
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_sum.real(0.0f);
|
||||
m_sum.imag(0.0f);
|
||||
m_undersampleCount = 0;
|
||||
m_sumCount = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
FreeDVModSource::~FreeDVModSource()
|
||||
{
|
||||
|
||||
delete m_SSBFilter;
|
||||
delete[] m_SSBFilterBuffer;
|
||||
|
||||
if (m_freeDV) {
|
||||
freedv_close(m_freeDV);
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void FreeDVModSource::pullOne(Sample& sample)
|
||||
{
|
||||
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
|
||||
ci *= 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
|
||||
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 FreeDVModSource::prefetch(unsigned int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
|
||||
pullAudio(nbSamplesAudio);
|
||||
}
|
||||
|
||||
void FreeDVModSource::pullAudio(unsigned int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_modemSampleRate);
|
||||
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void FreeDVModSource::modulateSample()
|
||||
{
|
||||
pullAF(m_modSample);
|
||||
if (!m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_modSample);
|
||||
}
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
void FreeDVModSource::pullAF(Complex& sample)
|
||||
{
|
||||
if (m_settings.m_audioMute)
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
fftfilt::cmplx *filtered;
|
||||
int n_out = 0;
|
||||
|
||||
int decim = 1<<(m_settings.m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
if (m_iModem >= m_nNomModemSamples)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case FreeDVModSettings::FreeDVModInputTone:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * m_settings.m_volumeFactor;
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputFile:
|
||||
if (m_iModem >= m_nNomModemSamples)
|
||||
{
|
||||
if (m_ifstream && m_ifstream->is_open())
|
||||
{
|
||||
std::fill(m_speechIn, m_speechIn + m_nSpeechSamples, 0);
|
||||
|
||||
if (m_ifstream->eof())
|
||||
{
|
||||
if (m_settings.m_playLoop)
|
||||
{
|
||||
m_ifstream->clear();
|
||||
m_ifstream->seekg(0, std::ios::beg);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ifstream->eof())
|
||||
{
|
||||
std::fill(m_modOut, m_modOut + m_nNomModemSamples, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
m_ifstream->read(reinterpret_cast<char*>(m_speechIn), sizeof(int16_t) * m_nSpeechSamples);
|
||||
|
||||
if ((m_settings.m_volumeFactor != 1.0) || m_settings.m_gaugeInputElseModem)
|
||||
{
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
if (m_settings.m_volumeFactor != 1.0) {
|
||||
m_speechIn[i] *= m_settings.m_volumeFactor;
|
||||
}
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(m_modOut, m_modOut + m_nNomModemSamples, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputAudio:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
qint16 audioSample = (m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) * (m_settings.m_volumeFactor / 2.0f);
|
||||
m_audioBufferFill++;
|
||||
|
||||
while (!m_audioResampler.downSample(audioSample, m_speechIn[i]))
|
||||
{
|
||||
audioSample = (m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) * (m_settings.m_volumeFactor / 2.0f);
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputCWTone:
|
||||
for (int i = 0; i < m_nSpeechSamples; i++)
|
||||
{
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * fadeFactor * m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
m_speechIn[i] = m_toneNco.next() * 32768.0f * fadeFactor * m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_speechIn[i] = 0;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_settings.m_gaugeInputElseModem) {
|
||||
calculateLevel(m_speechIn[i]);
|
||||
}
|
||||
}
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVModInputNone:
|
||||
default:
|
||||
std::fill(m_speechIn, m_speechIn + m_nSpeechSamples, 0);
|
||||
freedv_tx(m_freeDV, m_modOut, m_speechIn);
|
||||
break;
|
||||
}
|
||||
|
||||
m_iModem = 0;
|
||||
}
|
||||
|
||||
ci.real(m_modOut[m_iModem++] / m_scaleFactor);
|
||||
ci.imag(0.0f);
|
||||
|
||||
n_out = m_SSBFilter->runSSB(ci, &filtered, true); // USB
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += filtered[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true); // SSB
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
|
||||
sample = m_SSBFilterBuffer[m_SSBFilterBufferIndex++];
|
||||
}
|
||||
|
||||
void FreeDVModSource::calculateLevel(Complex& sample)
|
||||
{
|
||||
Real t = sample.real(); // TODO: possibly adjust depending on sample type
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
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 FreeDVModSource::calculateLevel(qint16& sample)
|
||||
{
|
||||
Real t = sample / SDR_TX_SCALEF;
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
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 FreeDVModSource::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("FreeDVModSource::applyAudioSampleRate: %d", sampleRate);
|
||||
// TODO: put up simple IIR interpolator when sampleRate < m_modemSampleRate
|
||||
|
||||
m_audioResampler.setDecimation(sampleRate / m_channelSampleRate);
|
||||
m_audioResampler.setAudioFilters(sampleRate, sampleRate, 250, 3300);
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void FreeDVModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "FreeDVMod::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_modemSampleRate / (Real) channelSampleRate;
|
||||
m_interpolator.create(48, m_modemSampleRate, m_hiCutoff, 3.0);
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void FreeDVModSource::applyFreeDVMode(FreeDVModSettings::FreeDVMode mode)
|
||||
{
|
||||
m_hiCutoff = FreeDVModSettings::getHiCutoff(mode);
|
||||
m_lowCutoff = FreeDVModSettings::getLowCutoff(mode);
|
||||
int modemSampleRate = FreeDVModSettings::getModSampleRate(mode);
|
||||
|
||||
m_SSBFilter->create_filter(m_lowCutoff / modemSampleRate, m_hiCutoff / modemSampleRate);
|
||||
|
||||
// baseband interpolator and filter
|
||||
if (modemSampleRate != m_modemSampleRate)
|
||||
{
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) modemSampleRate / (Real) m_channelSampleRate;
|
||||
m_interpolator.create(48, modemSampleRate, m_hiCutoff, 3.0);
|
||||
m_modemSampleRate = modemSampleRate;
|
||||
}
|
||||
|
||||
// FreeDV object
|
||||
|
||||
if (m_freeDV) {
|
||||
freedv_close(m_freeDV);
|
||||
}
|
||||
|
||||
int fdv_mode = -1;
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
case FreeDVModSettings::FreeDVMode700C:
|
||||
fdv_mode = FREEDV_MODE_700C;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode700D:
|
||||
fdv_mode = FREEDV_MODE_700D;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode800XA:
|
||||
fdv_mode = FREEDV_MODE_800XA;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 8.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode1600:
|
||||
fdv_mode = FREEDV_MODE_1600;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 3.2f;
|
||||
break;
|
||||
case FreeDVModSettings::FreeDVMode2400A:
|
||||
default:
|
||||
fdv_mode = FREEDV_MODE_2400A;
|
||||
m_scaleFactor = SDR_TX_SCALEF / 8.2f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (fdv_mode == FREEDV_MODE_700D)
|
||||
{
|
||||
struct freedv_advanced adv;
|
||||
adv.interleave_frames = 1;
|
||||
m_freeDV = freedv_open_advanced(fdv_mode, &adv);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_freeDV = freedv_open(fdv_mode);
|
||||
}
|
||||
|
||||
if (m_freeDV)
|
||||
{
|
||||
freedv_set_test_frames(m_freeDV, 0);
|
||||
freedv_set_snr_squelch_thresh(m_freeDV, -100.0);
|
||||
freedv_set_squelch_en(m_freeDV, 1);
|
||||
freedv_set_clip(m_freeDV, 0);
|
||||
freedv_set_tx_bpf(m_freeDV, 1);
|
||||
freedv_set_ext_vco(m_freeDV, 0);
|
||||
|
||||
freedv_set_callback_txt(m_freeDV, nullptr, nullptr, nullptr);
|
||||
freedv_set_callback_protocol(m_freeDV, nullptr, nullptr, nullptr);
|
||||
freedv_set_callback_data(m_freeDV, nullptr, nullptr, nullptr);
|
||||
|
||||
int nSpeechSamples = freedv_get_n_speech_samples(m_freeDV);
|
||||
int nNomModemSamples = freedv_get_n_nom_modem_samples(m_freeDV);
|
||||
int Fs = freedv_get_modem_sample_rate(m_freeDV);
|
||||
int Rs = freedv_get_modem_symbol_rate(m_freeDV);
|
||||
|
||||
if (nSpeechSamples != m_nSpeechSamples)
|
||||
{
|
||||
if (m_speechIn) {
|
||||
delete[] m_speechIn;
|
||||
}
|
||||
|
||||
m_speechIn = new int16_t[nSpeechSamples];
|
||||
m_nSpeechSamples = nSpeechSamples;
|
||||
}
|
||||
|
||||
if (nNomModemSamples != m_nNomModemSamples)
|
||||
{
|
||||
if (m_modOut) {
|
||||
delete[] m_modOut;
|
||||
}
|
||||
|
||||
m_modOut = new int16_t[nNomModemSamples];
|
||||
m_nNomModemSamples = nNomModemSamples;
|
||||
}
|
||||
|
||||
m_iSpeech = 0;
|
||||
m_iModem = 0;
|
||||
|
||||
qDebug() << "FreeDVMod::applyFreeDVMode:"
|
||||
<< " fdv_mode: " << fdv_mode
|
||||
<< " m_modemSampleRate: " << m_modemSampleRate
|
||||
<< " m_lowCutoff: " << m_lowCutoff
|
||||
<< " m_hiCutoff: " << m_hiCutoff
|
||||
<< " Fs: " << Fs
|
||||
<< " Rs: " << Rs
|
||||
<< " m_nSpeechSamples: " << m_nSpeechSamples
|
||||
<< " m_nNomModemSamples: " << m_nNomModemSamples;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeDVModSource::applySettings(const FreeDVModSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_channelSampleRate);
|
||||
}
|
||||
|
||||
if ((m_settings.m_freeDVMode != settings.m_freeDVMode) || force) {
|
||||
applyFreeDVMode(settings.m_freeDVMode);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
140
plugins/channeltx/modfreedv/freedvmodsource.h
Normal file
140
plugins/channeltx/modfreedv/freedvmodsource.h
Normal file
@ -0,0 +1,140 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FREEDVMODSOURCE_H
|
||||
#define INCLUDE_FREEDVMODSOURCE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "audio/audioresampler.h"
|
||||
|
||||
#include "freedvmodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
|
||||
class FreeDVModSource : public ChannelSampleSource
|
||||
{
|
||||
public:
|
||||
FreeDVModSource();
|
||||
virtual ~FreeDVModSource();
|
||||
|
||||
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; }
|
||||
void applyAudioSampleRate(unsigned int sampleRate);
|
||||
CWKeyer& getCWKeyer() { return m_cwKeyer; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
void getLevels(Real& rmsLevel, Real& peakLevel, Real& numSamples) const
|
||||
{
|
||||
rmsLevel = m_rmsLevel;
|
||||
peakLevel = m_peakLevel;
|
||||
numSamples = m_levelNbSamples;
|
||||
}
|
||||
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
unsigned int getModemSampleRate() const { return m_modemSampleRate; }
|
||||
Real getLowCutoff() const { return m_lowCutoff; }
|
||||
Real getHiCutoff() const { return m_hiCutoff; }
|
||||
void setSpectrumSink(BasebandSampleSink *sampleSink) { m_spectrumSink = sampleSink; }
|
||||
|
||||
void applySettings(const FreeDVModSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
void applyFreeDVMode(FreeDVModSettings::FreeDVMode mode);
|
||||
|
||||
private:
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
int m_modemSampleRate;
|
||||
Real m_lowCutoff;
|
||||
Real m_hiCutoff;
|
||||
FreeDVModSettings m_settings;
|
||||
|
||||
NCOF m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
Complex m_modSample;
|
||||
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
bool m_interpolatorConsumed;
|
||||
|
||||
fftfilt* m_SSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
static const int m_ssbFftLen;
|
||||
|
||||
BasebandSampleSink* m_spectrumSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_sumCount;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_rmsLevel;
|
||||
Real m_peakLevelOut;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
std::ifstream *m_ifstream;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
struct freedv *m_freeDV;
|
||||
int m_nSpeechSamples;
|
||||
int m_nNomModemSamples;
|
||||
int m_iSpeech;
|
||||
int m_iModem;
|
||||
int16_t *m_speechIn;
|
||||
int16_t *m_modOut;
|
||||
float m_scaleFactor; //!< divide by this amount to scale from int16 to float in [-1.0, 1.0] interval
|
||||
AudioResampler m_audioResampler;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void processOneSample(Complex& ci);
|
||||
void pullAF(Complex& sample);
|
||||
void pullAudio(unsigned int nbSamples);
|
||||
void pushFeedback(Real sample);
|
||||
void calculateLevel(Complex& sample);
|
||||
void calculateLevel(qint16& sample);
|
||||
void modulateSample();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // INCLUDE_FREEDVMODSOURCE_H
|
@ -16,6 +16,7 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "freedvmod.h"
|
||||
#include "freedvmodwebapiadapter.h"
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
project(modnfm)
|
||||
|
||||
set(modnfm_SOURCES
|
||||
nfmmod.cpp
|
||||
nfmmod.cpp
|
||||
nfmmodbaseband.cpp
|
||||
nfmmodsource.cpp
|
||||
nfmmodplugin.cpp
|
||||
nfmmodsettings.cpp
|
||||
nfmmodwebapiadapter.cpp
|
||||
@ -9,6 +11,8 @@ set(modnfm_SOURCES
|
||||
|
||||
set(modnfm_HEADERS
|
||||
nfmmod.h
|
||||
nfmmodbaseband.h
|
||||
nfmmodsource.h
|
||||
nfmmodplugin.h
|
||||
nfmmodsettings.h
|
||||
nfmmodwebapiadapter.h
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGCWKeyerSettings.h"
|
||||
@ -31,13 +32,13 @@
|
||||
#include <complex.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "nfmmodbaseband.h"
|
||||
#include "nfmmod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureNFMMod, Message)
|
||||
@ -50,54 +51,25 @@ MESSAGE_CLASS_DEFINITION(NFMMod::MsgReportFileSourceStreamTiming, Message)
|
||||
|
||||
const QString NFMMod::m_channelIdURI = "sdrangel.channeltx.modnfm";
|
||||
const QString NFMMod::m_channelId = "NFMMod";
|
||||
const int NFMMod::m_levelNbSamples = 480; // every 10ms
|
||||
|
||||
NFMMod::NFMMod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(48000),
|
||||
m_outputSampleRate(48000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_modPhasor(0.0f),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_fileSize(0),
|
||||
m_recordLength(0),
|
||||
m_sampleRate(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f)
|
||||
m_sampleRate(48000)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new NFMModBaseband();
|
||||
m_basebandSource->setInputFileStream(&m_ifstream);
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
|
||||
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
|
||||
m_lowpass.create(301, m_audioSampleRate, 250.0);
|
||||
m_toneNco.setFreq(1000.0, m_audioSampleRate);
|
||||
m_ctcssNco.setFreq(88.5, m_audioSampleRate);
|
||||
m_cwKeyer.setSampleRate(m_audioSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -108,263 +80,43 @@ NFMMod::~NFMMod()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_feedbackAudioFifo);
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void NFMMod::pull(Sample& sample)
|
||||
{
|
||||
if (m_settings.m_channelMute)
|
||||
{
|
||||
sample.m_real = 0.0f;
|
||||
sample.m_imag = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
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 NFMMod::pullAudio(int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate);
|
||||
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void NFMMod::modulateSample()
|
||||
{
|
||||
Real t;
|
||||
|
||||
pullAF(t);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(t);
|
||||
m_audioBufferFill++;
|
||||
|
||||
if (m_settings.m_ctcssOn)
|
||||
{
|
||||
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 378.0f * m_ctcssNco.next()) * (M_PI / 378.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 378 = 302 * 1.25; 302 = number of filter taps (established experimentally)
|
||||
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 378.0f);
|
||||
}
|
||||
|
||||
m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
|
||||
m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
|
||||
}
|
||||
|
||||
void NFMMod::pullAF(Real& sample)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case NFMModSettings::NFMModInputTone:
|
||||
sample = m_toneNco.next();
|
||||
break;
|
||||
case NFMModSettings::NFMModInputFile:
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
|
||||
if (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 NFMModSettings::NFMModInputAudio:
|
||||
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
|
||||
break;
|
||||
case NFMModSettings::NFMModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = 0.0f;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NFMModSettings::NFMModInputNone:
|
||||
default:
|
||||
sample = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMMod::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 NFMMod::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("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMMod::calculateLevel(Real& sample)
|
||||
{
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
|
||||
m_levelSum += sample * sample;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void NFMMod::start()
|
||||
{
|
||||
qDebug() << "NFMMod::start: m_outputSampleRate: " << m_outputSampleRate
|
||||
<< " m_inputFrequencyOffset: " << m_inputFrequencyOffset;
|
||||
|
||||
m_audioFifo.clear();
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug("NFMMod::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void NFMMod::stop()
|
||||
{
|
||||
qDebug("NFMMod::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
void NFMMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool NFMMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "NFMMod::handleMessage: UpChannelizer::MsgChannelizerNotification";
|
||||
|
||||
applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "NFMMod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " getSampleRate: " << cfg.getSampleRate()
|
||||
<< " getCenterFrequency: " << cfg.getCenterFrequency();
|
||||
<< " getSourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
NFMModBaseband::MsgConfigureChannelizer *msg
|
||||
= NFMModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -412,6 +164,41 @@ bool NFMMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceName::match(cmd))
|
||||
{
|
||||
MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
|
||||
m_fileName = conf.getFileName();
|
||||
openFileStream();
|
||||
qDebug() << "NFMMod::handleMessage: MsgConfigureFileSourceName:"
|
||||
<< " m_fileName: " << m_fileName;
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureFileSourceSeek::match(cmd))
|
||||
{
|
||||
MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
|
||||
int seekPercentage = conf.getPercentage();
|
||||
seekFileStream(seekPercentage);
|
||||
qDebug() << "NFMMod::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 (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
@ -422,33 +209,14 @@ bool NFMMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "NFMMod::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
// Forward to the source
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "NFMMod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSource->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -492,79 +260,6 @@ void NFMMod::seekFileStream(int seekPercentage)
|
||||
}
|
||||
}
|
||||
|
||||
void NFMMod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("NFMMod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
sampleRate, m_settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_lowpass.create(301, sampleRate, 250.0);
|
||||
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
|
||||
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void NFMMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("NFMMod::applyFeedbackAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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_settingsMutex.unlock();
|
||||
|
||||
m_feedbackAudioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void NFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "NFMMod::applyChannelSettings:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate
|
||||
<< " outputSampleRate: " << outputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
m_outputSampleRate = outputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void NFMMod::applySettings(const NFMModSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "NFMMod::applySettings:"
|
||||
@ -617,40 +312,20 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("modAFInput");
|
||||
}
|
||||
|
||||
if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
|
||||
reverseAPIKeys.append("rfBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
|
||||
{
|
||||
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) {
|
||||
reverseAPIKeys.append("afBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
m_lowpass.create(301, m_audioSampleRate, 250.0);
|
||||
m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force)
|
||||
{
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
reverseAPIKeys.append("toneFrequency");
|
||||
m_settingsMutex.lock();
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force)
|
||||
{
|
||||
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) {
|
||||
reverseAPIKeys.append("ctcssIndex");
|
||||
m_settingsMutex.lock();
|
||||
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
||||
@ -658,12 +333,14 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("audioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSource(m_basebandSource->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
if (m_basebandSource->getAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("audioSampleRate");
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,15 +349,20 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("feedbackAudioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
|
||||
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSink(m_basebandSource->getFeedbackAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_feedbackAudioSampleRate != audioSampleRate) {
|
||||
if (m_basebandSource->getFeedbackAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("feedbackAudioSampleRate");
|
||||
applyFeedbackAudioSampleRate(audioSampleRate);
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioOutput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
NFMModBaseband::MsgConfigureNFMModBaseband *msg = NFMModBaseband::MsgConfigureNFMModBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -729,7 +411,7 @@ int NFMMod::webapiSettingsGet(
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getNfmModSettings()->getCwKeyer();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
return 200;
|
||||
@ -748,11 +430,11 @@ int NFMMod::webapiSettingsPutPatch(
|
||||
if (channelSettingsKeys.contains("cwKeyer"))
|
||||
{
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getNfmModSettings()->getCwKeyer();
|
||||
CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
CWKeyerSettings cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiSettingsPutPatch(channelSettingsKeys, cwKeyerSettings, apiCwKeyerSettings);
|
||||
|
||||
CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force);
|
||||
m_cwKeyer.getInputMessageQueue()->push(msgCwKeyer);
|
||||
m_basebandSource->getCWKeyer().getInputMessageQueue()->push(msgCwKeyer);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -763,8 +445,8 @@ int NFMMod::webapiSettingsPutPatch(
|
||||
|
||||
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
|
||||
{
|
||||
NFMMod::MsgConfigureChannelizer *msgChan = NFMMod::MsgConfigureChannelizer::create(
|
||||
m_audioSampleRate, settings.m_inputFrequencyOffset);
|
||||
NFMModBaseband::MsgConfigureChannelizer *msgChan = NFMModBaseband::MsgConfigureChannelizer::create(
|
||||
m_basebandSource->getAudioSampleRate(), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(msgChan);
|
||||
}
|
||||
|
||||
@ -902,8 +584,8 @@ void NFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon
|
||||
void NFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
||||
{
|
||||
response.getNfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
response.getNfmModReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getNfmModReport()->setChannelSampleRate(m_outputSampleRate);
|
||||
response.getNfmModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
|
||||
response.getNfmModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void NFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const NFMModSettings& settings, bool force)
|
||||
@ -963,10 +645,10 @@ void NFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
|
||||
if (force)
|
||||
{
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
swgNFMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgNFMModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
@ -977,13 +659,14 @@ void NFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -998,7 +681,7 @@ void NFMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
|
||||
swgNFModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgNFModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
@ -1008,13 +691,14 @@ void NFMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -1029,11 +713,28 @@ void NFMMod::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("NFMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("NFMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
double NFMMod::getMagSq() const
|
||||
{
|
||||
return m_basebandSource->getMagSq();
|
||||
}
|
||||
|
||||
CWKeyer *NFMMod::getCWKeyer()
|
||||
{
|
||||
return &m_basebandSource->getCWKeyer();
|
||||
}
|
||||
|
||||
void NFMMod::setLevelMeter(QObject *levelMeter)
|
||||
{
|
||||
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
|
||||
// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
@ -27,24 +27,16 @@
|
||||
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "dsp/bandpass.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "nfmmodsettings.h"
|
||||
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class CWKeyer;
|
||||
class NFMModBaseband;
|
||||
|
||||
class NFMMod : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -73,26 +65,33 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -207,10 +206,9 @@ public:
|
||||
~NFMMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -253,23 +251,13 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
CWKeyer *getCWKeyer() { return &m_cwKeyer; }
|
||||
double getMagSq() const;
|
||||
CWKeyer *getCWKeyer();
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
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:
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
@ -277,46 +265,10 @@ private:
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_basebandSampleRate;
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
NFMModBaseband* m_basebandSource;
|
||||
NFMModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
NCOF m_ctcssNco;
|
||||
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;
|
||||
|
||||
Lowpass<Real> m_lowpass;
|
||||
Bandpass<Real> m_bandpass;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
@ -326,26 +278,10 @@ private:
|
||||
quint32 m_recordLength; //!< record length in seconds computed from file size
|
||||
int m_sampleRate;
|
||||
|
||||
NFMModSettings::NFMModInputAF m_afInput;
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
void processOneSample(Complex& ci);
|
||||
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const NFMModSettings& settings, bool force = false);
|
||||
void pullAF(Real& sample);
|
||||
void pushFeedback(Real sample);
|
||||
void calculateLevel(Real& sample);
|
||||
void modulateSample();
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekPercentage);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
228
plugins/channeltx/modnfm/nfmmodbaseband.cpp
Normal file
228
plugins/channeltx/modnfm/nfmmodbaseband.cpp
Normal file
@ -0,0 +1,228 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "nfmmodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(NFMModBaseband::MsgConfigureNFMModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(NFMModBaseband::MsgConfigureChannelizer, Message)
|
||||
|
||||
NFMModBaseband::NFMModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("NFMModBaseband::NFMModBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&NFMModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(m_source.getAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate());
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_source.getFeedbackAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyFeedbackAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
NFMModBaseband::~NFMModBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_source.getFeedbackAudioFifo());
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void NFMModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void NFMModBaseband::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 NFMModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
Real rmsLevel, peakLevel, 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 NFMModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void NFMModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NFMModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "NFMModBaseband::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_source.getAudioSampleRate()) {
|
||||
m_source.applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_source.getFeedbackAudioSampleRate()) {
|
||||
m_source.applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureNFMModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureNFMModBaseband& cfg = (MsgConfigureNFMModBaseband&) cmd;
|
||||
qDebug() << "NFMModBaseband::handleMessage: MsgConfigureNFMModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "NFMModBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< "(requested) sourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "NFMModBaseband::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());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
CWKeyer::MsgConfigureCWKeyer *notif = new CWKeyer::MsgConfigureCWKeyer(cfg);
|
||||
CWKeyer& cwKeyer = m_source.getCWKeyer();
|
||||
cwKeyer.getInputMessageQueue()->push(notif);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMModBaseband::applySettings(const NFMModSettings& settings, bool force)
|
||||
{
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int NFMModBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
123
plugins/channeltx/modnfm/nfmmodbaseband.h
Normal file
123
plugins/channeltx/modnfm/nfmmodbaseband.h
Normal file
@ -0,0 +1,123 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_NFMMODBASEBAND_H
|
||||
#define INCLUDE_NFMMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "nfmmodsource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
|
||||
class NFMModBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureNFMModBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const NFMModSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureNFMModBaseband* create(const NFMModSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureNFMModBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
NFMModSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureNFMModBaseband(const NFMModSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
NFMModBaseband();
|
||||
~NFMModBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
CWKeyer& getCWKeyer() { return m_source.getCWKeyer(); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
unsigned int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
|
||||
unsigned 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(); }
|
||||
|
||||
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;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
NFMModSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
NFMModSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const NFMModSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
|
||||
#endif // INCLUDE_NFMMODBASEBAND_H
|
@ -26,6 +26,7 @@
|
||||
#include "util/simpleserializer.h"
|
||||
#include "util/db.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/audioselectdialog.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
@ -410,7 +411,7 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
|
||||
ui->cwKeyerGUI->setCWKeyer(m_nfmMod->getCWKeyer());
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
connect(m_nfmMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
m_nfmMod->setLevelMeter(ui->volumeMeter);
|
||||
|
||||
m_settings.setChannelMarker(&m_channelMarker);
|
||||
m_settings.setCWKeyerGUI(ui->cwKeyerGUI);
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor NFMModPlugin::m_pluginDescriptor = {
|
||||
QString("NFM Modulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
@ -49,7 +49,7 @@ void NFMModSettings::resetToDefaults()
|
||||
m_afBandwidth = 3000;
|
||||
m_inputFrequencyOffset = 0;
|
||||
m_rfBandwidth = 12500.0f;
|
||||
m_fmDeviation = 5000.0f;
|
||||
m_fmDeviation = 3000.0f;
|
||||
m_toneFrequency = 1000.0f;
|
||||
m_volumeFactor = 1.0f;
|
||||
m_channelMute = false;
|
||||
@ -129,7 +129,7 @@ bool NFMModSettings::deserialize(const QByteArray& data)
|
||||
m_inputFrequencyOffset = tmp;
|
||||
d.readReal(2, &m_rfBandwidth, 12500.0);
|
||||
d.readReal(3, &m_afBandwidth, 1000.0);
|
||||
d.readReal(4, &m_fmDeviation, 5000.0);
|
||||
d.readReal(4, &m_fmDeviation, 3000.0);
|
||||
d.readU32(5, &m_rgbColor);
|
||||
d.readReal(6, &m_toneFrequency, 1000.0);
|
||||
d.readReal(7, &m_volumeFactor, 1.0);
|
||||
|
359
plugins/channeltx/modnfm/nfmmodsource.cpp
Normal file
359
plugins/channeltx/modnfm/nfmmodsource.cpp
Normal file
@ -0,0 +1,359 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "nfmmodsource.h"
|
||||
|
||||
const int NFMModSource::m_levelNbSamples = 480; // every 10ms
|
||||
const float NFMModSource::m_preemphasis = 120.0e-6; // 120us
|
||||
|
||||
NFMModSource::NFMModSource() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_modPhasor(0.0f),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_ifstream(nullptr),
|
||||
m_preemphasisFilter(m_preemphasis*48000),
|
||||
m_audioSampleRate(48000)
|
||||
{
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
NFMModSource::~NFMModSource()
|
||||
{
|
||||
}
|
||||
|
||||
void NFMModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void NFMModSource::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 NFMModSource::prefetch(unsigned int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
|
||||
pullAudio(nbSamplesAudio);
|
||||
}
|
||||
|
||||
void NFMModSource::pullAudio(unsigned int nbSamplesAudio)
|
||||
{
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void NFMModSource::modulateSample()
|
||||
{
|
||||
Real t0, t;
|
||||
|
||||
pullAF(t0);
|
||||
m_preemphasisFilter.process(t0, t);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(t);
|
||||
m_audioBufferFill++;
|
||||
|
||||
if (m_settings.m_ctcssOn)
|
||||
{
|
||||
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 189.0f * m_ctcssNco.next()) * (M_PI / 189.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 378 = 302 * 1.25; 302 = number of filter taps (established experimentally) and 189 = 378/2 for 2*PI
|
||||
m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * m_bandpass.filter(t) * (M_PI / 189.0f);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
void NFMModSource::pullAF(Real& sample)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case NFMModSettings::NFMModInputTone:
|
||||
sample = m_toneNco.next();
|
||||
break;
|
||||
case NFMModSettings::NFMModInputFile:
|
||||
// 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 NFMModSettings::NFMModInputAudio:
|
||||
sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
|
||||
break;
|
||||
case NFMModSettings::NFMModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
sample = m_toneNco.next() * fadeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample = 0.0f;
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NFMModSettings::NFMModInputNone:
|
||||
default:
|
||||
sample = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMModSource::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 NFMModSource::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("NFMModSource::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMModSource::calculateLevel(Real& sample)
|
||||
{
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
|
||||
m_levelSum += sample * sample;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
m_peakLevelOut = m_peakLevel;
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NFMModSource::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("NFMModSource::applyAudioSampleRate: %u", 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, 250.0);
|
||||
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
|
||||
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
m_cwKeyer.reset();
|
||||
m_preemphasisFilter.configure(m_preemphasis*sampleRate);
|
||||
m_audioSampleRate = sampleRate;
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void NFMModSource::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("NFMModSource::applyFeedbackAudioSampleRate: %u", 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 NFMModSource::applySettings(const NFMModSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|
||||
|| (settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
|
||||
{
|
||||
m_settings.m_rfBandwidth = settings.m_rfBandwidth;
|
||||
m_settings.m_afBandwidth = settings.m_afBandwidth;
|
||||
applyAudioSampleRate(m_audioSampleRate);
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
|
||||
}
|
||||
|
||||
if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) {
|
||||
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
void NFMModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "NFMModSource::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;
|
||||
}
|
127
plugins/channeltx/modnfm/nfmmodsource.h
Normal file
127
plugins/channeltx/modnfm/nfmmodsource.h
Normal file
@ -0,0 +1,127 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_NFMMODSOURCE_H
|
||||
#define INCLUDE_NFMMODSOURCE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/lowpass.h"
|
||||
#include "dsp/bandpass.h"
|
||||
#include "dsp/filterrc.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
#include "nfmmodsettings.h"
|
||||
|
||||
class NFMModSource : public ChannelSampleSource
|
||||
{
|
||||
public:
|
||||
NFMModSource();
|
||||
virtual ~NFMModSource();
|
||||
|
||||
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(unsigned int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
unsigned int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
|
||||
CWKeyer& getCWKeyer() { return m_cwKeyer; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
void getLevels(Real& rmsLevel, Real& peakLevel, Real& numSamples) const
|
||||
{
|
||||
rmsLevel = m_rmsLevel;
|
||||
peakLevel = m_peakLevel;
|
||||
numSamples = m_levelNbSamples;
|
||||
}
|
||||
void applySettings(const NFMModSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
|
||||
private:
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
NFMModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
NCOF m_ctcssNco;
|
||||
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;
|
||||
|
||||
Lowpass<Real> m_lowpass;
|
||||
Bandpass<Real> m_bandpass;
|
||||
HighPassFilterRC m_preemphasisFilter;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_rmsLevel;
|
||||
Real m_peakLevelOut;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
std::ifstream *m_ifstream;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // INCLUDE_NFMMODSOURCE_H
|
@ -16,6 +16,7 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "nfmmod.h"
|
||||
#include "nfmmodwebapiadapter.h"
|
||||
|
||||
|
@ -2,6 +2,8 @@ project(modssb)
|
||||
|
||||
set(modssb_SOURCES
|
||||
ssbmod.cpp
|
||||
ssbmodbaseband.cpp
|
||||
ssbmodsource.cpp
|
||||
ssbmodplugin.cpp
|
||||
ssbmodsettings.cpp
|
||||
ssbmodwebapiadapter.cpp
|
||||
@ -9,6 +11,8 @@ set(modssb_SOURCES
|
||||
|
||||
set(modssb_HEADERS
|
||||
ssbmod.h
|
||||
ssbmodbaseband.h
|
||||
ssbmodsource.h
|
||||
ssbmodplugin.h
|
||||
ssbmodsettings.h
|
||||
ssbmodwebapiadapter.h
|
||||
|
@ -15,14 +15,13 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "ssbmod.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
@ -32,13 +31,15 @@
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGSSBModReport.h"
|
||||
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "ssbmodbaseband.h"
|
||||
#include "ssbmod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureSSBMod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureChannelizer, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceName, Message)
|
||||
@ -49,84 +50,25 @@ MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamTiming, Message)
|
||||
|
||||
const QString SSBMod::m_channelIdURI = "sdrangel.channeltx.modssb";
|
||||
const QString SSBMod::m_channelId = "SSBMod";
|
||||
const int SSBMod::m_levelNbSamples = 480; // every 10ms
|
||||
const int SSBMod::m_ssbFftLen = 1024;
|
||||
|
||||
SSBMod::SSBMod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(48000),
|
||||
m_outputSampleRate(48000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_SSBFilter(0),
|
||||
m_DSBFilter(0),
|
||||
m_SSBFilterBuffer(0),
|
||||
m_DSBFilterBuffer(0),
|
||||
m_SSBFilterBufferIndex(0),
|
||||
m_DSBFilterBufferIndex(0),
|
||||
m_sampleSink(0),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_fileSize(0),
|
||||
m_recordLength(0),
|
||||
m_sampleRate(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_agcStepLength(2400)
|
||||
m_sampleRate(48000)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new SSBModBaseband();
|
||||
m_basebandSource->setInputFileStream(&m_ifstream);
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue());
|
||||
m_feedbackAudioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
|
||||
m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_audioSampleRate, m_settings.m_bandwidth / m_audioSampleRate, m_ssbFftLen);
|
||||
m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen);
|
||||
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
|
||||
m_DSBFilterBuffer = new Complex[m_ssbFftLen];
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0});
|
||||
// memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
|
||||
// memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_sum.real(0.0f);
|
||||
m_sum.imag(0.0f);
|
||||
m_undersampleCount = 0;
|
||||
m_sumCount = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
m_toneNco.setFreq(1000.0, m_audioSampleRate);
|
||||
m_cwKeyer.setSampleRate(48000);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
m_audioCompressor.initSimple(
|
||||
m_audioSampleRate,
|
||||
50, // pregain (dB)
|
||||
-30, // threshold (dB)
|
||||
20, // knee (dB)
|
||||
12, // ratio (dB)
|
||||
0.003, // attack (s)
|
||||
0.25 // release (s)
|
||||
);
|
||||
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -137,482 +79,50 @@ SSBMod::~SSBMod()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_feedbackAudioFifo);
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
|
||||
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
|
||||
delete m_SSBFilter;
|
||||
delete m_DSBFilter;
|
||||
delete[] m_SSBFilterBuffer;
|
||||
delete[] m_DSBFilterBuffer;
|
||||
}
|
||||
|
||||
void SSBMod::pull(Sample& sample)
|
||||
{
|
||||
Complex ci;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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
|
||||
ci *= 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
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 SSBMod::pullAudio(int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate);
|
||||
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void SSBMod::modulateSample()
|
||||
{
|
||||
pullAF(m_modSample);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(m_modSample * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(m_modSample);
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
void SSBMod::pullAF(Complex& sample)
|
||||
{
|
||||
if (m_settings.m_audioMute)
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
fftfilt::cmplx *filtered;
|
||||
int n_out = 0;
|
||||
|
||||
int decim = 1<<(m_settings.m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case SSBModSettings::SSBModInputTone:
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = m_toneNco.next()/1.25;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ();
|
||||
} else {
|
||||
sample = m_toneNco.nextQI();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SSBModSettings::SSBModInputFile:
|
||||
// Monaural (mono):
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
|
||||
// Binaural (stereo):
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 2 f4exb_call.raw
|
||||
if (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())
|
||||
{
|
||||
ci.real(0.0f);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_audioBinaural)
|
||||
{
|
||||
Complex c;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&c), sizeof(Complex));
|
||||
|
||||
if (m_settings.m_audioFlipChannels)
|
||||
{
|
||||
ci.real(c.imag() * m_settings.m_volumeFactor);
|
||||
ci.imag(c.real() * m_settings.m_volumeFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ci = c * m_settings.m_volumeFactor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Real real;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&real), sizeof(Real));
|
||||
|
||||
if (m_settings.m_agc)
|
||||
{
|
||||
real = m_audioCompressor.compress(real);
|
||||
ci.real(real);
|
||||
ci.imag(0.0f);
|
||||
ci *= m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(real * m_settings.m_volumeFactor);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(0.0f);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
break;
|
||||
case SSBModSettings::SSBModInputAudio:
|
||||
if (m_settings.m_audioBinaural)
|
||||
{
|
||||
if (m_settings.m_audioFlipChannels)
|
||||
{
|
||||
ci.real((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
ci.imag((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
ci.imag((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_agc)
|
||||
{
|
||||
ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f));
|
||||
ci.real(m_audioCompressor.compress(ci.real()));
|
||||
ci.imag(0.0f);
|
||||
ci *= m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SSBModSettings::SSBModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = m_toneNco.next() * fadeFactor;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ() * fadeFactor;
|
||||
} else {
|
||||
sample = m_toneNco.nextQI() * fadeFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = (m_toneNco.next() * fadeFactor)/1.25;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ() * fadeFactor;
|
||||
} else {
|
||||
sample = m_toneNco.nextQI() * fadeFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SSBModSettings::SSBModInputNone:
|
||||
default:
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputFile)
|
||||
|| (m_settings.m_modAFInput == SSBModSettings::SSBModInputAudio)) // real audio
|
||||
{
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
n_out = m_DSBFilter->runDSB(ci, &filtered);
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_DSBFilterBufferIndex = 0;
|
||||
}
|
||||
|
||||
sample = m_DSBFilterBuffer[m_DSBFilterBufferIndex];
|
||||
m_DSBFilterBufferIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
n_out = m_SSBFilter->runSSB(ci, &filtered, m_settings.m_usb);
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
}
|
||||
|
||||
sample = m_SSBFilterBuffer[m_SSBFilterBufferIndex];
|
||||
m_SSBFilterBufferIndex++;
|
||||
}
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += filtered[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
|
||||
if (!m_settings.m_dsb & !m_settings.m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Real audio
|
||||
else if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputTone)
|
||||
|| (m_settings.m_modAFInput == SSBModSettings::SSBModInputCWTone)) // tone
|
||||
{
|
||||
m_sum += sample;
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
|
||||
if (!m_settings.m_dsb & !m_settings.m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
|
||||
if (m_sumCount < (m_settings.m_dsb ? m_ssbFftLen : m_ssbFftLen>>1))
|
||||
{
|
||||
n_out = 0;
|
||||
m_sumCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
n_out = m_sumCount;
|
||||
m_sumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
if (m_sampleSink != 0)
|
||||
{
|
||||
m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_settings.m_dsb);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SSBMod::pushFeedback(Complex c)
|
||||
{
|
||||
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 SSBMod::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("AMDemod::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SSBMod::calculateLevel(Complex& sample)
|
||||
{
|
||||
Real t = sample.real(); // TODO: possibly adjust depending on sample type
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void SSBMod::start()
|
||||
{
|
||||
qDebug() << "SSBMod::start: m_outputSampleRate: " << m_outputSampleRate
|
||||
<< " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset;
|
||||
|
||||
m_audioFifo.clear();
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug("SSBMod::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void SSBMod::stop()
|
||||
{
|
||||
qDebug("SSBMod::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
void SSBMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool SSBMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "SSBMod::handleMessage: MsgChannelizerNotification";
|
||||
|
||||
applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "SSBMod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
|
||||
<< " centerFrequency: " << cfg.getCenterFrequency();
|
||||
qDebug() << "SSBMod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " getSourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
SSBModBaseband::MsgConfigureChannelizer *msg
|
||||
= SSBModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureSSBMod::match(cmd))
|
||||
{
|
||||
MsgConfigureSSBMod& cfg = (MsgConfigureSSBMod&) cmd;
|
||||
qDebug() << "SSBMod::handleMessage: MsgConfigureSSBMod";
|
||||
qDebug() << "NFMMod::handleMessage: MsgConfigureSSBMod";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
@ -662,33 +172,14 @@ bool SSBMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "SSBMod::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
// Forward to the source
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "NFMMod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSource->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -735,109 +226,6 @@ void SSBMod::seekFileStream(int seekPercentage)
|
||||
}
|
||||
}
|
||||
|
||||
void SSBMod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("SSBMod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
|
||||
MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
|
||||
sampleRate, m_settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(channelConfigMsg);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, sampleRate, m_settings.m_bandwidth, 3.0);
|
||||
|
||||
float band = m_settings.m_bandwidth;
|
||||
float lowCutoff = m_settings.m_lowCutoff;
|
||||
bool usb = m_settings.m_usb;
|
||||
|
||||
if (band < 100.0f) // at least 100 Hz
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
if (band - lowCutoff < 100.0f) {
|
||||
lowCutoff = band - 100.0f;
|
||||
}
|
||||
|
||||
m_SSBFilter->create_filter(lowCutoff / sampleRate, band / sampleRate);
|
||||
m_DSBFilter->create_dsb_filter((2.0f * band) / sampleRate);
|
||||
|
||||
m_settings.m_bandwidth = band;
|
||||
m_settings.m_lowCutoff = lowCutoff;
|
||||
m_settings.m_usb = usb;
|
||||
|
||||
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
|
||||
m_audioCompressor.m_rate = sampleRate;
|
||||
m_audioCompressor.initState();
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
DSPConfigureAudio *cfg = new DSPConfigureAudio(m_audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
getMessageQueueToGUI()->push(cfg);
|
||||
}
|
||||
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void SSBMod::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("SSBMod::applyFeedbackAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
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_settingsMutex.unlock();
|
||||
|
||||
m_feedbackAudioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void SSBMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "SSBMod::applyChannelSettings:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate
|
||||
<< " outputSampleRate: " << outputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, m_settings.m_bandwidth, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
m_outputSampleRate = outputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void SSBMod::applySettings(const SSBModSettings& settings, bool force)
|
||||
{
|
||||
float band = settings.m_bandwidth;
|
||||
@ -897,61 +285,18 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("audioDeviceName");
|
||||
}
|
||||
|
||||
if ((settings.m_bandwidth != m_settings.m_bandwidth) ||
|
||||
(settings.m_lowCutoff != m_settings.m_lowCutoff) || force)
|
||||
{
|
||||
if (band < 100.0f) // at least 100 Hz
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
if (band - lowCutoff < 100.0f) {
|
||||
lowCutoff = band - 100.0f;
|
||||
}
|
||||
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, band, 3.0);
|
||||
m_SSBFilter->create_filter(lowCutoff / m_audioSampleRate, band / m_audioSampleRate);
|
||||
m_DSBFilter->create_dsb_filter((2.0f * band) / m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_dsb != m_settings.m_dsb) || force)
|
||||
{
|
||||
if (settings.m_dsb)
|
||||
{
|
||||
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0});
|
||||
//memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
|
||||
m_DSBFilterBufferIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
//memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
||||
{
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSource(m_basebandSource->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
if (m_basebandSource->getAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("audioSampleRate");
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -960,15 +305,19 @@ void SSBMod::applySettings(const SSBModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("feedbackAudioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_feedbackAudioDeviceName);
|
||||
audioDeviceManager->addAudioSink(&m_feedbackAudioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSink(m_basebandSource->getFeedbackAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_feedbackAudioSampleRate != audioSampleRate) {
|
||||
if (m_basebandSource->getFeedbackAudioSampleRate() != audioSampleRate) {
|
||||
reverseAPIKeys.append("feedbackAudioSampleRate");
|
||||
applyFeedbackAudioSampleRate(audioSampleRate);
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioOutput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
SSBModBaseband::MsgConfigureSSBModBaseband *msg = SSBModBaseband::MsgConfigureSSBModBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -1017,7 +366,7 @@ int SSBMod::webapiSettingsGet(
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
return 200;
|
||||
@ -1036,11 +385,11 @@ int SSBMod::webapiSettingsPutPatch(
|
||||
if (channelSettingsKeys.contains("cwKeyer"))
|
||||
{
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getSsbModSettings()->getCwKeyer();
|
||||
CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
CWKeyerSettings cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiSettingsPutPatch(channelSettingsKeys, cwKeyerSettings, apiCwKeyerSettings);
|
||||
|
||||
CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force);
|
||||
m_cwKeyer.getInputMessageQueue()->push(msgCwKeyer);
|
||||
m_basebandSource->getCWKeyer().getInputMessageQueue()->push(msgCwKeyer);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -1052,7 +401,7 @@ int SSBMod::webapiSettingsPutPatch(
|
||||
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
|
||||
{
|
||||
SSBMod::MsgConfigureChannelizer *msgChan = SSBMod::MsgConfigureChannelizer::create(
|
||||
m_audioSampleRate, settings.m_inputFrequencyOffset);
|
||||
m_basebandSource->getAudioSampleRate(), settings.m_inputFrequencyOffset);
|
||||
m_inputMessageQueue.push(msgChan);
|
||||
}
|
||||
|
||||
@ -1205,8 +554,8 @@ void SSBMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon
|
||||
void SSBMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
||||
{
|
||||
response.getSsbModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
response.getSsbModReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getSsbModReport()->setChannelSampleRate(m_outputSampleRate);
|
||||
response.getSsbModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
|
||||
response.getSsbModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void SSBMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const SSBModSettings& settings, bool force)
|
||||
@ -1275,10 +624,10 @@ void SSBMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
|
||||
if (force)
|
||||
{
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
swgSSBModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgSSBModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
@ -1289,13 +638,14 @@ void SSBMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -1310,7 +660,7 @@ void SSBMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
|
||||
swgSSBModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgSSBModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
@ -1320,13 +670,14 @@ void SSBMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -1341,10 +692,38 @@ void SSBMod::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("SSBMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("SSBMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
double SSBMod::getMagSq() const
|
||||
{
|
||||
return m_basebandSource->getMagSq();
|
||||
}
|
||||
|
||||
CWKeyer *SSBMod::getCWKeyer()
|
||||
{
|
||||
return &m_basebandSource->getCWKeyer();
|
||||
}
|
||||
|
||||
void SSBMod::setLevelMeter(QObject *levelMeter)
|
||||
{
|
||||
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
}
|
||||
|
||||
unsigned int SSBMod::getAudioSampleRate() const
|
||||
{
|
||||
return m_basebandSource->getAudioSampleRate();
|
||||
}
|
||||
|
||||
void SSBMod::setSpectrumSink(BasebandSampleSink *sampleSink)
|
||||
{
|
||||
m_basebandSource->setSpectrumSink(sampleSink);
|
||||
}
|
@ -28,23 +28,16 @@
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "audio/audiocompressorsnd.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "ssbmodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class CWKeyer;
|
||||
class SSBModBaseband;
|
||||
|
||||
class SSBMod : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -73,26 +66,33 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -207,12 +207,9 @@ public:
|
||||
~SSBMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -255,24 +252,15 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
CWKeyer *getCWKeyer() { return &m_cwKeyer; }
|
||||
double getMagSq() const;
|
||||
CWKeyer *getCWKeyer();
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
unsigned int getAudioSampleRate() const;
|
||||
void setSpectrumSink(BasebandSampleSink *sampleSink);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
|
||||
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:
|
||||
enum RateState {
|
||||
RSInitialFill,
|
||||
@ -280,56 +268,11 @@ private:
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_basebandSampleRate;
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
SSBModBaseband* m_basebandSource;
|
||||
SSBModSettings m_settings;
|
||||
|
||||
NCOF m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
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;
|
||||
|
||||
fftfilt* m_SSBFilter;
|
||||
fftfilt* m_DSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
Complex* m_DSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
int m_DSBFilterBufferIndex;
|
||||
static const int m_ssbFftLen;
|
||||
|
||||
BasebandSampleSink* m_sampleSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_sumCount;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
std::ifstream m_ifstream;
|
||||
@ -338,28 +281,10 @@ private:
|
||||
quint32 m_recordLength; //!< record length in seconds computed from file size
|
||||
int m_sampleRate;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
AudioCompressorSnd m_audioCompressor;
|
||||
int m_agcStepLength;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
void processOneSample(Complex& ci);
|
||||
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const SSBModSettings& settings, bool force = false);
|
||||
void pullAF(Complex& sample);
|
||||
void pushFeedback(Complex sample);
|
||||
void calculateLevel(Complex& sample);
|
||||
void modulateSample();
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekPercentage);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
227
plugins/channeltx/modssb/ssbmodbaseband.cpp
Normal file
227
plugins/channeltx/modssb/ssbmodbaseband.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/upsamplechannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
|
||||
#include "ssbmodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(SSBModBaseband::MsgConfigureSSBModBaseband, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SSBModBaseband::MsgConfigureChannelizer, Message)
|
||||
|
||||
SSBModBaseband::SSBModBaseband() :
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
|
||||
m_channelizer = new UpSampleChannelizer(&m_source);
|
||||
|
||||
qDebug("SSBModBaseband::SSBModBaseband");
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSourceFifo::dataRead,
|
||||
this,
|
||||
&SSBModBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(m_source.getAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate());
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_source.getFeedbackAudioFifo(), getInputMessageQueue());
|
||||
m_source.applyFeedbackAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate());
|
||||
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
SSBModBaseband::~SSBModBaseband()
|
||||
{
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_source.getFeedbackAudioFifo());
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void SSBModBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void SSBModBaseband::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 SSBModBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
SampleVector& data = m_sampleFifo.getData();
|
||||
unsigned int ipart1begin;
|
||||
unsigned int ipart1end;
|
||||
unsigned int ipart2begin;
|
||||
unsigned int ipart2end;
|
||||
Real rmsLevel, peakLevel, 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 SSBModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
|
||||
{
|
||||
m_channelizer->prefetch(iEnd - iBegin);
|
||||
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
|
||||
}
|
||||
|
||||
void SSBModBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SSBModBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
DSPConfigureAudio::AudioType audioType = cfg.getAudioType();
|
||||
|
||||
qDebug() << "SSBModBaseband::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate
|
||||
<< " audioType: " << audioType;
|
||||
|
||||
if (audioType == DSPConfigureAudio::AudioInput)
|
||||
{
|
||||
if (sampleRate != m_source.getAudioSampleRate()) {
|
||||
m_source.applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
else if (audioType == DSPConfigureAudio::AudioOutput)
|
||||
{
|
||||
if (sampleRate != m_source.getFeedbackAudioSampleRate()) {
|
||||
m_source.applyFeedbackAudioSampleRate(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureSSBModBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureSSBModBaseband& cfg = (MsgConfigureSSBModBaseband&) cmd;
|
||||
qDebug() << "SSBModBaseband::handleMessage: MsgConfigureSSBModBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "SSBModBaseband::handleMessage: MsgConfigureChannelizer"
|
||||
<< "(requested) sourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< "(requested) sourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
m_channelizer->setChannelization(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "SSBModBaseband::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());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (CWKeyer::MsgConfigureCWKeyer::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
const CWKeyer::MsgConfigureCWKeyer& cfg = (CWKeyer::MsgConfigureCWKeyer&) cmd;
|
||||
CWKeyer::MsgConfigureCWKeyer *notif = new CWKeyer::MsgConfigureCWKeyer(cfg);
|
||||
CWKeyer& cwKeyer = m_source.getCWKeyer();
|
||||
cwKeyer.getInputMessageQueue()->push(notif);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SSBModBaseband::applySettings(const SSBModSettings& settings, bool force)
|
||||
{
|
||||
m_source.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
int SSBModBaseband::getChannelSampleRate() const
|
||||
{
|
||||
return m_channelizer->getChannelSampleRate();
|
||||
}
|
124
plugins/channeltx/modssb/ssbmodbaseband.h
Normal file
124
plugins/channeltx/modssb/ssbmodbaseband.h
Normal file
@ -0,0 +1,124 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SSBMODBASEBAND_H
|
||||
#define INCLUDE_SSBMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesourcefifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "ssbmodsource.h"
|
||||
|
||||
class UpSampleChannelizer;
|
||||
class BasebandSampleSink;
|
||||
|
||||
class SSBModBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureSSBModBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const SSBModSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureSSBModBaseband* create(const SSBModSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigureSSBModBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
SSBModSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureSSBModBaseband(const SSBModSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
SSBModBaseband();
|
||||
~SSBModBaseband();
|
||||
void reset();
|
||||
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
CWKeyer& getCWKeyer() { return m_source.getCWKeyer(); }
|
||||
double getMagSq() const { return m_source.getMagSq(); }
|
||||
unsigned int getAudioSampleRate() const { return m_source.getAudioSampleRate(); }
|
||||
unsigned 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 setSpectrumSink(BasebandSampleSink *sampleSink) { m_source.setSpectrumSink(sampleSink); }
|
||||
|
||||
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;
|
||||
UpSampleChannelizer *m_channelizer;
|
||||
SSBModSource m_source;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
SSBModSettings m_settings;
|
||||
QMutex m_mutex;
|
||||
|
||||
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
|
||||
bool handleMessage(const Message& cmd);
|
||||
void applySettings(const SSBModSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SSBMODBASEBAND_H
|
@ -31,6 +31,7 @@
|
||||
#include "util/db.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "gui/audioselectdialog.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
@ -405,7 +406,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
|
||||
|
||||
m_spectrumVis = new SpectrumVis(SDR_TX_SCALEF, ui->glSpectrum);
|
||||
m_ssbMod = (SSBMod*) channelTx; //new SSBMod(m_deviceUISet->m_deviceSinkAPI);
|
||||
m_ssbMod->setSpectrumSampleSink(m_spectrumVis);
|
||||
m_ssbMod->setSpectrumSink(m_spectrumVis);
|
||||
m_ssbMod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
|
||||
resetToDefaults();
|
||||
@ -455,7 +456,7 @@ SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
|
||||
m_settings.setCWKeyerGUI(ui->cwKeyerGUI);
|
||||
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
|
||||
connect(m_ssbMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
m_ssbMod->setLevelMeter(ui->volumeMeter);
|
||||
|
||||
m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On);
|
||||
m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::Off);
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
const PluginDescriptor SSBModPlugin::m_pluginDescriptor = {
|
||||
QString("SSB Modulator"),
|
||||
QString("4.11.6"),
|
||||
QString("4.12.0"),
|
||||
QString("(c) Edouard Griffiths, F4EXB"),
|
||||
QString("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
|
638
plugins/channeltx/modssb/ssbmodsource.cpp
Normal file
638
plugins/channeltx/modssb/ssbmodsource.cpp
Normal file
@ -0,0 +1,638 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "ssbmodsource.h"
|
||||
|
||||
const int SSBModSource::m_ssbFftLen = 1024;
|
||||
const int SSBModSource::m_levelNbSamples = 480; // every 10ms
|
||||
|
||||
SSBModSource::SSBModSource() :
|
||||
m_channelSampleRate(48000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_audioFifo(4800),
|
||||
m_feedbackAudioFifo(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f),
|
||||
m_ifstream(nullptr),
|
||||
m_audioSampleRate(48000)
|
||||
{
|
||||
m_SSBFilter = new fftfilt(m_settings.m_lowCutoff / m_audioSampleRate, m_settings.m_bandwidth / m_audioSampleRate, m_ssbFftLen);
|
||||
m_DSBFilter = new fftfilt((2.0f * m_settings.m_bandwidth) / m_audioSampleRate, 2 * m_ssbFftLen);
|
||||
m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
|
||||
m_DSBFilterBuffer = new Complex[m_ssbFftLen];
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0});
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_feedbackAudioBuffer.resize(1<<14);
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
|
||||
m_sum.real(0.0f);
|
||||
m_sum.imag(0.0f);
|
||||
m_undersampleCount = 0;
|
||||
m_sumCount = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
m_toneNco.setFreq(1000.0, m_audioSampleRate);
|
||||
|
||||
m_cwKeyer.setSampleRate(m_audioSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
m_audioCompressor.initSimple(
|
||||
m_audioSampleRate,
|
||||
50, // pregain (dB)
|
||||
-30, // threshold (dB)
|
||||
20, // knee (dB)
|
||||
12, // ratio (dB)
|
||||
0.003, // attack (s)
|
||||
0.25 // release (s)
|
||||
);
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
SSBModSource::~SSBModSource()
|
||||
{
|
||||
delete m_SSBFilter;
|
||||
delete m_DSBFilter;
|
||||
delete[] m_SSBFilterBuffer;
|
||||
delete[] m_DSBFilterBuffer;
|
||||
}
|
||||
|
||||
void SSBModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
|
||||
{
|
||||
std::for_each(
|
||||
begin,
|
||||
begin + nbSamples,
|
||||
[this](Sample& s) {
|
||||
pullOne(s);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SSBModSource::pullOne(Sample& sample)
|
||||
{
|
||||
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
|
||||
ci *= 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
|
||||
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 SSBModSource::prefetch(unsigned int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
|
||||
pullAudio(nbSamplesAudio);
|
||||
}
|
||||
|
||||
void SSBModSource::pullAudio(unsigned int nbSamplesAudio)
|
||||
{
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void SSBModSource::modulateSample()
|
||||
{
|
||||
pullAF(m_modSample);
|
||||
|
||||
if (m_settings.m_feedbackAudioEnable) {
|
||||
pushFeedback(m_modSample * m_settings.m_feedbackVolumeFactor * 16384.0f);
|
||||
}
|
||||
|
||||
calculateLevel(m_modSample);
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
void SSBModSource::pullAF(Complex& sample)
|
||||
{
|
||||
if (m_settings.m_audioMute)
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci;
|
||||
fftfilt::cmplx *filtered;
|
||||
int n_out = 0;
|
||||
|
||||
int decim = 1<<(m_settings.m_spanLog2 - 1);
|
||||
unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
|
||||
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case SSBModSettings::SSBModInputTone:
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = m_toneNco.next()/1.25;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ();
|
||||
} else {
|
||||
sample = m_toneNco.nextQI();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SSBModSettings::SSBModInputFile:
|
||||
// Monaural (mono):
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
|
||||
// Binaural (stereo):
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 2 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())
|
||||
{
|
||||
ci.real(0.0f);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_audioBinaural)
|
||||
{
|
||||
Complex c;
|
||||
m_ifstream->read(reinterpret_cast<char*>(&c), sizeof(Complex));
|
||||
|
||||
if (m_settings.m_audioFlipChannels)
|
||||
{
|
||||
ci.real(c.imag() * m_settings.m_volumeFactor);
|
||||
ci.imag(c.real() * m_settings.m_volumeFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ci = c * m_settings.m_volumeFactor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Real real;
|
||||
m_ifstream->read(reinterpret_cast<char*>(&real), sizeof(Real));
|
||||
|
||||
if (m_settings.m_agc)
|
||||
{
|
||||
real = m_audioCompressor.compress(real);
|
||||
ci.real(real);
|
||||
ci.imag(0.0f);
|
||||
ci *= m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(real * m_settings.m_volumeFactor);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(0.0f);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
break;
|
||||
case SSBModSettings::SSBModInputAudio:
|
||||
if (m_settings.m_audioBinaural)
|
||||
{
|
||||
if (m_settings.m_audioFlipChannels)
|
||||
{
|
||||
ci.real((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
ci.imag((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real((m_audioBuffer[m_audioBufferFill].l / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
ci.imag((m_audioBuffer[m_audioBufferFill].r / SDR_TX_SCALEF) * m_settings.m_volumeFactor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_agc)
|
||||
{
|
||||
ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f));
|
||||
ci.real(m_audioCompressor.compress(ci.real()));
|
||||
ci.imag(0.0f);
|
||||
ci *= m_settings.m_volumeFactor;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor);
|
||||
ci.imag(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SSBModSettings::SSBModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = m_toneNco.next() * fadeFactor;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ() * fadeFactor;
|
||||
} else {
|
||||
sample = m_toneNco.nextQI() * fadeFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
Real t = (m_toneNco.next() * fadeFactor)/1.25;
|
||||
sample.real(t);
|
||||
sample.imag(t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_settings.m_usb) {
|
||||
sample = m_toneNco.nextIQ() * fadeFactor;
|
||||
} else {
|
||||
sample = m_toneNco.nextQI() * fadeFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
m_toneNco.setPhase(0);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SSBModSettings::SSBModInputNone:
|
||||
default:
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
break;
|
||||
}
|
||||
|
||||
if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputFile)
|
||||
|| (m_settings.m_modAFInput == SSBModSettings::SSBModInputAudio)) // real audio
|
||||
{
|
||||
if (m_settings.m_dsb)
|
||||
{
|
||||
n_out = m_DSBFilter->runDSB(ci, &filtered);
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_DSBFilterBufferIndex = 0;
|
||||
}
|
||||
|
||||
sample = m_DSBFilterBuffer[m_DSBFilterBufferIndex];
|
||||
m_DSBFilterBufferIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
n_out = m_SSBFilter->runSSB(ci, &filtered, m_settings.m_usb);
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
}
|
||||
|
||||
sample = m_SSBFilterBuffer[m_SSBFilterBufferIndex];
|
||||
m_SSBFilterBufferIndex++;
|
||||
}
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
for (int i = 0; i < n_out; i++)
|
||||
{
|
||||
// Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
|
||||
// smart decimation with bit gain using float arithmetic (23 bits significand)
|
||||
|
||||
m_sum += filtered[i];
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
|
||||
if (!m_settings.m_dsb & !m_settings.m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Real audio
|
||||
else if ((m_settings.m_modAFInput == SSBModSettings::SSBModInputTone)
|
||||
|| (m_settings.m_modAFInput == SSBModSettings::SSBModInputCWTone)) // tone
|
||||
{
|
||||
m_sum += sample;
|
||||
|
||||
if (!(m_undersampleCount++ & decim_mask))
|
||||
{
|
||||
Real avgr = (m_sum.real() / decim) * 0.891235351562f * SDR_TX_SCALEF; //scaling at -1 dB to account for possible filter overshoot
|
||||
Real avgi = (m_sum.imag() / decim) * 0.891235351562f * SDR_TX_SCALEF;
|
||||
|
||||
if (!m_settings.m_dsb & !m_settings.m_usb)
|
||||
{ // invert spectrum for LSB
|
||||
m_sampleBuffer.push_back(Sample(avgi, avgr));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sampleBuffer.push_back(Sample(avgr, avgi));
|
||||
}
|
||||
|
||||
m_sum.real(0.0);
|
||||
m_sum.imag(0.0);
|
||||
}
|
||||
|
||||
if (m_sumCount < (m_settings.m_dsb ? m_ssbFftLen : m_ssbFftLen>>1))
|
||||
{
|
||||
n_out = 0;
|
||||
m_sumCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
n_out = m_sumCount;
|
||||
m_sumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (n_out > 0)
|
||||
{
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_settings.m_dsb);
|
||||
}
|
||||
|
||||
m_sampleBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SSBModSource::pushFeedback(Complex c)
|
||||
{
|
||||
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 SSBModSource::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("SSBModSource::pushFeedback: %u/%u audio samples written m_feedbackInterpolatorDistance: %f",
|
||||
res, m_feedbackAudioBufferFill, m_feedbackInterpolatorDistance);
|
||||
m_feedbackAudioFifo.clear();
|
||||
}
|
||||
|
||||
m_feedbackAudioBufferFill = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SSBModSource::calculateLevel(Complex& sample)
|
||||
{
|
||||
Real t = sample.real(); // TODO: possibly adjust depending on sample type
|
||||
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), t);
|
||||
m_levelSum += t * t;
|
||||
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 SSBModSource::applyAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("SSBModSource::applyAudioSampleRate: %u", sampleRate);
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) sampleRate / (Real) m_channelSampleRate;
|
||||
m_interpolator.create(48, sampleRate, m_settings.m_bandwidth, 3.0);
|
||||
|
||||
float band = m_settings.m_bandwidth;
|
||||
float lowCutoff = m_settings.m_lowCutoff;
|
||||
bool usb = m_settings.m_usb;
|
||||
|
||||
if (band < 100.0f) // at least 100 Hz
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
if (band - lowCutoff < 100.0f) {
|
||||
lowCutoff = band - 100.0f;
|
||||
}
|
||||
|
||||
m_SSBFilter->create_filter(lowCutoff / sampleRate, band / sampleRate);
|
||||
m_DSBFilter->create_dsb_filter((2.0f * band) / sampleRate);
|
||||
|
||||
m_settings.m_bandwidth = band;
|
||||
m_settings.m_lowCutoff = lowCutoff;
|
||||
m_settings.m_usb = usb;
|
||||
|
||||
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
|
||||
m_cwKeyer.setSampleRate(sampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
m_audioCompressor.m_rate = sampleRate;
|
||||
m_audioCompressor.initState();
|
||||
m_audioSampleRate = sampleRate;
|
||||
|
||||
applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
|
||||
}
|
||||
|
||||
void SSBModSource::applyFeedbackAudioSampleRate(unsigned int sampleRate)
|
||||
{
|
||||
qDebug("SSBModSource::applyFeedbackAudioSampleRate: %u", 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 SSBModSource::applySettings(const SSBModSettings& settings, bool force)
|
||||
{
|
||||
float band = settings.m_bandwidth;
|
||||
float lowCutoff = settings.m_lowCutoff;
|
||||
bool usb = settings.m_usb;
|
||||
|
||||
if ((settings.m_bandwidth != m_settings.m_bandwidth) ||
|
||||
(settings.m_lowCutoff != m_settings.m_lowCutoff) || force)
|
||||
{
|
||||
if (band < 100.0f) // at least 100 Hz
|
||||
{
|
||||
band = 100.0f;
|
||||
lowCutoff = 0;
|
||||
}
|
||||
|
||||
if (band - lowCutoff < 100.0f) {
|
||||
lowCutoff = band - 100.0f;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_channelSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, band, 3.0);
|
||||
m_SSBFilter->create_filter(lowCutoff / m_audioSampleRate, band / m_audioSampleRate);
|
||||
m_DSBFilter->create_dsb_filter((2.0f * band) / m_audioSampleRate);
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
|
||||
}
|
||||
|
||||
if ((settings.m_dsb != m_settings.m_dsb) || force)
|
||||
{
|
||||
if (settings.m_dsb)
|
||||
{
|
||||
std::fill(m_DSBFilterBuffer, m_DSBFilterBuffer+m_ssbFftLen, Complex{0,0});
|
||||
m_DSBFilterBufferIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fill(m_SSBFilterBuffer, m_SSBFilterBuffer+(m_ssbFftLen>>1), Complex{0,0});
|
||||
m_SSBFilterBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
m_settings.m_bandwidth = band;
|
||||
m_settings.m_lowCutoff = lowCutoff;
|
||||
m_settings.m_usb = usb;
|
||||
}
|
||||
|
||||
void SSBModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "SSBModSource::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_bandwidth, 3.0);
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
137
plugins/channeltx/modssb/ssbmodsource.h
Normal file
137
plugins/channeltx/modssb/ssbmodsource.h
Normal file
@ -0,0 +1,137 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_SSBMODSOURCE_H
|
||||
#define INCLUDE_SSBMODSOURCE_H
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "dsp/channelsamplesource.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "audio/audiocompressorsnd.h"
|
||||
#include "audio/audiofifo.h"
|
||||
|
||||
#include "ssbmodsettings.h"
|
||||
|
||||
class BasebandSampleSink;
|
||||
|
||||
class SSBModSource : public ChannelSampleSource
|
||||
{
|
||||
public:
|
||||
SSBModSource();
|
||||
virtual ~SSBModSource();
|
||||
|
||||
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(unsigned int sampleRate);
|
||||
void applyFeedbackAudioSampleRate(unsigned int sampleRate);
|
||||
unsigned int getAudioSampleRate() const { return m_audioSampleRate; }
|
||||
unsigned int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
|
||||
CWKeyer& getCWKeyer() { return m_cwKeyer; }
|
||||
double getMagSq() const { return m_magsq; }
|
||||
void getLevels(Real& rmsLevel, Real& peakLevel, Real& numSamples) const
|
||||
{
|
||||
rmsLevel = m_rmsLevel;
|
||||
peakLevel = m_peakLevel;
|
||||
numSamples = m_levelNbSamples;
|
||||
}
|
||||
void applySettings(const SSBModSettings& settings, bool force = false);
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = 0);
|
||||
void setSpectrumSink(BasebandSampleSink *sampleSink) { m_spectrumSink = sampleSink; }
|
||||
|
||||
private:
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
SSBModSettings m_settings;
|
||||
|
||||
NCOF m_carrierNco;
|
||||
NCOF m_toneNco;
|
||||
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;
|
||||
|
||||
fftfilt* m_SSBFilter;
|
||||
fftfilt* m_DSBFilter;
|
||||
Complex* m_SSBFilterBuffer;
|
||||
Complex* m_DSBFilterBuffer;
|
||||
int m_SSBFilterBufferIndex;
|
||||
int m_DSBFilterBufferIndex;
|
||||
static const int m_ssbFftLen;
|
||||
|
||||
BasebandSampleSink* m_spectrumSink;
|
||||
SampleVector m_sampleBuffer;
|
||||
|
||||
fftfilt::cmplx m_sum;
|
||||
int m_undersampleCount;
|
||||
int m_sumCount;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
quint32 m_feedbackAudioSampleRate;
|
||||
AudioVector m_feedbackAudioBuffer;
|
||||
uint m_feedbackAudioBufferFill;
|
||||
AudioFifo m_feedbackAudioFifo;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_rmsLevel;
|
||||
Real m_peakLevelOut;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
|
||||
std::ifstream *m_ifstream;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
AudioCompressorSnd m_audioCompressor;
|
||||
int m_agcStepLength;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void processOneSample(Complex& ci);
|
||||
void pullAF(Complex& sample);
|
||||
void pullAudio(unsigned int nbSamples);
|
||||
void pushFeedback(Complex sample);
|
||||
void calculateLevel(Complex& sample);
|
||||
void modulateSample();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_SSBMODSOURCE_H
|
@ -16,6 +16,7 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "ssbmod.h"
|
||||
#include "ssbmodwebapiadapter.h"
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
project(modwfm)
|
||||
|
||||
set(modwfm_SOURCES
|
||||
wfmmod.cpp
|
||||
wfmmod.cpp
|
||||
wfmmodbaseband.cpp
|
||||
wfmmodsource.cpp
|
||||
wfmmodplugin.cpp
|
||||
wfmmodsettings.cpp
|
||||
wfmmodwebapiadapter.cpp
|
||||
@ -9,6 +11,8 @@ set(modwfm_SOURCES
|
||||
|
||||
set(modwfm_HEADERS
|
||||
wfmmod.h
|
||||
wfmmodbaseband.h
|
||||
wfmmodsource.h
|
||||
wfmmodplugin.h
|
||||
wfmmodsettings.h
|
||||
wfmmodwebapiadapter.h
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
@ -30,13 +31,12 @@
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGAMModReport.h"
|
||||
|
||||
#include "dsp/upchannelizer.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/threadedbasebandsamplesource.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
|
||||
#include "wfmmodbaseband.h"
|
||||
#include "wfmmod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMMod, Message)
|
||||
@ -49,51 +49,25 @@ MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamTiming, Message)
|
||||
|
||||
const QString WFMMod::m_channelIdURI = "sdrangel.channeltx.modwfm";
|
||||
const QString WFMMod::m_channelId = "WFMMod";
|
||||
const int WFMMod::m_levelNbSamples = 480; // every 10ms
|
||||
const int WFMMod::m_rfFilterFFTLength = 1024;
|
||||
|
||||
WFMMod::WFMMod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(384000),
|
||||
m_outputSampleRate(384000),
|
||||
m_inputFrequencyOffset(0),
|
||||
m_modPhasor(0.0f),
|
||||
m_audioFifo(4800),
|
||||
m_settingsMutex(QMutex::Recursive),
|
||||
m_fileSize(0),
|
||||
m_recordLength(0),
|
||||
m_sampleRate(48000),
|
||||
m_levelCalcCount(0),
|
||||
m_peakLevel(0.0f),
|
||||
m_levelSum(0.0f)
|
||||
m_sampleRate(48000)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_rfFilter = new fftfilt(-62500.0 / 384000.0, 62500.0 / 384000.0, m_rfFilterFFTLength);
|
||||
m_rfFilterBuffer = new Complex[m_rfFilterFFTLength];
|
||||
std::fill(m_rfFilterBuffer, m_rfFilterBuffer+m_rfFilterFFTLength, Complex{0,0});
|
||||
//memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength));
|
||||
m_rfFilterBufferIndex = 0;
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSource = new WFMModBaseband();
|
||||
m_basebandSource->setInputFileStream(&m_ifstream);
|
||||
m_basebandSource->moveToThread(m_thread);
|
||||
|
||||
m_audioBuffer.resize(1<<14);
|
||||
m_audioBufferFill = 0;
|
||||
|
||||
m_magsq = 0.0;
|
||||
|
||||
DSPEngine::instance()->getAudioDeviceManager()->addAudioSource(&m_audioFifo, getInputMessageQueue());
|
||||
m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getInputSampleRate();
|
||||
|
||||
m_toneNcoRF.setFreq(1000.0, m_outputSampleRate);
|
||||
m_cwKeyer.setSampleRate(m_outputSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_channelizer = new UpChannelizer(this);
|
||||
m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
|
||||
m_deviceAPI->addChannelSource(m_threadedChannelizer);
|
||||
m_deviceAPI->addChannelSource(this);
|
||||
m_deviceAPI->addChannelSourceAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
@ -104,227 +78,50 @@ WFMMod::~WFMMod()
|
||||
{
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(&m_audioFifo);
|
||||
m_deviceAPI->removeChannelSourceAPI(this);
|
||||
m_deviceAPI->removeChannelSource(m_threadedChannelizer);
|
||||
delete m_threadedChannelizer;
|
||||
delete m_channelizer;
|
||||
delete m_rfFilter;
|
||||
delete[] m_rfFilterBuffer;
|
||||
}
|
||||
|
||||
void WFMMod::pull(Sample& sample)
|
||||
{
|
||||
if (m_settings.m_channelMute)
|
||||
{
|
||||
sample.m_real = 0.0f;
|
||||
sample.m_imag = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
Complex ci, ri;
|
||||
fftfilt::cmplx *rf;
|
||||
int rf_out;
|
||||
|
||||
m_settingsMutex.lock();
|
||||
|
||||
if ((m_settings.m_modAFInput == WFMModSettings::WFMModInputFile)
|
||||
|| (m_settings.m_modAFInput == WFMModSettings::WFMModInputAudio))
|
||||
{
|
||||
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri))
|
||||
{
|
||||
pullAF(m_modSample);
|
||||
calculateLevel(m_modSample.real());
|
||||
m_audioBufferFill++;
|
||||
}
|
||||
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
pullAF(ri);
|
||||
}
|
||||
|
||||
m_modPhasor += (m_settings.m_fmDeviation / (float) m_outputSampleRate) * ri.real() * M_PI * 2.0f;
|
||||
ci.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
|
||||
ci.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
|
||||
|
||||
// RF filtering
|
||||
rf_out = m_rfFilter->runFilt(ci, &rf);
|
||||
|
||||
if (rf_out > 0)
|
||||
{
|
||||
memcpy((void *) m_rfFilterBuffer, (const void *) rf, rf_out*sizeof(Complex));
|
||||
m_rfFilterBufferIndex = 0;
|
||||
|
||||
}
|
||||
|
||||
ci = m_rfFilterBuffer[m_rfFilterBufferIndex] * m_carrierNco.nextIQ(); // shift to carrier frequency
|
||||
m_rfFilterBufferIndex++;
|
||||
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
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 WFMMod::pullAudio(int nbSamples)
|
||||
{
|
||||
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_basebandSampleRate);
|
||||
|
||||
if (nbSamplesAudio > m_audioBuffer.size())
|
||||
{
|
||||
m_audioBuffer.resize(nbSamplesAudio);
|
||||
}
|
||||
|
||||
m_audioFifo.read(reinterpret_cast<quint8*>(&m_audioBuffer[0]), nbSamplesAudio);
|
||||
m_audioBufferFill = 0;
|
||||
}
|
||||
|
||||
void WFMMod::pullAF(Complex& sample)
|
||||
{
|
||||
switch (m_settings.m_modAFInput)
|
||||
{
|
||||
case WFMModSettings::WFMModInputTone:
|
||||
sample.real(m_toneNcoRF.next() * m_settings.m_volumeFactor);
|
||||
sample.imag(0.0f);
|
||||
break;
|
||||
case WFMModSettings::WFMModInputFile:
|
||||
// sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
|
||||
// ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
|
||||
if (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.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Real s;
|
||||
m_ifstream.read(reinterpret_cast<char*>(&s), sizeof(Real));
|
||||
sample.real(s * m_settings.m_volumeFactor);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
break;
|
||||
case WFMModSettings::WFMModInputAudio:
|
||||
{
|
||||
sample.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
break;
|
||||
case WFMModSettings::WFMModInputCWTone:
|
||||
Real fadeFactor;
|
||||
|
||||
if (m_cwKeyer.getSample())
|
||||
{
|
||||
m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
|
||||
sample.real(m_toneNcoRF.next() * m_settings.m_volumeFactor * fadeFactor);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
|
||||
{
|
||||
sample.real(m_toneNcoRF.next() * m_settings.m_volumeFactor * fadeFactor);
|
||||
sample.imag(0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
m_toneNcoRF.setPhase(0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WFMModSettings::WFMModInputNone:
|
||||
default:
|
||||
sample.real(0.0f);
|
||||
sample.imag(0.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WFMMod::calculateLevel(const Real& sample)
|
||||
{
|
||||
if (m_levelCalcCount < m_levelNbSamples)
|
||||
{
|
||||
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
|
||||
m_levelSum += sample * sample;
|
||||
m_levelCalcCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
|
||||
//qDebug("WFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
|
||||
emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
|
||||
m_peakLevel = 0.0f;
|
||||
m_levelSum = 0.0f;
|
||||
m_levelCalcCount = 0;
|
||||
}
|
||||
m_deviceAPI->removeChannelSource(this);
|
||||
delete m_basebandSource;
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
void WFMMod::start()
|
||||
{
|
||||
qDebug() << "WFMMod::start: m_outputSampleRate: " << m_outputSampleRate
|
||||
<< " m_inputFrequencyOffset: " << m_inputFrequencyOffset;
|
||||
|
||||
m_audioFifo.clear();
|
||||
applyChannelSettings(m_basebandSampleRate, m_outputSampleRate, m_inputFrequencyOffset, true);
|
||||
qDebug("WFMMod::start");
|
||||
m_basebandSource->reset();
|
||||
m_thread->start();
|
||||
}
|
||||
|
||||
void WFMMod::stop()
|
||||
{
|
||||
qDebug("WFMMod::stop");
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
}
|
||||
|
||||
void WFMMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
|
||||
{
|
||||
m_basebandSource->pull(begin, nbSamples);
|
||||
}
|
||||
|
||||
bool WFMMod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
|
||||
{
|
||||
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
|
||||
qDebug() << "WFMMod::handleMessage: MsgChannelizerNotification";
|
||||
|
||||
applyChannelSettings(notif.getBasebandSampleRate(), notif.getSampleRate(), notif.getFrequencyOffset());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureChannelizer::match(cmd))
|
||||
if (MsgConfigureChannelizer::match(cmd))
|
||||
{
|
||||
MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
|
||||
qDebug() << "WFMMod::handleMessage: MsgConfigureChannelizer:"
|
||||
<< " getSampleRate: " << cfg.getSampleRate()
|
||||
<< " getCenterFrequency: " << cfg.getCenterFrequency();
|
||||
<< " getSourceSampleRate: " << cfg.getSourceSampleRate()
|
||||
<< " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
|
||||
|
||||
m_channelizer->configure(m_channelizer->getInputMessageQueue(),
|
||||
cfg.getSampleRate(),
|
||||
cfg.getCenterFrequency());
|
||||
WFMModBaseband::MsgConfigureChannelizer *msg
|
||||
= WFMModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgConfigureWFMMod::match(cmd))
|
||||
{
|
||||
MsgConfigureWFMMod& cfg = (MsgConfigureWFMMod&) cmd;
|
||||
qDebug() << "NFWFMMod::handleMessage: MsgConfigureWFMMod";
|
||||
qDebug() << "WFMMod::handleMessage: MsgConfigureWFMMod";
|
||||
|
||||
WFMModSettings settings = cfg.getSettings();
|
||||
|
||||
@ -373,22 +170,14 @@ bool WFMMod::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPConfigureAudio::match(cmd))
|
||||
{
|
||||
DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
|
||||
uint32_t sampleRate = cfg.getSampleRate();
|
||||
|
||||
qDebug() << "WFMMod::handleMessage: DSPConfigureAudio:"
|
||||
<< " sampleRate: " << sampleRate;
|
||||
|
||||
if (sampleRate != m_audioSampleRate) {
|
||||
applyAudioSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
// Forward to the source
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "WFMMod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSource->getInputMessageQueue()->push(rep);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -432,56 +221,6 @@ void WFMMod::seekFileStream(int seekPercentage)
|
||||
}
|
||||
}
|
||||
|
||||
void WFMMod::applyAudioSampleRate(int sampleRate)
|
||||
{
|
||||
qDebug("WFMMod::applyAudioSampleRate: %d", sampleRate);
|
||||
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) sampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, sampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
|
||||
m_audioSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
void WFMMod::applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "WFMMod::applyChannelSettings:"
|
||||
<< " basebandSampleRate: " << basebandSampleRate
|
||||
<< " outputSampleRate: " << outputSampleRate
|
||||
<< " inputFrequencyOffset: " << inputFrequencyOffset;
|
||||
|
||||
if ((inputFrequencyOffset != m_inputFrequencyOffset) ||
|
||||
(outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_carrierNco.setFreq(inputFrequencyOffset, outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((outputSampleRate != m_outputSampleRate) || force)
|
||||
{
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, m_settings.m_rfBandwidth / 2.2, 3.0);
|
||||
Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / outputSampleRate;
|
||||
Real hiCut = (m_settings.m_rfBandwidth / 2.0) / outputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_toneNcoRF.setFreq(m_settings.m_toneFrequency, outputSampleRate);
|
||||
m_cwKeyer.setSampleRate(outputSampleRate);
|
||||
m_cwKeyer.reset();
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
m_basebandSampleRate = basebandSampleRate;
|
||||
m_outputSampleRate = outputSampleRate;
|
||||
m_inputFrequencyOffset = inputFrequencyOffset;
|
||||
}
|
||||
|
||||
void WFMMod::applySettings(const WFMModSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "WFMMod::applySettings:"
|
||||
@ -522,34 +261,14 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force)
|
||||
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) {
|
||||
reverseAPIKeys.append("modAFInput");
|
||||
}
|
||||
|
||||
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
|
||||
{
|
||||
if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) {
|
||||
reverseAPIKeys.append("afBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorConsumed = false;
|
||||
m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_outputSampleRate;
|
||||
m_interpolator.create(48, m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
|
||||
{
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
|
||||
reverseAPIKeys.append("rfBandwidth");
|
||||
m_settingsMutex.lock();
|
||||
Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_outputSampleRate;
|
||||
Real hiCut = (settings.m_rfBandwidth / 2.0) / m_outputSampleRate;
|
||||
m_rfFilter->create_filter(lowCut, hiCut);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force)
|
||||
{
|
||||
if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
|
||||
reverseAPIKeys.append("toneFrequency");
|
||||
m_settingsMutex.lock();
|
||||
m_toneNcoRF.setFreq(settings.m_toneFrequency, m_outputSampleRate);
|
||||
m_settingsMutex.unlock();
|
||||
}
|
||||
|
||||
if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force)
|
||||
@ -557,14 +276,20 @@ void WFMMod::applySettings(const WFMModSettings& settings, bool force)
|
||||
reverseAPIKeys.append("audioDeviceName");
|
||||
AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
|
||||
int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
|
||||
audioDeviceManager->addAudioSource(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
|
||||
audioDeviceManager->addAudioSource(m_basebandSource->getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
|
||||
uint32_t audioSampleRate = audioDeviceManager->getInputSampleRate(audioDeviceIndex);
|
||||
|
||||
if (m_audioSampleRate != audioSampleRate) {
|
||||
applyAudioSampleRate(audioSampleRate);
|
||||
if (m_basebandSource->getAudioSampleRate() != audioSampleRate)
|
||||
{
|
||||
reverseAPIKeys.append("audioSampleRate");
|
||||
DSPConfigureAudio *msg = new DSPConfigureAudio(audioSampleRate, DSPConfigureAudio::AudioInput);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
WFMModBaseband::MsgConfigureWFMModBaseband *msg = WFMModBaseband::MsgConfigureWFMModBaseband::create(settings, force);
|
||||
m_basebandSource->getInputMessageQueue()->push(msg);
|
||||
|
||||
if (settings.m_useReverseAPI)
|
||||
{
|
||||
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
|
||||
@ -610,7 +335,7 @@ int WFMMod::webapiSettingsGet(
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
return 200;
|
||||
@ -629,11 +354,11 @@ int WFMMod::webapiSettingsPutPatch(
|
||||
if (channelSettingsKeys.contains("cwKeyer"))
|
||||
{
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = response.getWfmModSettings()->getCwKeyer();
|
||||
CWKeyerSettings cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
CWKeyerSettings cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
CWKeyer::webapiSettingsPutPatch(channelSettingsKeys, cwKeyerSettings, apiCwKeyerSettings);
|
||||
|
||||
CWKeyer::MsgConfigureCWKeyer *msgCwKeyer = CWKeyer::MsgConfigureCWKeyer::create(cwKeyerSettings, force);
|
||||
m_cwKeyer.getInputMessageQueue()->push(msgCwKeyer);
|
||||
m_basebandSource->getCWKeyer().getInputMessageQueue()->push(msgCwKeyer);
|
||||
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
@ -776,8 +501,8 @@ void WFMMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon
|
||||
void WFMMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
|
||||
{
|
||||
response.getWfmModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
|
||||
response.getWfmModReport()->setAudioSampleRate(m_audioSampleRate);
|
||||
response.getWfmModReport()->setChannelSampleRate(m_outputSampleRate);
|
||||
response.getWfmModReport()->setAudioSampleRate(m_basebandSource->getAudioSampleRate());
|
||||
response.getWfmModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
|
||||
}
|
||||
|
||||
void WFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const WFMModSettings& settings, bool force)
|
||||
@ -831,10 +556,10 @@ void WFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
|
||||
if (force)
|
||||
{
|
||||
const CWKeyerSettings& cwKeyerSettings = m_cwKeyer.getSettings();
|
||||
const CWKeyerSettings& cwKeyerSettings = m_basebandSource->getCWKeyer().getSettings();
|
||||
swgWFMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgWFMModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
}
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
@ -845,13 +570,14 @@ void WFMMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, cons
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -866,7 +592,7 @@ void WFMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
|
||||
swgWFMModSettings->setCwKeyer(new SWGSDRangel::SWGCWKeyerSettings());
|
||||
SWGSDRangel::SWGCWKeyerSettings *apiCwKeyerSettings = swgWFMModSettings->getCwKeyer();
|
||||
m_cwKeyer.webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
m_basebandSource->getCWKeyer().webapiFormatChannelSettings(apiCwKeyerSettings, cwKeyerSettings);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(m_settings.m_reverseAPIAddress)
|
||||
@ -876,13 +602,14 @@ void WFMMod::webapiReverseSendCWSettings(const CWKeyerSettings& cwKeyerSettings)
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer=new QBuffer();
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->asJson().toUtf8());
|
||||
buffer->seek(0);
|
||||
|
||||
// Always use PATCH to avoid passing reverse API settings
|
||||
m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
|
||||
buffer->setParent(reply);
|
||||
|
||||
delete swgChannelSettings;
|
||||
}
|
||||
@ -897,10 +624,28 @@ void WFMMod::networkManagerFinished(QNetworkReply *reply)
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("WFMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("WFMMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
double WFMMod::getMagSq() const
|
||||
{
|
||||
return m_basebandSource->getMagSq();
|
||||
}
|
||||
|
||||
CWKeyer *WFMMod::getCWKeyer()
|
||||
{
|
||||
return &m_basebandSource->getCWKeyer();
|
||||
}
|
||||
|
||||
void WFMMod::setLevelMeter(QObject *levelMeter)
|
||||
{
|
||||
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
|
||||
}
|
||||
|
@ -27,23 +27,16 @@
|
||||
|
||||
#include "dsp/basebandsamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/ncof.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/fftfilt.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "dsp/agc.h"
|
||||
#include "dsp/cwkeyer.h"
|
||||
#include "audio/audiofifo.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "wfmmodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
class ThreadedBasebandSampleSource;
|
||||
class UpChannelizer;
|
||||
class CWKeyer;
|
||||
class WFMModBaseband;
|
||||
|
||||
class WFMMod : public BasebandSampleSource, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
@ -72,26 +65,33 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
/**
|
||||
* |<------ Baseband from device (before device soft interpolation) -------------------------->|
|
||||
* |<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|<- Channel SR ------->|
|
||||
* | ^-------------------------------|
|
||||
* | | Source CF
|
||||
* | | Source SR |
|
||||
*/
|
||||
class MsgConfigureChannelizer : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
int getSampleRate() const { return m_sampleRate; }
|
||||
int getCenterFrequency() const { return m_centerFrequency; }
|
||||
int getSourceSampleRate() const { return m_sourceSampleRate; }
|
||||
int getSourceCenterFrequency() const { return m_sourceCenterFrequency; }
|
||||
|
||||
static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
|
||||
static MsgConfigureChannelizer* create(int sourceSampleRate, int sourceCenterFrequency)
|
||||
{
|
||||
return new MsgConfigureChannelizer(sampleRate, centerFrequency);
|
||||
return new MsgConfigureChannelizer(sourceSampleRate, sourceCenterFrequency);
|
||||
}
|
||||
|
||||
private:
|
||||
int m_sampleRate;
|
||||
int m_centerFrequency;
|
||||
int m_sourceSampleRate;
|
||||
int m_sourceCenterFrequency;
|
||||
|
||||
MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
|
||||
MsgConfigureChannelizer(int sourceSampleRate, int sourceCenterFrequency) :
|
||||
Message(),
|
||||
m_sampleRate(sampleRate),
|
||||
m_centerFrequency(centerFrequency)
|
||||
m_sourceSampleRate(sourceSampleRate),
|
||||
m_sourceCenterFrequency(sourceCenterFrequency)
|
||||
{ }
|
||||
};
|
||||
|
||||
@ -206,10 +206,9 @@ public:
|
||||
~WFMMod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
virtual void pull(Sample& sample);
|
||||
virtual void pullAudio(int nbSamples);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
@ -252,9 +251,9 @@ public:
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
CWKeyer *getCWKeyer() { return &m_cwKeyer; }
|
||||
double getMagSq() const;
|
||||
CWKeyer *getCWKeyer();
|
||||
void setLevelMeter(QObject *levelMeter);
|
||||
|
||||
static const QString m_channelIdURI;
|
||||
static const QString m_channelId;
|
||||
@ -276,36 +275,10 @@ private:
|
||||
};
|
||||
|
||||
DeviceAPI* m_deviceAPI;
|
||||
ThreadedBasebandSampleSource* m_threadedChannelizer;
|
||||
UpChannelizer* m_channelizer;
|
||||
|
||||
int m_basebandSampleRate;
|
||||
int m_outputSampleRate;
|
||||
int m_inputFrequencyOffset;
|
||||
QThread *m_thread;
|
||||
WFMModBaseband* m_basebandSource;
|
||||
WFMModSettings m_settings;
|
||||
|
||||
NCO m_carrierNco;
|
||||
NCOF m_toneNcoRF;
|
||||
float m_modPhasor; //!< baseband modulator phasor
|
||||
Complex m_modSample;
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
bool m_interpolatorConsumed;
|
||||
|
||||
fftfilt* m_rfFilter;
|
||||
static const int m_rfFilterFFTLength;
|
||||
fftfilt::cmplx *m_rfFilterBuffer;
|
||||
int m_rfFilterBufferIndex;
|
||||
|
||||
double m_magsq;
|
||||
MovingAverageUtil<double, double, 16> m_movingAverage;
|
||||
|
||||
quint32 m_audioSampleRate;
|
||||
AudioVector m_audioBuffer;
|
||||
uint m_audioBufferFill;
|
||||
AudioFifo m_audioFifo;
|
||||
|
||||
SampleVector m_sampleBuffer;
|
||||
QMutex m_settingsMutex;
|
||||
|
||||
@ -315,21 +288,12 @@ private:
|
||||
quint32 m_recordLength; //!< record length in seconds computed from file size
|
||||
int m_sampleRate;
|
||||
|
||||
quint32 m_levelCalcCount;
|
||||
Real m_peakLevel;
|
||||
Real m_levelSum;
|
||||
CWKeyer m_cwKeyer;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
static const int m_levelNbSamples;
|
||||
|
||||
void applyAudioSampleRate(int sampleRate);
|
||||
void applyChannelSettings(int basebandSampleRate, int outputSampleRate, int inputFrequencyOffset, bool force = false);
|
||||
void applySettings(const WFMModSettings& settings, bool force = false);
|
||||
void pullAF(Complex& sample);
|
||||
void calculateLevel(const Real& sample);
|
||||
void openFileStream();
|
||||
void seekFileStream(int seekPercentage);
|
||||
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user