SampleSourceFifo refactoring and Tx code reorganization

This commit is contained in:
f4exb 2019-11-15 01:04:24 +01:00
parent 246ff824af
commit 3b74153ec6
198 changed files with 13267 additions and 7750 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -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),

View File

@ -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;
};

View File

@ -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

View File

@ -47,8 +47,7 @@ public:
{ }
};
static const float m_sampleFifoLengthInSeconds;
static const int m_sampleFifoMinSize;
static const unsigned int m_sampleFifoMinRate;
};

View File

@ -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;

View File

@ -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),

View File

@ -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;

View File

@ -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),

View File

@ -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),

View File

@ -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;
};

View File

@ -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),

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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());
}

View File

@ -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();

View 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();
}

View 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

View File

@ -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()

View File

@ -86,7 +86,6 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
void applyChannelSettings();
void configureFileName();
void updateWithAcquisition();
void updateWithStreamData();

View File

@ -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,

View 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()
{}

View 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_

View 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;
}

View 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_

View File

@ -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

View File

@ -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();
}

View File

@ -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_ */

View File

@ -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

View 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();
}

View 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

View File

@ -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()

View File

@ -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();

View File

@ -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"/>

View File

@ -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,

View File

@ -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;

View File

@ -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;

View 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
);
}
}

View 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_

View File

@ -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()

View File

@ -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;

View File

@ -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
)

View File

@ -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)));
}

View File

@ -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);

View 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();
}

View 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

View File

@ -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);

View File

@ -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,

View 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;
}

View 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

View File

@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "dsp/cwkeyer.h"
#include "ammod.h"
#include "ammodwebapiadapter.h"

View File

@ -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

View File

@ -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_ */

View 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);
}

View 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

View File

@ -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);

View File

@ -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,

View 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()
{ }

View 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_

File diff suppressed because it is too large Load Diff

View 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_ */

View File

@ -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

View File

@ -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)));
}

View File

@ -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);

View 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();
}

View 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

View File

@ -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)

View File

@ -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,

View 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;
}

View 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

View File

@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "dsp/cwkeyer.h"
#include "freedvmod.h"
#include "freedvmodwebapiadapter.h"

View File

@ -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

View File

@ -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)));
}

View File

@ -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);

View 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();
}

View 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

View File

@ -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);

View File

@ -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,

View File

@ -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);

View 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;
}

View 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

View File

@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "dsp/cwkeyer.h"
#include "nfmmod.h"
#include "nfmmodwebapiadapter.h"

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View 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();
}

View 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

View File

@ -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);

View File

@ -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,

View 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;
}

View 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

View File

@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "dsp/cwkeyer.h"
#include "ssbmod.h"
#include "ssbmodwebapiadapter.h"

View File

@ -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

View File

@ -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)));
}

View File

@ -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