diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0c87bd25a..bba1c77ac 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/devices/bladerf1/devicebladerf1shared.cpp b/devices/bladerf1/devicebladerf1shared.cpp
index 934f9fe84..dc373cd30 100644
--- a/devices/bladerf1/devicebladerf1shared.cpp
+++ b/devices/bladerf1/devicebladerf1shared.cpp
@@ -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;
diff --git a/devices/bladerf1/devicebladerf1shared.h b/devices/bladerf1/devicebladerf1shared.h
index 2f0f4fd4c..eea918389 100644
--- a/devices/bladerf1/devicebladerf1shared.h
+++ b/devices/bladerf1/devicebladerf1shared.h
@@ -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;
};
diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp
index 84f4461bd..d2975c7e0 100644
--- a/devices/bladerf2/devicebladerf2shared.cpp
+++ b/devices/bladerf2/devicebladerf2shared.cpp
@@ -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),
diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h
index 347ea170a..878bce927 100644
--- a/devices/bladerf2/devicebladerf2shared.h
+++ b/devices/bladerf2/devicebladerf2shared.h
@@ -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;
};
diff --git a/devices/hackrf/devicehackrfshared.cpp b/devices/hackrf/devicehackrfshared.cpp
index 69fa9249d..a76505e3d 100644
--- a/devices/hackrf/devicehackrfshared.cpp
+++ b/devices/hackrf/devicehackrfshared.cpp
@@ -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
diff --git a/devices/hackrf/devicehackrfshared.h b/devices/hackrf/devicehackrfshared.h
index cf83658af..2eb7c4bf9 100644
--- a/devices/hackrf/devicehackrfshared.h
+++ b/devices/hackrf/devicehackrfshared.h
@@ -47,8 +47,7 @@ public:
{ }
};
- static const float m_sampleFifoLengthInSeconds;
- static const int m_sampleFifoMinSize;
+ static const unsigned int m_sampleFifoMinRate;
};
diff --git a/devices/limesdr/devicelimesdrshared.cpp b/devices/limesdr/devicelimesdrshared.cpp
index 151e9c318..33f855458 100644
--- a/devices/limesdr/devicelimesdrshared.cpp
+++ b/devices/limesdr/devicelimesdrshared.cpp
@@ -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;
diff --git a/devices/limesdr/devicelimesdrshared.h b/devices/limesdr/devicelimesdrshared.h
index c2ba75997..74f44e0ab 100644
--- a/devices/limesdr/devicelimesdrshared.h
+++ b/devices/limesdr/devicelimesdrshared.h
@@ -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),
diff --git a/devices/plutosdr/deviceplutosdrshared.cpp b/devices/plutosdr/deviceplutosdrshared.cpp
index 1b7cd3882..a26d0518c 100644
--- a/devices/plutosdr/deviceplutosdrshared.cpp
+++ b/devices/plutosdr/deviceplutosdrshared.cpp
@@ -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;
diff --git a/devices/plutosdr/deviceplutosdrshared.h b/devices/plutosdr/deviceplutosdrshared.h
index 14bd6b452..c01d89d0b 100644
--- a/devices/plutosdr/deviceplutosdrshared.h
+++ b/devices/plutosdr/deviceplutosdrshared.h
@@ -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),
diff --git a/devices/soapysdr/devicesoapysdrshared.cpp b/devices/soapysdr/devicesoapysdrshared.cpp
index f34e92f07..a7a44bc30 100644
--- a/devices/soapysdr/devicesoapysdrshared.cpp
+++ b/devices/soapysdr/devicesoapysdrshared.cpp
@@ -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),
diff --git a/devices/soapysdr/devicesoapysdrshared.h b/devices/soapysdr/devicesoapysdrshared.h
index 72b48cfcb..0562b8a94 100644
--- a/devices/soapysdr/devicesoapysdrshared.h
+++ b/devices/soapysdr/devicesoapysdrshared.h
@@ -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;
};
diff --git a/devices/xtrx/devicextrxshared.cpp b/devices/xtrx/devicextrxshared.cpp
index 99ebbc3ab..87c5b910a 100644
--- a/devices/xtrx/devicextrxshared.cpp
+++ b/devices/xtrx/devicextrxshared.cpp
@@ -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),
diff --git a/devices/xtrx/devicextrxshared.h b/devices/xtrx/devicextrxshared.h
index 7486169cd..69ed80b44 100644
--- a/devices/xtrx/devicextrxshared.h
+++ b/devices/xtrx/devicextrxshared.h
@@ -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();
diff --git a/doc/model/SDRangelFlow.odg b/doc/model/SDRangelFlow.odg
index aa6ad094f..048f2dc46 100644
Binary files a/doc/model/SDRangelFlow.odg and b/doc/model/SDRangelFlow.odg differ
diff --git a/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodbaseband.cpp b/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodbaseband.cpp
new file mode 100644
index 000000000..c5931ec68
--- /dev/null
+++ b/plugins/channelmimo/beamsteeringcwmod/beamsteeringcwmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+
+#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& 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& 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& 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;
+}
\ No newline at end of file
diff --git a/plugins/channeltx/filesource/CMakeLists.txt b/plugins/channeltx/filesource/CMakeLists.txt
index 746cad56d..7268eab2c 100644
--- a/plugins/channeltx/filesource/CMakeLists.txt
+++ b/plugins/channeltx/filesource/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/filesource/filesource.cpp b/plugins/channeltx/filesource/filesource.cpp
index d6c972742..e2a88bc0b 100644
--- a/plugins/channeltx/filesource/filesource.cpp
+++ b/plugins/channeltx/filesource/filesource.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#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(&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(&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 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& 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());
+}
\ No newline at end of file
diff --git a/plugins/channeltx/filesource/filesource.h b/plugins/channeltx/filesource/filesource.h
index 70817aab6..2e6edc469 100644
--- a/plugins/channeltx/filesource/filesource.h
+++ b/plugins/channeltx/filesource/filesource.h
@@ -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 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();
diff --git a/plugins/channeltx/filesource/filesourcebaseband.cpp b/plugins/channeltx/filesource/filesourcebaseband.cpp
new file mode 100644
index 000000000..08fbed0a8
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcebaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/filesource/filesourcebaseband.h b/plugins/channeltx/filesource/filesourcebaseband.h
new file mode 100644
index 000000000..fbc5160c4
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcebaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESOURCEBASEBAND_H
+#define INCLUDE_FILESOURCEBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/filesource/filesourcegui.cpp b/plugins/channeltx/filesource/filesourcegui.cpp
index c7390bbba..f13b01d6f 100644
--- a/plugins/channeltx/filesource/filesourcegui.cpp
+++ b/plugins/channeltx/filesource/filesourcegui.cpp
@@ -19,8 +19,6 @@
#include
#include
-#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()
diff --git a/plugins/channeltx/filesource/filesourcegui.h b/plugins/channeltx/filesource/filesourcegui.h
index 710f6709d..27a2c723a 100644
--- a/plugins/channeltx/filesource/filesourcegui.h
+++ b/plugins/channeltx/filesource/filesourcegui.h
@@ -86,7 +86,6 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
- void applyChannelSettings();
void configureFileName();
void updateWithAcquisition();
void updateWithStreamData();
diff --git a/plugins/channeltx/filesource/filesourceplugin.cpp b/plugins/channeltx/filesource/filesourceplugin.cpp
index 8ae86bb42..cd6b94013 100644
--- a/plugins/channeltx/filesource/filesourceplugin.cpp
+++ b/plugins/channeltx/filesource/filesourceplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/filesource/filesourcereport.cpp b/plugins/channeltx/filesource/filesourcereport.cpp
new file mode 100644
index 000000000..734505186
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcereport.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#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()
+{}
\ No newline at end of file
diff --git a/plugins/channeltx/filesource/filesourcereport.h b/plugins/channeltx/filesource/filesourcereport.h
new file mode 100644
index 000000000..c63f2dcf3
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcereport.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEREPORT_H_
+#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCEREPORT_H_
+
+#include
+#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_
\ No newline at end of file
diff --git a/plugins/channeltx/filesource/filesourcesource.cpp b/plugins/channeltx/filesource/filesourcesource.cpp
new file mode 100644
index 000000000..0263cf2cf
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcesource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "filesourcesource.h"
+#include "filesourcereport.h"
+
+#if (defined _WIN32_) || (defined _MSC_VER)
+#include "windows_time.h"
+#include
+#else
+#include
+#include
+#endif
+
+#include
+
+#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(&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(&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;
+}
diff --git a/plugins/channeltx/filesource/filesourcesource.h b/plugins/channeltx/filesource/filesourcesource.h
new file mode 100644
index 000000000..4ad7720ea
--- /dev/null
+++ b/plugins/channeltx/filesource/filesourcesource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESOURCE_H_
+#define PLUGINS_CHANNELTX_FILESOURCE_FILESOURCESOURCE_H_
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#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 m_movingAverage;
+
+ MessageQueue *m_guiMessageQueue;
+
+ void handleEOF();
+ MessageQueue *getMessageQueueToGUI() { return m_guiMessageQueue; }
+};
+
+#endif // PLUGINS_CHANNELTX_FILESOURCE_FILESOURCE_H_
diff --git a/plugins/channeltx/localsource/CMakeLists.txt b/plugins/channeltx/localsource/CMakeLists.txt
index 176c14f60..7e25980de 100644
--- a/plugins/channeltx/localsource/CMakeLists.txt
+++ b/plugins/channeltx/localsource/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/localsource/localsource.cpp b/plugins/channeltx/localsource/localsource.cpp
index c9993e80b..7f6802d2e 100644
--- a/plugins/channeltx/localsource/localsource.cpp
+++ b/plugins/channeltx/localsource/localsource.cpp
@@ -17,18 +17,14 @@
#include "localsource.h"
-#include
-#include
-
#include
#include
#include
+#include
#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& 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<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 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& 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();
}
diff --git a/plugins/channeltx/localsource/localsource.h b/plugins/channeltx/localsource/localsource.h
index afa2c05f0..ecaeb3dba 100644
--- a/plugins/channeltx/localsource/localsource.h
+++ b/plugins/channeltx/localsource/localsource.h
@@ -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& 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& channelSettingsKeys, const LocalSourceSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
- void processSamples(int offset);
};
#endif /* INCLUDE_LOCALSOURCE_H_ */
diff --git a/plugins/channeltx/localsource/localsource.pro b/plugins/channeltx/localsource/localsource.pro
deleted file mode 100644
index 33d25f77b..000000000
--- a/plugins/channeltx/localsource/localsource.pro
+++ /dev/null
@@ -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
diff --git a/plugins/channeltx/localsource/localsourcebaseband.cpp b/plugins/channeltx/localsource/localsourcebaseband.cpp
new file mode 100644
index 000000000..0a88334a9
--- /dev/null
+++ b/plugins/channeltx/localsource/localsourcebaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
diff --git a/plugins/channeltx/localsource/localsourcebaseband.h b/plugins/channeltx/localsource/localsourcebaseband.h
new file mode 100644
index 000000000..79f30dcbd
--- /dev/null
+++ b/plugins/channeltx/localsource/localsourcebaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESOURCEBASEBAND_H
+#define INCLUDE_FILESOURCEBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/localsource/localsourcegui.cpp b/plugins/channeltx/localsource/localsourcegui.cpp
index 31cf92009..2995c788b 100644
--- a/plugins/channeltx/localsource/localsourcegui.cpp
+++ b/plugins/channeltx/localsource/localsourcegui.cpp
@@ -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<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()
diff --git a/plugins/channeltx/localsource/localsourcegui.h b/plugins/channeltx/localsource/localsourcegui.h
index 789160e2a..804d342f7 100644
--- a/plugins/channeltx/localsource/localsourcegui.h
+++ b/plugins/channeltx/localsource/localsourcegui.h
@@ -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();
diff --git a/plugins/channeltx/localsource/localsourcegui.ui b/plugins/channeltx/localsource/localsourcegui.ui
index 6224aabaf..9ca20eccf 100644
--- a/plugins/channeltx/localsource/localsourcegui.ui
+++ b/plugins/channeltx/localsource/localsourcegui.ui
@@ -298,6 +298,21 @@
+ -
+
+
+ Start/Stop source
+
+
+
+
+
+
+ :/play.png
+ :/pause.png:/play.png
+
+
+
-
@@ -323,6 +338,11 @@
1
+
+ ButtonSwitch
+ QToolButton
+
+
diff --git a/plugins/channeltx/localsource/localsourceplugin.cpp b/plugins/channeltx/localsource/localsourceplugin.cpp
index c021d7c6d..5e2b579f0 100644
--- a/plugins/channeltx/localsource/localsourceplugin.cpp
+++ b/plugins/channeltx/localsource/localsourceplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/localsource/localsourcesettings.cpp b/plugins/channeltx/localsource/localsourcesettings.cpp
index ff6427ba4..e08419e0f 100644
--- a/plugins/channeltx/localsource/localsourcesettings.cpp
+++ b/plugins/channeltx/localsource/localsourcesettings.cpp
@@ -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;
diff --git a/plugins/channeltx/localsource/localsourcesettings.h b/plugins/channeltx/localsource/localsourcesettings.h
index c883f9f26..f9ab4c45a 100644
--- a/plugins/channeltx/localsource/localsourcesettings.h
+++ b/plugins/channeltx/localsource/localsourcesettings.h
@@ -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;
diff --git a/plugins/channeltx/localsource/localsourcesource.cpp b/plugins/channeltx/localsource/localsourcesource.cpp
new file mode 100644
index 000000000..d721845fe
--- /dev/null
+++ b/plugins/channeltx/localsource/localsourcesource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#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
+ );
+ }
+}
diff --git a/plugins/channeltx/localsource/localsourcesource.h b/plugins/channeltx/localsource/localsourcesource.h
new file mode 100644
index 000000000..a42440cdc
--- /dev/null
+++ b/plugins/channeltx/localsource/localsourcesource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELTX_LOCALSOURCE_LOCALSOURCESOURCE_H_
+#define PLUGINS_CHANNELTX_LOCALSOURCE_LOCALSOURCESOURCE_H_
+
+#include
+
+#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_
diff --git a/plugins/channeltx/localsource/localsourcethread.cpp b/plugins/channeltx/localsource/localsourcethread.cpp
index d16210c71..195c85b51 100644
--- a/plugins/channeltx/localsource/localsourcethread.cpp
+++ b/plugins/channeltx/localsource/localsourcethread.cpp
@@ -15,7 +15,7 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#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()
diff --git a/plugins/channeltx/localsource/localsourcethread.h b/plugins/channeltx/localsource/localsourcethread.h
index f59240ae0..ff5a7b54e 100644
--- a/plugins/channeltx/localsource/localsourcethread.h
+++ b/plugins/channeltx/localsource/localsourcethread.h
@@ -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;
diff --git a/plugins/channeltx/modam/CMakeLists.txt b/plugins/channeltx/modam/CMakeLists.txt
index c7a0efd5b..5634eefd9 100644
--- a/plugins/channeltx/modam/CMakeLists.txt
+++ b/plugins/channeltx/modam/CMakeLists.txt
@@ -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
)
diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp
index 369cfd048..689019fc9 100644
--- a/plugins/channeltx/modam/ammod.cpp
+++ b/plugins/channeltx/modam/ammod.cpp
@@ -24,21 +24,22 @@
#include
#include
#include
+#include
#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(&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(&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& channelSettingsKeys, const AMModSettings& settings, bool force)
@@ -926,10 +591,10 @@ void AMMod::webapiReverseSendSettings(QList& 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& 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)));
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modam/ammod.h b/plugins/channeltx/modam/ammod.h
index d83f0441c..b0e60c28a 100644
--- a/plugins/channeltx/modam/ammod.h
+++ b/plugins/channeltx/modam/ammod.h
@@ -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 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);
diff --git a/plugins/channeltx/modam/ammodbaseband.cpp b/plugins/channeltx/modam/ammodbaseband.cpp
new file mode 100644
index 000000000..1a3b6c458
--- /dev/null
+++ b/plugins/channeltx/modam/ammodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modam/ammodbaseband.h b/plugins/channeltx/modam/ammodbaseband.h
new file mode 100644
index 000000000..9a391cb39
--- /dev/null
+++ b/plugins/channeltx/modam/ammodbaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_AMMODBASEBAND_H
+#define INCLUDE_AMMODBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/modam/ammodgui.cpp b/plugins/channeltx/modam/ammodgui.cpp
index 440cadc99..13b9981d4 100644
--- a/plugins/channeltx/modam/ammodgui.cpp
+++ b/plugins/channeltx/modam/ammodgui.cpp
@@ -22,12 +22,12 @@
#include
#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);
diff --git a/plugins/channeltx/modam/ammodplugin.cpp b/plugins/channeltx/modam/ammodplugin.cpp
index 7f73a29df..94326f4ef 100644
--- a/plugins/channeltx/modam/ammodplugin.cpp
+++ b/plugins/channeltx/modam/ammodplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/modam/ammodsource.cpp b/plugins/channeltx/modam/ammodsource.cpp
new file mode 100644
index 000000000..7d0610887
--- /dev/null
+++ b/plugins/channeltx/modam/ammodsource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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(&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(&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;
+}
diff --git a/plugins/channeltx/modam/ammodsource.h b/plugins/channeltx/modam/ammodsource.h
new file mode 100644
index 000000000..bf0f5472d
--- /dev/null
+++ b/plugins/channeltx/modam/ammodsource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_AMMODSOURCE_H
+#define INCLUDE_AMMODSOURCE_H
+
+#include
+
+#include
+#include
+
+#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 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
diff --git a/plugins/channeltx/modam/ammodwebapiadapter.cpp b/plugins/channeltx/modam/ammodwebapiadapter.cpp
index 0a954cb85..e6a6a8443 100644
--- a/plugins/channeltx/modam/ammodwebapiadapter.cpp
+++ b/plugins/channeltx/modam/ammodwebapiadapter.cpp
@@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
+#include "dsp/cwkeyer.h"
#include "ammod.h"
#include "ammodwebapiadapter.h"
diff --git a/plugins/channeltx/modatv/CMakeLists.txt b/plugins/channeltx/modatv/CMakeLists.txt
index 0c1eab90e..876314997 100644
--- a/plugins/channeltx/modatv/CMakeLists.txt
+++ b/plugins/channeltx/modatv/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp
index 8892fcc21..846079aca 100644
--- a/plugins/channeltx/modatv/atvmod.cpp
+++ b/plugins/channeltx/modatv/atvmod.cpp
@@ -1,5 +1,5 @@
///////////////////////////////////////////////////////////////////////////////////
-// Copyright (C) 2017 Edouard Griffiths, F4EXB //
+// Copyright (C) 2016 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 //
@@ -15,95 +15,56 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#include
+#include
+#include
+#include
#include
+#include
#include
#include
#include
+#include
#include "SWGChannelSettings.h"
#include "SWGChannelReport.h"
#include "SWGATVModReport.h"
-#include "opencv2/imgproc/imgproc.hpp"
-
-#include "dsp/upchannelizer.h"
-#include "dsp/threadedbasebandsamplesource.h"
+#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
+#include "dsp/devicesamplemimo.h"
#include "device/deviceapi.h"
#include "util/db.h"
+#include "atvmodbaseband.h"
#include "atvmod.h"
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureATVMod, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureChannelizer, Message)
+MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureSourceCenterFrequency, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureImageFileName, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureVideoFileName, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureVideoFileSourceSeek, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureVideoFileSourceStreamTiming, Message)
-MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportVideoFileSourceStreamTiming, Message)
-MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportVideoFileSourceStreamData, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraIndex, Message)
MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureCameraData, Message)
-MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportCameraData, Message)
-MESSAGE_CLASS_DEFINITION(ATVMod::MsgReportEffectiveSampleRate, Message)
const QString ATVMod::m_channelIdURI = "sdrangel.channeltx.modatv";
const QString ATVMod::m_channelId = "ATVMod";
-const float ATVMod::m_blackLevel = 0.3f;
-const float ATVMod::m_spanLevel = 0.7f;
-const int ATVMod::m_levelNbSamples = 10000; // every 10ms
-const int ATVMod::m_nbBars = 6;
-const int ATVMod::m_cameraFPSTestNbFrames = 100;
-const int ATVMod::m_ssbFftLen = 1024;
ATVMod::ATVMod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
- m_deviceAPI(deviceAPI),
- m_outputSampleRate(1000000),
- m_inputFrequencyOffset(0),
- m_modPhasor(0.0f),
- m_tvSampleRate(1000000),
- m_evenImage(true),
- m_settingsMutex(QMutex::Recursive),
- m_horizontalCount(0),
- m_lineCount(0),
- m_imageOK(false),
- m_videoFPSq(1.0f),
- m_videoFPSCount(0.0f),
- m_videoPrevFPSCount(0),
- m_videoEOF(false),
- m_videoOK(false),
- m_cameraIndex(-1),
- //m_showOverlayText(false),
- m_SSBFilter(0),
- m_SSBFilterBuffer(0),
- m_SSBFilterBufferIndex(0),
- m_DSBFilter(0),
- m_DSBFilterBuffer(0),
- m_DSBFilterBufferIndex(0)
+ m_deviceAPI(deviceAPI)
{
- setObjectName(m_channelId);
- scanCameras();
+ setObjectName(m_channelId);
- m_SSBFilter = new fftfilt(0, m_settings.m_rfBandwidth / m_outputSampleRate, m_ssbFftLen);
- m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
- memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
+ m_thread = new QThread(this);
+ m_basebandSource = new ATVModBaseband();
+ m_basebandSource->moveToThread(m_thread);
- m_DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_outputSampleRate, 2 * m_ssbFftLen);
- m_DSBFilterBuffer = new Complex[m_ssbFftLen];
- memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
+ applySettings(m_settings, true);
- m_interpolatorDistanceRemain = 0.0f;
- m_interpolatorDistance = 1.0f;
-
- applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true);
- applySettings(m_settings, true); // does applyStandard() too;
-
- 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();
@@ -114,431 +75,60 @@ ATVMod::~ATVMod()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
-
- if (m_video.isOpened()) {
- m_video.release();
- }
-
- releaseCameras();
- 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;
+ m_deviceAPI->removeChannelSourceAPI(this);
+ m_deviceAPI->removeChannelSource(this);
+ delete m_basebandSource;
+ delete m_thread;
}
-void ATVMod::pullAudio(int nbSamples)
+uint32_t ATVMod::getNumberOfDeviceStreams() const
{
- (void) nbSamples;
-}
-
-void ATVMod::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_tvSampleRate == m_outputSampleRate) && (!m_settings.m_forceDecimator)) // no interpolation nor decimation
- {
- modulateSample();
- pullFinalize(m_modSample, sample);
- }
- else
- {
- 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;
- pullFinalize(ci, sample);
- }
-}
-
-void ATVMod::pullFinalize(Complex& ci, Sample& sample)
-{
- 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);
-
- sample.m_real = (FixReal) ci.real();
- sample.m_imag = (FixReal) ci.imag();
-}
-
-void ATVMod::modulateSample()
-{
- Real t;
-
- pullVideo(t);
- calculateLevel(t);
-
- t = m_settings.m_invertedVideo ? 1.0f - t : t;
-
- switch (m_settings.m_atvModulation)
- {
- case ATVModSettings::ATVModulationFM: // FM half bandwidth deviation
- m_modPhasor += (t - 0.5f) * m_settings.m_fmExcursion * 2.0f * M_PI;
- if (m_modPhasor > 2.0f * M_PI) m_modPhasor -= 2.0f * M_PI; // limit growth
- if (m_modPhasor < 2.0f * M_PI) m_modPhasor += 2.0f * M_PI; // limit growth
- m_modSample.real(cos(m_modPhasor) * m_settings.m_rfScalingFactor); // -1 dB
- m_modSample.imag(sin(m_modPhasor) * m_settings.m_rfScalingFactor);
- break;
- case ATVModSettings::ATVModulationLSB:
- case ATVModSettings::ATVModulationUSB:
- m_modSample = modulateSSB(t);
- m_modSample *= m_settings.m_rfScalingFactor;
- break;
- case ATVModSettings::ATVModulationVestigialLSB:
- case ATVModSettings::ATVModulationVestigialUSB:
- m_modSample = modulateVestigialSSB(t);
- m_modSample *= m_settings.m_rfScalingFactor;
- break;
- case ATVModSettings::ATVModulationAM: // AM 90%
- default:
- m_modSample.real((t*1.8f + 0.1f) * (m_settings.m_rfScalingFactor/2.0f)); // modulate and scale zero frequency carrier
- m_modSample.imag(0.0f);
- }
-}
-
-Complex& ATVMod::modulateSSB(Real& sample)
-{
- int n_out;
- Complex ci(sample, 0.0f);
- fftfilt::cmplx *filtered;
-
- n_out = m_SSBFilter->runSSB(ci, &filtered, m_settings.m_atvModulation == ATVModSettings::ATVModulationUSB);
-
- if (n_out > 0)
- {
- memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
- m_SSBFilterBufferIndex = 0;
- }
-
- m_SSBFilterBufferIndex++;
-
- return m_SSBFilterBuffer[m_SSBFilterBufferIndex-1];
-}
-
-Complex& ATVMod::modulateVestigialSSB(Real& sample)
-{
- int n_out;
- Complex ci(sample, 0.0f);
- fftfilt::cmplx *filtered;
-
- n_out = m_DSBFilter->runAsym(ci, &filtered, m_settings.m_atvModulation == ATVModSettings::ATVModulationVestigialUSB);
-
- if (n_out > 0)
- {
- memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
- m_DSBFilterBufferIndex = 0;
- }
-
- m_DSBFilterBufferIndex++;
-
- return m_DSBFilterBuffer[m_DSBFilterBufferIndex-1];
-}
-
-void ATVMod::pullVideo(Real& sample)
-{
- if ((m_settings.m_atvStd == ATVModSettings::ATVStdHSkip) && (m_lineCount == m_nbLines2)) // last line in skip mode
- {
- pullImageLine(sample, true); // pull image line without sync
- }
- else if (m_lineCount < m_nbLines2 + 1) // even image or non interlaced
- {
- int iLine = m_lineCount;
-
- if (iLine < m_nbSyncLinesHeadE + m_nbBlankLines)
- {
- pullVSyncLine(sample);
- }
- else if (iLine > m_nbLines2 - m_nbSyncLinesBottom)
- {
- pullVSyncLine(sample);
- }
- else
- {
- pullImageLine(sample);
- }
- }
- else // odd image
- {
- int iLine = m_lineCount - m_nbLines2 - 1;
-
- if (iLine < m_nbSyncLinesHeadO + m_nbBlankLines)
- {
- pullVSyncLine(sample);
- }
- else if (iLine > m_nbLines2 - 1 - m_nbSyncLinesBottom)
- {
- pullVSyncLine(sample);
- }
- else
- {
- pullImageLine(sample);
- }
- }
-
- if (m_horizontalCount < m_nbHorizPoints - 1)
- {
- m_horizontalCount++;
- }
- else
- {
- if (m_lineCount < m_nbLines - 1)
- {
- m_lineCount++;
- if (m_lineCount > (m_nbLines/2)) m_evenImage = !m_evenImage;
- }
- else // new image
- {
- m_lineCount = 0;
- m_evenImage = !m_evenImage;
-
- if ((m_settings.m_atvModInput == ATVModSettings::ATVModInputVideo) && m_videoOK && (m_settings.m_videoPlay) && !m_videoEOF)
- {
- int grabOK = 0;
- int fpsIncrement = (int) m_videoFPSCount - m_videoPrevFPSCount;
-
- // move a number of frames according to increment
- // use grab to test for EOF then retrieve to preserve last valid frame as the current original frame
- // TODO: handle pause (no move)
- for (int i = 0; i < fpsIncrement; i++)
- {
- grabOK = m_video.grab();
- if (!grabOK) break;
- }
-
- if (grabOK)
- {
- cv::Mat colorFrame;
- m_video.retrieve(colorFrame);
-
- if (!colorFrame.empty()) // some frames may not come out properly
- {
- if (m_settings.m_showOverlayText) {
- mixImageAndText(colorFrame);
- }
-
- cv::cvtColor(colorFrame, m_videoframeOriginal, CV_BGR2GRAY);
- resizeVideo();
- }
- }
- else
- {
- if (m_settings.m_videoPlayLoop) { // play loop
- seekVideoFileStream(0);
- } else { // stops
- m_videoEOF = true;
- }
- }
-
- if (m_videoFPSCount < m_videoFPS)
- {
- m_videoPrevFPSCount = (int) m_videoFPSCount;
- m_videoFPSCount += m_videoFPSq;
- }
- else
- {
- m_videoPrevFPSCount = 0;
- m_videoFPSCount = m_videoFPSq;
- }
- }
- else if ((m_settings.m_atvModInput == ATVModSettings::ATVModInputCamera) && (m_settings.m_cameraPlay))
- {
- ATVCamera& camera = m_cameras[m_cameraIndex]; // currently selected canera
-
- if (camera.m_videoFPS < 0.0f) // default frame rate when it could not be obtained via get
- {
- time_t start, end;
- cv::Mat frame;
-
- if (getMessageQueueToGUI())
- {
- MsgReportCameraData *report;
- report = MsgReportCameraData::create(
- camera.m_cameraNumber,
- 0.0f,
- camera.m_videoFPSManual,
- camera.m_videoFPSManualEnable,
- camera.m_videoWidth,
- camera.m_videoHeight,
- 1); // open splash screen on GUI side
- getMessageQueueToGUI()->push(report);
- }
-
- int nbFrames = 0;
-
- time(&start);
-
- for (int i = 0; i < m_cameraFPSTestNbFrames; i++)
- {
- camera.m_camera >> frame;
- if (!frame.empty()) nbFrames++;
- }
-
- time(&end);
-
- double seconds = difftime (end, start);
- // take a 10% guard and divide bandwidth between all cameras as a hideous hack
- camera.m_videoFPS = ((nbFrames / seconds) * 0.9) / m_cameras.size();
- camera.m_videoFPSq = camera.m_videoFPS / m_fps;
- camera.m_videoFPSCount = camera.m_videoFPSq;
- camera.m_videoPrevFPSCount = 0;
-
- if (getMessageQueueToGUI())
- {
- MsgReportCameraData *report;
- report = MsgReportCameraData::create(
- camera.m_cameraNumber,
- camera.m_videoFPS,
- camera.m_videoFPSManual,
- camera.m_videoFPSManualEnable,
- camera.m_videoWidth,
- camera.m_videoHeight,
- 2); // close splash screen on GUI side
- getMessageQueueToGUI()->push(report);
- }
- }
- else if (camera.m_videoFPS == 0.0f) // Hideous hack for windows
- {
- camera.m_videoFPS = 5.0f;
- camera.m_videoFPSq = camera.m_videoFPS / m_fps;
- camera.m_videoFPSCount = camera.m_videoFPSq;
- camera.m_videoPrevFPSCount = 0;
-
- if (getMessageQueueToGUI())
- {
- MsgReportCameraData *report;
- report = MsgReportCameraData::create(
- camera.m_cameraNumber,
- camera.m_videoFPS,
- camera.m_videoFPSManual,
- camera.m_videoFPSManualEnable,
- camera.m_videoWidth,
- camera.m_videoHeight,
- 0);
- getMessageQueueToGUI()->push(report);
- }
- }
-
- int fpsIncrement = (int) camera.m_videoFPSCount - camera.m_videoPrevFPSCount;
-
- // move a number of frames according to increment
- // use grab to test for EOF then retrieve to preserve last valid frame as the current original frame
- cv::Mat colorFrame;
-
- for (int i = 0; i < fpsIncrement; i++)
- {
- camera.m_camera >> colorFrame;
- if (colorFrame.empty()) break;
- }
-
- if (!colorFrame.empty()) // some frames may not come out properly
- {
- if (m_settings.m_showOverlayText) {
- mixImageAndText(colorFrame);
- }
-
- cv::cvtColor(colorFrame, camera.m_videoframeOriginal, CV_BGR2GRAY);
- resizeCamera();
- }
-
- if (camera.m_videoFPSCount < (camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS))
- {
- camera.m_videoPrevFPSCount = (int) camera.m_videoFPSCount;
- camera.m_videoFPSCount += (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq);
- }
- else
- {
- camera.m_videoPrevFPSCount = 0;
- camera.m_videoFPSCount = (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq);
- }
- }
- }
-
- m_horizontalCount = 0;
- }
-}
-
-void ATVMod::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 = std::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;
- }
+ return m_deviceAPI->getNbSinkStreams();
}
void ATVMod::start()
{
- qDebug() << "ATVMod::start: m_outputSampleRate: " << m_outputSampleRate
- << " m_inputFrequencyOffset: " << m_settings.m_inputFrequencyOffset;
- applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true);
+ qDebug("ATVMod::start");
+ m_basebandSource->reset();
+ m_thread->start();
}
void ATVMod::stop()
{
+ qDebug("ATVMod::stop");
+ m_thread->exit();
+ m_thread->wait();
+}
+
+void ATVMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
+{
+ m_basebandSource->pull(begin, nbSamples);
}
bool ATVMod::handleMessage(const Message& cmd)
{
- if (UpChannelizer::MsgChannelizerNotification::match(cmd))
+ if (MsgConfigureChannelizer::match(cmd))
{
- UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
- qDebug() << "ATVMod::handleMessage: MsgChannelizerNotification:"
- << " outputSampleRate: " << notif.getSampleRate()
- << " inputFrequencyOffset: " << notif.getFrequencyOffset();
+ MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
+ qDebug() << "ATVMod::handleMessage: MsgConfigureChannelizer:"
+ << " getSourceSampleRate: " << cfg.getSourceSampleRate()
+ << " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
- applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
+ ATVModBaseband::MsgConfigureChannelizer *msg
+ = ATVModBaseband::MsgConfigureChannelizer::create(cfg.getSourceSampleRate(), cfg.getSourceCenterFrequency());
+ m_basebandSource->getInputMessageQueue()->push(msg);
return true;
}
- else if (MsgConfigureChannelizer::match(cmd))
+ else if (MsgConfigureSourceCenterFrequency::match(cmd))
{
- MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
- qDebug() << "SSBMod::handleMessage: MsgConfigureChannelizer: sampleRate: " << m_channelizer->getOutputSampleRate()
- << " centerFrequency: " << cfg.getCenterFrequency();
+ MsgConfigureSourceCenterFrequency& cfg = (MsgConfigureSourceCenterFrequency&) cmd;
+ qDebug() << "ATVMod::handleMessage: MsgConfigureSourceCenterFrequency:"
+ << " getSourceCenterFrequency: " << cfg.getSourceCenterFrequency();
- m_channelizer->configure(m_channelizer->getInputMessageQueue(),
- m_channelizer->getOutputSampleRate(),
- cfg.getCenterFrequency());
+ ATVModBaseband::MsgConfigureChannelizer *msg
+ = ATVModBaseband::MsgConfigureChannelizer::create(m_basebandSource->getChannelSampleRate(), cfg.getSourceCenterFrequency());
+ m_basebandSource->getInputMessageQueue()->push(msg);
return true;
}
@@ -551,538 +141,72 @@ bool ATVMod::handleMessage(const Message& cmd)
return true;
}
+ else if (DSPSignalNotification::match(cmd))
+ {
+ // Forward to the source
+ DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
+ DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
+ qDebug() << "ATVMod::handleMessage: DSPSignalNotification";
+ m_basebandSource->getInputMessageQueue()->push(rep);
+
+ return true;
+ }
else if (MsgConfigureImageFileName::match(cmd))
{
- MsgConfigureImageFileName& conf = (MsgConfigureImageFileName&) cmd;
- openImage(conf.getFileName());
+ MsgConfigureImageFileName& cfg = (MsgConfigureImageFileName&) cmd;
+ ATVModBaseband::MsgConfigureImageFileName *msg = ATVModBaseband::MsgConfigureImageFileName::create(
+ cfg.getFileName());
+ m_basebandSource->getInputMessageQueue()->push(msg);
+
return true;
}
else if (MsgConfigureVideoFileName::match(cmd))
{
- MsgConfigureVideoFileName& conf = (MsgConfigureVideoFileName&) cmd;
- openVideo(conf.getFileName());
+ MsgConfigureVideoFileName& cfg = (MsgConfigureVideoFileName&) cmd;
+ ATVModBaseband::MsgConfigureVideoFileName *msg = ATVModBaseband::MsgConfigureVideoFileName::create(
+ cfg.getFileName());
+ m_basebandSource->getInputMessageQueue()->push(msg);
+
return true;
}
else if (MsgConfigureVideoFileSourceSeek::match(cmd))
{
- MsgConfigureVideoFileSourceSeek& conf = (MsgConfigureVideoFileSourceSeek&) cmd;
- int seekPercentage = conf.getPercentage();
- seekVideoFileStream(seekPercentage);
+ MsgConfigureVideoFileSourceSeek& cfg = (MsgConfigureVideoFileSourceSeek&) cmd;
+ ATVModBaseband::MsgConfigureVideoFileSourceSeek *rep = ATVModBaseband::MsgConfigureVideoFileSourceSeek::create(cfg.getPercentage());
+ m_basebandSource->getInputMessageQueue()->push(rep);
+
return true;
}
else if (MsgConfigureVideoFileSourceStreamTiming::match(cmd))
{
- int framesCount;
-
- if (m_videoOK && m_video.isOpened())
- {
- framesCount = m_video.get(CV_CAP_PROP_POS_FRAMES);;
- } else {
- framesCount = 0;
- }
-
- if (getMessageQueueToGUI())
- {
- MsgReportVideoFileSourceStreamTiming *report;
- report = MsgReportVideoFileSourceStreamTiming::create(framesCount);
- getMessageQueueToGUI()->push(report);
- }
+ ATVModBaseband::MsgConfigureVideoFileSourceStreamTiming *rep = ATVModBaseband::MsgConfigureVideoFileSourceStreamTiming::create();
+ m_basebandSource->getInputMessageQueue()->push(rep);
return true;
}
else if (MsgConfigureCameraIndex::match(cmd))
{
- MsgConfigureCameraIndex& cfg = (MsgConfigureCameraIndex&) cmd;
- uint32_t index = cfg.getIndex() & 0x7FFFFFF;
+ MsgConfigureCameraIndex& cfg = (MsgConfigureCameraIndex&) cmd;
+ ATVModBaseband::MsgConfigureCameraIndex *rep = ATVModBaseband::MsgConfigureCameraIndex::create(cfg.getIndex());
+ m_basebandSource->getInputMessageQueue()->push(rep);
- if (index < m_cameras.size())
- {
- m_cameraIndex = index;
-
- if (getMessageQueueToGUI())
- {
- MsgReportCameraData *report;
- report = MsgReportCameraData::create(
- m_cameras[m_cameraIndex].m_cameraNumber,
- m_cameras[m_cameraIndex].m_videoFPS,
- m_cameras[m_cameraIndex].m_videoFPSManual,
- m_cameras[m_cameraIndex].m_videoFPSManualEnable,
- m_cameras[m_cameraIndex].m_videoWidth,
- m_cameras[m_cameraIndex].m_videoHeight,
- 0);
- getMessageQueueToGUI()->push(report);
- }
- }
-
- return true;
+ return true;
}
else if (MsgConfigureCameraData::match(cmd))
{
- MsgConfigureCameraData& cfg = (MsgConfigureCameraData&) cmd;
- uint32_t index = cfg.getIndex() & 0x7FFFFFF;
- float mnaualFPS = cfg.getManualFPS();
- bool manualFPSEnable = cfg.getManualFPSEnable();
+ MsgConfigureCameraData& cfg = (MsgConfigureCameraData&) cmd;
+ ATVModBaseband::MsgConfigureCameraData *rep = ATVModBaseband::MsgConfigureCameraData::create(
+ cfg.getIndex(), cfg.getManualFPS(), cfg.getManualFPSEnable()
+ );
- if (index < m_cameras.size())
- {
- m_cameras[index].m_videoFPSManual = mnaualFPS;
- m_cameras[index].m_videoFPSManualEnable = manualFPSEnable;
- }
-
- return true;
- }
- else if (DSPSignalNotification::match(cmd))
- {
return true;
}
- else
- {
- return false;
- }
-}
-
-void ATVMod::getBaseValues(int outputSampleRate, int linesPerSecond, int& sampleRateUnits, uint32_t& nbPointsPerRateUnit)
-{
- int maxPoints = outputSampleRate / linesPerSecond;
- int i = maxPoints;
-
- for (; i > 0; i--)
- {
- if ((i * linesPerSecond) % 10 == 0)
- break;
- }
-
- nbPointsPerRateUnit = i == 0 ? maxPoints : i;
- sampleRateUnits = nbPointsPerRateUnit * linesPerSecond;
-}
-
-float ATVMod::getRFBandwidthDivisor(ATVModSettings::ATVModulation modulation)
-{
- switch(modulation)
- {
- case ATVModSettings::ATVModulationLSB:
- case ATVModSettings::ATVModulationUSB:
- case ATVModSettings::ATVModulationVestigialLSB:
- case ATVModSettings::ATVModulationVestigialUSB:
- return 1.05f;
- break;
- case ATVModSettings::ATVModulationAM:
- case ATVModSettings::ATVModulationFM:
- default:
- return 2.2f;
- }
-}
-
-void ATVMod::applyStandard()
-{
- m_pointsPerSync = (uint32_t) ((4.7f / 64.0f) * m_pointsPerLine);
- m_pointsPerBP = (uint32_t) ((4.7f / 64.0f) * m_pointsPerLine);
- m_pointsPerFP = (uint32_t) ((2.6f / 64.0f) * m_pointsPerLine);
- m_pointsPerFSync = (uint32_t) ((2.3f / 64.0f) * m_pointsPerLine);
-
- m_pointsPerImgLine = m_pointsPerLine - m_pointsPerSync - m_pointsPerBP - m_pointsPerFP;
- m_nbHorizPoints = m_pointsPerLine;
-
- m_pointsPerHBar = m_pointsPerImgLine / m_nbBars;
- m_hBarIncrement = m_spanLevel / (float) m_nbBars;
- m_vBarIncrement = m_spanLevel / (float) m_nbBars;
-
- m_nbLines = m_settings.m_nbLines;
- m_nbLines2 = m_nbLines / 2;
- m_fps = m_settings.m_fps * 1.0f;
-
-// qDebug() << "ATVMod::applyStandard: "
-// << " m_nbLines: " << m_config.m_nbLines
-// << " m_fps: " << m_config.m_fps
-// << " rateUnits: " << rateUnits
-// << " nbPointsPerRateUnit: " << nbPointsPerRateUnit
-// << " m_tvSampleRate: " << m_tvSampleRate
-// << " m_pointsPerTU: " << m_pointsPerTU;
-
- switch(m_settings.m_atvStd)
- {
- case ATVModSettings::ATVStdHSkip:
- m_nbImageLines = m_nbLines; // lines less the total number of sync lines
- m_nbImageLines2 = m_nbImageLines; // force non interleaved for vbars
- m_interleaved = false;
- m_nbSyncLinesHeadE = 0; // number of sync lines on the top of a frame even
- m_nbSyncLinesHeadO = 0; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = -1; // force no vsync in even block
- m_nbLongSyncLines = 0;
- m_nbHalfLongSync = 0;
- m_nbWholeEqLines = 0;
- m_singleLongSync = true;
- m_nbBlankLines = 0;
- m_blankLineLvel = 0.7f;
- m_nbLines2 = m_nbLines - 1;
- break;
- case ATVModSettings::ATVStdShort:
- m_nbImageLines = m_nbLines - 2; // lines less the total number of sync lines
- m_nbImageLines2 = m_nbImageLines; // force non interleaved for vbars
- m_interleaved = false;
- m_nbSyncLinesHeadE = 1; // number of sync lines on the top of a frame even
- m_nbSyncLinesHeadO = 1; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = 0;
- m_nbLongSyncLines = 1;
- m_nbHalfLongSync = 0;
- m_nbWholeEqLines = 0;
- m_singleLongSync = true;
- m_nbBlankLines = 1;
- m_blankLineLvel = 0.7f;
- m_nbLines2 = m_nbLines; // force non interleaved => treated as even for all lines
- break;
- case ATVModSettings::ATVStdShortInterleaved:
- m_nbImageLines = m_nbLines - 2; // lines less the total number of sync lines
- m_nbImageLines2 = m_nbImageLines / 2;
- m_interleaved = true;
- m_nbSyncLinesHeadE = 1; // number of sync lines on the top of a frame even
- m_nbSyncLinesHeadO = 1; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = 0;
- m_nbLongSyncLines = 1;
- m_nbHalfLongSync = 0;
- m_nbWholeEqLines = 0;
- m_singleLongSync = true;
- m_nbBlankLines = 1;
- m_blankLineLvel = 0.7f;
- break;
- case ATVModSettings::ATVStd405: // Follows loosely the 405 lines standard
- m_nbImageLines = m_nbLines - 15; // lines less the total number of sync lines
- m_nbImageLines2 = m_nbImageLines / 2;
- m_interleaved = true;
- m_nbSyncLinesHeadE = 5; // number of sync lines on the top of a frame even
- m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = 3;
- m_nbLongSyncLines = 2;
- m_nbHalfLongSync = 1;
- m_nbWholeEqLines = 2;
- m_singleLongSync = false;
- m_nbBlankLines = 7; // yields 376 lines (195 - 7) * 2
- m_blankLineLvel = m_blackLevel;
- break;
- case ATVModSettings::ATVStdPAL525: // Follows PAL-M standard
- m_nbImageLines = m_nbLines - 15;
- m_nbImageLines2 = m_nbImageLines / 2;
- m_interleaved = true;
- m_nbSyncLinesHeadE = 5;
- m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = 3;
- m_nbLongSyncLines = 2;
- m_nbHalfLongSync = 1;
- m_nbWholeEqLines = 2;
- m_singleLongSync = false;
- m_nbBlankLines = 15; // yields 480 lines (255 - 15) * 2
- m_blankLineLvel = m_blackLevel;
- break;
- case ATVModSettings::ATVStdPAL625: // Follows PAL-B/G/H standard
- default:
- m_nbImageLines = m_nbLines - 15;
- m_nbImageLines2 = m_nbImageLines / 2;
- m_interleaved = true;
- m_nbSyncLinesHeadE = 5;
- m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
- m_nbSyncLinesBottom = 3;
- m_nbLongSyncLines = 2;
- m_nbHalfLongSync = 1;
- m_nbWholeEqLines = 2;
- m_singleLongSync = false;
- m_nbBlankLines = 17; // yields 576 lines (305 - 17) * 2
- m_blankLineLvel = m_blackLevel;
- }
-
- m_linesPerVBar = m_nbImageLines2 / m_nbBars;
-
- if (m_imageOK)
- {
- resizeImage();
- }
-
- if (m_videoOK)
- {
- calculateVideoSizes();
- resizeVideo();
- }
-
- calculateCamerasSizes();
-}
-
-void ATVMod::openImage(const QString& fileName)
-{
- m_imageFromFile = cv::imread(qPrintable(fileName), CV_LOAD_IMAGE_GRAYSCALE);
- m_imageOK = m_imageFromFile.data != 0;
-
- if (m_imageOK)
- {
- m_settings.m_imageFileName = fileName;
- m_imageFromFile.copyTo(m_imageOriginal);
-
- if (m_settings.m_showOverlayText) {
- mixImageAndText(m_imageOriginal);
- }
-
- resizeImage();
- }
else
{
- m_settings.m_imageFileName.clear();
- qDebug("ATVMod::openImage: cannot open image file %s", qPrintable(fileName));
+ return false;
}
}
-void ATVMod::openVideo(const QString& fileName)
-{
- //if (m_videoOK && m_video.isOpened()) m_video.release(); should be done by OpenCV in open method
-
- m_videoOK = m_video.open(qPrintable(fileName));
-
- if (m_videoOK)
- {
- m_settings.m_videoFileName = fileName;
- m_videoFPS = m_video.get(CV_CAP_PROP_FPS);
- m_videoWidth = (int) m_video.get(CV_CAP_PROP_FRAME_WIDTH);
- m_videoHeight = (int) m_video.get(CV_CAP_PROP_FRAME_HEIGHT);
- m_videoLength = (int) m_video.get(CV_CAP_PROP_FRAME_COUNT);
- int ex = static_cast(m_video.get(CV_CAP_PROP_FOURCC));
- char ext[] = {(char)(ex & 0XFF),(char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24),0};
-
- qDebug("ATVMod::openVideo: %s FPS: %f size: %d x %d #frames: %d codec: %s",
- m_video.isOpened() ? "OK" : "KO",
- m_videoFPS,
- m_videoWidth,
- m_videoHeight,
- m_videoLength,
- ext);
-
- calculateVideoSizes();
- m_videoEOF = false;
-
- if (getMessageQueueToGUI())
- {
- MsgReportVideoFileSourceStreamData *report;
- report = MsgReportVideoFileSourceStreamData::create(m_videoFPS, m_videoLength);
- getMessageQueueToGUI()->push(report);
- }
- }
- else
- {
- m_settings.m_videoFileName.clear();
- qDebug("ATVMod::openVideo: cannot open video file %s", qPrintable(fileName));
- }
-}
-
-void ATVMod::resizeImage()
-{
- float fy = (m_nbImageLines - 2*m_nbBlankLines) / (float) m_imageOriginal.rows;
- float fx = m_pointsPerImgLine / (float) m_imageOriginal.cols;
- cv::resize(m_imageOriginal, m_image, cv::Size(), fx, fy);
- qDebug("ATVMod::resizeImage: %d x %d -> %d x %d", m_imageOriginal.cols, m_imageOriginal.rows, m_image.cols, m_image.rows);
-}
-
-void ATVMod::calculateVideoSizes()
-{
- m_videoFy = (m_nbImageLines - 2*m_nbBlankLines) / (float) m_videoHeight;
- m_videoFx = m_pointsPerImgLine / (float) m_videoWidth;
- m_videoFPSq = m_videoFPS / m_fps;
- m_videoFPSCount = m_videoFPSq;
- m_videoPrevFPSCount = 0;
-
- qDebug("ATVMod::calculateVideoSizes: factors: %f x %f FPSq: %f", m_videoFx, m_videoFy, m_videoFPSq);
-}
-
-void ATVMod::resizeVideo()
-{
- if (!m_videoframeOriginal.empty()) {
- cv::resize(m_videoframeOriginal, m_videoFrame, cv::Size(), m_videoFx, m_videoFy); // resize current frame
- }
-}
-
-void ATVMod::calculateCamerasSizes()
-{
- for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
- {
- it->m_videoFy = (m_nbImageLines - 2*m_nbBlankLines) / (float) it->m_videoHeight;
- it->m_videoFx = m_pointsPerImgLine / (float) it->m_videoWidth;
- it->m_videoFPSq = it->m_videoFPS / m_fps;
- it->m_videoFPSqManual = it->m_videoFPSManual / m_fps;
- it->m_videoFPSCount = 0; //it->m_videoFPSq;
- it->m_videoPrevFPSCount = 0;
-
- qDebug("ATVMod::calculateCamerasSizes: [%d] factors: %f x %f FPSq: %f", (int) (it - m_cameras.begin()), it->m_videoFx, it->m_videoFy, it->m_videoFPSq);
- }
-}
-
-void ATVMod::resizeCameras()
-{
- for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
- {
- if (!it->m_videoframeOriginal.empty()) {
- cv::resize(it->m_videoframeOriginal, it->m_videoFrame, cv::Size(), it->m_videoFx, it->m_videoFy); // resize current frame
- }
- }
-}
-
-void ATVMod::resizeCamera()
-{
- ATVCamera& camera = m_cameras[m_cameraIndex];
-
- if (!camera.m_videoframeOriginal.empty()) {
- cv::resize(camera.m_videoframeOriginal, camera.m_videoFrame, cv::Size(), camera.m_videoFx, camera.m_videoFy); // resize current frame
- }
-}
-
-void ATVMod::seekVideoFileStream(int seekPercentage)
-{
- QMutexLocker mutexLocker(&m_settingsMutex);
-
- if ((m_videoOK) && m_video.isOpened())
- {
- int seekPoint = ((m_videoLength * seekPercentage) / 100);
- m_video.set(CV_CAP_PROP_POS_FRAMES, seekPoint);
- m_videoFPSCount = m_videoFPSq;
- m_videoPrevFPSCount = 0;
- m_videoEOF = false;
- }
-}
-
-void ATVMod::scanCameras()
-{
- for (int i = 0; i < 4; i++)
- {
- ATVCamera newCamera;
- m_cameras.push_back(newCamera);
- m_cameras.back().m_cameraNumber = i;
- m_cameras.back().m_camera.open(i);
-
- if (m_cameras.back().m_camera.isOpened())
- {
- m_cameras.back().m_videoFPS = m_cameras.back().m_camera.get(CV_CAP_PROP_FPS);
- m_cameras.back().m_videoWidth = (int) m_cameras.back().m_camera.get(CV_CAP_PROP_FRAME_WIDTH);
- m_cameras.back().m_videoHeight = (int) m_cameras.back().m_camera.get(CV_CAP_PROP_FRAME_HEIGHT);
-
- //m_cameras.back().m_videoFPS = m_cameras.back().m_videoFPS < 0 ? 16.3f : m_cameras.back().m_videoFPS;
-
- qDebug("ATVMod::scanCameras: [%d] FPS: %f %dx%d",
- i,
- m_cameras.back().m_videoFPS,
- m_cameras.back().m_videoWidth ,
- m_cameras.back().m_videoHeight);
- }
- else
- {
- m_cameras.pop_back();
- }
- }
-
- if (m_cameras.size() > 0)
- {
- calculateCamerasSizes();
- m_cameraIndex = 0;
- }
-}
-
-void ATVMod::releaseCameras()
-{
- for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
- {
- if (it->m_camera.isOpened()) it->m_camera.release();
- }
-}
-
-void ATVMod::getCameraNumbers(std::vector& numbers)
-{
- for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it) {
- numbers.push_back(it->m_cameraNumber);
- }
-
- if (m_cameras.size() > 0)
- {
- m_cameraIndex = 0;
-
- if (getMessageQueueToGUI())
- {
- MsgReportCameraData *report;
- report = MsgReportCameraData::create(
- m_cameras[0].m_cameraNumber,
- m_cameras[0].m_videoFPS,
- m_cameras[0].m_videoFPSManual,
- m_cameras[0].m_videoFPSManualEnable,
- m_cameras[0].m_videoWidth,
- m_cameras[0].m_videoHeight,
- 0);
- getMessageQueueToGUI()->push(report);
- }
- }
-}
-
-void ATVMod::mixImageAndText(cv::Mat& image)
-{
- int fontFace = cv::FONT_HERSHEY_PLAIN;
- double fontScale = image.rows / 100.0;
- int thickness = image.cols / 160;
- int baseline=0;
-
- fontScale = fontScale < 4.0f ? 4.0f : fontScale; // minimum size
- cv::Size textSize = cv::getTextSize(m_settings.m_overlayText.toStdString(), fontFace, fontScale, thickness, &baseline);
- baseline += thickness;
-
- // position the text in the top left corner
- cv::Point textOrg(6, textSize.height+10);
- // then put the text itself
- cv::putText(image, m_settings.m_overlayText.toStdString(), textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA);
-}
-
-void ATVMod::applyChannelSettings(int outputSampleRate, int inputFrequencyOffset, bool force)
-{
- qDebug() << "AMMod::applyChannelSettings:"
- << " 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)
- {
- getBaseValues(outputSampleRate, m_settings.m_nbLines * m_settings.m_fps, m_tvSampleRate, m_pointsPerLine);
-
- m_settingsMutex.lock();
-
- if (m_tvSampleRate > 0)
- {
- m_interpolatorDistanceRemain = 0;
- m_interpolatorDistance = (Real) m_tvSampleRate / (Real) outputSampleRate;
- m_interpolator.create(32,
- m_tvSampleRate,
- m_settings.m_rfBandwidth / getRFBandwidthDivisor(m_settings.m_atvModulation),
- 3.0);
- }
- else
- {
- m_tvSampleRate = outputSampleRate;
- }
-
- m_SSBFilter->create_filter(0, m_settings.m_rfBandwidth / m_tvSampleRate);
- memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
- m_SSBFilterBufferIndex = 0;
-
- applyStandard(); // set all timings
- m_settingsMutex.unlock();
-
- if (getMessageQueueToGUI())
- {
- MsgReportEffectiveSampleRate *report;
- report = MsgReportEffectiveSampleRate::create(m_tvSampleRate, m_pointsPerLine);
- getMessageQueueToGUI()->push(report);
- }
- }
-
- m_outputSampleRate = outputSampleRate;
- m_inputFrequencyOffset = inputFrequencyOffset;
-}
-
void ATVMod::applySettings(const ATVModSettings& settings, bool force)
{
qDebug() << "ATVMod::applySettings:"
@@ -1170,72 +294,8 @@ void ATVMod::applySettings(const ATVModSettings& settings, bool force)
reverseAPIKeys.append("overlayText");
}
- if ((settings.m_atvStd != m_settings.m_atvStd)
- || (settings.m_nbLines != m_settings.m_nbLines)
- || (settings.m_fps != m_settings.m_fps)
- || (settings.m_rfBandwidth != m_settings.m_rfBandwidth)
- || (settings.m_atvModulation != m_settings.m_atvModulation) || force)
- {
- getBaseValues(m_outputSampleRate, settings.m_nbLines * settings.m_fps, m_tvSampleRate, m_pointsPerLine);
-
- m_settingsMutex.lock();
-
- if (m_tvSampleRate > 0)
- {
- m_interpolatorDistanceRemain = 0;
- m_interpolatorDistance = (Real) m_tvSampleRate / (Real) m_outputSampleRate;
- m_interpolator.create(32,
- m_tvSampleRate,
- settings.m_rfBandwidth / getRFBandwidthDivisor(settings.m_atvModulation),
- 3.0);
- }
-
- m_SSBFilter->create_filter(0, settings.m_rfBandwidth / m_tvSampleRate);
- memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
- m_SSBFilterBufferIndex = 0;
-
- applyStandard(); // set all timings
- m_settingsMutex.unlock();
-
- if (getMessageQueueToGUI())
- {
- MsgReportEffectiveSampleRate *report;
- report = MsgReportEffectiveSampleRate::create(m_tvSampleRate, m_pointsPerLine);
- getMessageQueueToGUI()->push(report);
- }
- }
-
- if ((settings.m_rfOppBandwidth != m_settings.m_rfOppBandwidth)
- || (settings.m_rfBandwidth != m_settings.m_rfBandwidth)
- || (settings.m_nbLines != m_settings.m_nbLines) // difference in line period may have changed TV sample rate
- || (settings.m_fps != m_settings.m_fps) //
- || force)
- {
- m_settingsMutex.lock();
-
- m_DSBFilter->create_asym_filter(settings.m_rfOppBandwidth / m_tvSampleRate, settings.m_rfBandwidth / m_tvSampleRate);
- memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
- m_DSBFilterBufferIndex = 0;
-
- m_settingsMutex.unlock();
- }
-
- if ((settings.m_showOverlayText != m_settings.m_showOverlayText) || force)
- {
- if (!m_imageFromFile.empty())
- {
- m_imageFromFile.copyTo(m_imageOriginal);
-
- if (settings.m_showOverlayText) {
- qDebug("ATVMod::applySettings: set overlay text");
- mixImageAndText(m_imageOriginal);
- } else{
- qDebug("ATVMod::applySettings: clear overlay text");
- }
-
- resizeImage();
- }
- }
+ ATVModBaseband::MsgConfigureATVModBaseband *msg = ATVModBaseband::MsgConfigureATVModBaseband::create(settings, force);
+ m_basebandSource->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
@@ -1296,7 +356,7 @@ int ATVMod::webapiSettingsPutPatch(
if (m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset)
{
ATVMod::MsgConfigureChannelizer *msgChan = ATVMod::MsgConfigureChannelizer::create(
- settings.m_inputFrequencyOffset);
+ m_basebandSource->getChannelSampleRate(), settings.m_inputFrequencyOffset);
m_inputMessageQueue.push(msgChan);
}
@@ -1311,9 +371,9 @@ int ATVMod::webapiSettingsPutPatch(
if (channelSettingsKeys.contains("imageFileName"))
{
- MsgConfigureImageFileName *msg = MsgConfigureImageFileName::create(
+ ATVModBaseband::MsgConfigureImageFileName *msg = ATVModBaseband::MsgConfigureImageFileName::create(
*response.getAtvModSettings()->getImageFileName());
- m_inputMessageQueue.push(msg);
+ m_basebandSource->getInputMessageQueue()->push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
@@ -1325,7 +385,7 @@ int ATVMod::webapiSettingsPutPatch(
if (channelSettingsKeys.contains("videoFileName"))
{
- MsgConfigureVideoFileName *msg = MsgConfigureVideoFileName::create(
+ ATVModBaseband::MsgConfigureVideoFileName *msg = ATVModBaseband::MsgConfigureVideoFileName::create(
*response.getAtvModSettings()->getVideoFileName());
m_inputMessageQueue.push(msg);
@@ -1501,7 +561,7 @@ void ATVMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon
void ATVMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getAtvModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
- response.getAtvModReport()->setChannelSampleRate(m_outputSampleRate);
+ response.getAtvModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
}
void ATVMod::webapiReverseSendSettings(QList& channelSettingsKeys, const ATVModSettings& settings, bool force)
@@ -1588,13 +648,14 @@ void ATVMod::webapiReverseSendSettings(QList& 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;
}
@@ -1609,10 +670,38 @@ void ATVMod::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("ATVMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
+ reply->deleteLater();
}
+
+double ATVMod::getMagSq() const
+{
+ return m_basebandSource->getMagSq();
+}
+
+void ATVMod::setLevelMeter(QObject *levelMeter)
+{
+ connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
+}
+
+int ATVMod::getEffectiveSampleRate() const
+{
+ return m_basebandSource->getEffectiveSampleRate();
+}
+
+void ATVMod::getCameraNumbers(std::vector& numbers)
+{
+ m_basebandSource->getCameraNumbers(numbers);
+}
+
+void ATVMod::propagateMessageQueueToGUI()
+{
+ m_basebandSource->setMessageQueueToGUI(getMessageQueueToGUI());
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h
index c0a55fcac..b47847b20 100644
--- a/plugins/channeltx/modatv/atvmod.h
+++ b/plugins/channeltx/modatv/atvmod.h
@@ -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
+#include
+#include
-#include
#include
#include
-#include
-#include
-#include
-
-#include
-
#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& 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& 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 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 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& 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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
- } else {
- pixv = m_image.at(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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
- } else {
- pixv = m_videoFrame.at(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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
- } else {
- pixv = camera.m_videoFrame.at(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_ */
diff --git a/plugins/channeltx/modatv/atvmodbaseband.cpp b/plugins/channeltx/modatv/atvmodbaseband.cpp
new file mode 100644
index 000000000..54fb59aa1
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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& numbers)
+{
+ m_source.getCameraNumbers(numbers);
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modatv/atvmodbaseband.h b/plugins/channeltx/modatv/atvmodbaseband.h
new file mode 100644
index 000000000..1358a0cca
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodbaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_ATVMODBASEBAND_H
+#define INCLUDE_ATVMODBASEBAND_H
+
+#include
+#include
+
+#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& 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
diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp
index 04f1f9a0f..87a911fe0 100644
--- a/plugins/channeltx/modatv/atvmodgui.cpp
+++ b/plugins/channeltx/modatv/atvmodgui.cpp
@@ -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 cameraNumbers;
+ m_atvMod->getCameraNumbers(cameraNumbers);
+
+ for (std::vector::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 cameraNumbers;
- m_atvMod->getCameraNumbers(cameraNumbers);
-
- for (std::vector::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);
diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp
index 4ca13b678..0f77b0fca 100644
--- a/plugins/channeltx/modatv/atvmodplugin.cpp
+++ b/plugins/channeltx/modatv/atvmodplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/modatv/atvmodreport.cpp b/plugins/channeltx/modatv/atvmodreport.cpp
new file mode 100644
index 000000000..d358ce935
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodreport.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#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()
+{ }
\ No newline at end of file
diff --git a/plugins/channeltx/modatv/atvmodreport.h b/plugins/channeltx/modatv/atvmodreport.h
new file mode 100644
index 000000000..ec3a6594c
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodreport.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELTX_MODATV_ATVMODREPORT_H_
+#define PLUGINS_CHANNELTX_MODATV_ATVMODREPORT_H_
+
+#include
+#include
+#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_
diff --git a/plugins/channeltx/modatv/atvmodsource.cpp b/plugins/channeltx/modatv/atvmodsource.cpp
new file mode 100644
index 000000000..5f798ab34
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodsource.cpp
@@ -0,0 +1,1073 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "opencv2/imgproc/imgproc.hpp"
+
+#include "dsp/dspcommands.h"
+#include "device/deviceapi.h"
+#include "util/db.h"
+#include "util/messagequeue.h"
+
+#include "atvmodreport.h"
+#include "atvmodsource.h"
+
+const float ATVModSource::m_blackLevel = 0.3f;
+const float ATVModSource::m_spanLevel = 0.7f;
+const int ATVModSource::m_levelNbSamples = 10000; // every 10ms
+const int ATVModSource::m_nbBars = 6;
+const int ATVModSource::m_cameraFPSTestNbFrames = 100;
+const int ATVModSource::m_ssbFftLen = 1024;
+
+ATVModSource::ATVModSource() :
+ m_outputSampleRate(1000000),
+ m_inputFrequencyOffset(0),
+ m_modPhasor(0.0f),
+ m_tvSampleRate(1000000),
+ m_evenImage(true),
+ m_settingsMutex(QMutex::Recursive),
+ m_horizontalCount(0),
+ m_lineCount(0),
+ m_imageOK(false),
+ m_videoFPSq(1.0f),
+ m_videoFPSCount(0.0f),
+ m_videoPrevFPSCount(0),
+ m_videoEOF(false),
+ m_videoOK(false),
+ m_cameraIndex(-1),
+ //m_showOverlayText(false),
+ m_SSBFilter(nullptr),
+ m_SSBFilterBuffer(nullptr),
+ m_SSBFilterBufferIndex(0),
+ m_DSBFilter(nullptr),
+ m_DSBFilterBuffer(nullptr),
+ m_DSBFilterBufferIndex(0),
+ m_messageQueueToGUI(nullptr)
+{
+ scanCameras();
+
+ m_SSBFilter = new fftfilt(0, m_settings.m_rfBandwidth / m_outputSampleRate, m_ssbFftLen);
+ m_SSBFilterBuffer = new Complex[m_ssbFftLen>>1]; // filter returns data exactly half of its size
+ memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
+
+ m_DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_outputSampleRate, 2 * m_ssbFftLen);
+ m_DSBFilterBuffer = new Complex[m_ssbFftLen];
+ memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
+
+ m_interpolatorDistanceRemain = 0.0f;
+ m_interpolatorDistance = 1.0f;
+
+ applyChannelSettings(m_outputSampleRate, m_inputFrequencyOffset, true);
+ applySettings(m_settings, true); // does applyStandard() too;
+}
+
+ATVModSource::~ATVModSource()
+{
+ if (m_video.isOpened()) {
+ m_video.release();
+ }
+
+ releaseCameras();
+ delete m_SSBFilter;
+ delete m_DSBFilter;
+ delete[] m_SSBFilterBuffer;
+ delete[] m_DSBFilterBuffer;
+}
+
+void ATVModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
+{
+ std::for_each(
+ begin,
+ begin + nbSamples,
+ [this](Sample& s) {
+ pullOne(s);
+ }
+ );
+}
+
+void ATVModSource::prefetch(unsigned int nbSamples)
+{
+ (void) nbSamples;
+}
+
+void ATVModSource::pullOne(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_tvSampleRate == m_outputSampleRate) && (!m_settings.m_forceDecimator)) // no interpolation nor decimation
+ {
+ modulateSample();
+ pullFinalize(m_modSample, sample);
+ }
+ else
+ {
+ 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;
+ pullFinalize(ci, sample);
+ }
+}
+
+void ATVModSource::pullFinalize(Complex& ci, Sample& sample)
+{
+ 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);
+
+ sample.m_real = (FixReal) ci.real();
+ sample.m_imag = (FixReal) ci.imag();
+}
+
+void ATVModSource::modulateSample()
+{
+ Real t;
+
+ pullVideo(t);
+ calculateLevel(t);
+
+ t = m_settings.m_invertedVideo ? 1.0f - t : t;
+
+ switch (m_settings.m_atvModulation)
+ {
+ case ATVModSettings::ATVModulationFM: // FM half bandwidth deviation
+ m_modPhasor += (t - 0.5f) * m_settings.m_fmExcursion * 2.0f * M_PI;
+ if (m_modPhasor > 2.0f * M_PI) m_modPhasor -= 2.0f * M_PI; // limit growth
+ if (m_modPhasor < 2.0f * M_PI) m_modPhasor += 2.0f * M_PI; // limit growth
+ m_modSample.real(cos(m_modPhasor) * m_settings.m_rfScalingFactor); // -1 dB
+ m_modSample.imag(sin(m_modPhasor) * m_settings.m_rfScalingFactor);
+ break;
+ case ATVModSettings::ATVModulationLSB:
+ case ATVModSettings::ATVModulationUSB:
+ m_modSample = modulateSSB(t);
+ m_modSample *= m_settings.m_rfScalingFactor;
+ break;
+ case ATVModSettings::ATVModulationVestigialLSB:
+ case ATVModSettings::ATVModulationVestigialUSB:
+ m_modSample = modulateVestigialSSB(t);
+ m_modSample *= m_settings.m_rfScalingFactor;
+ break;
+ case ATVModSettings::ATVModulationAM: // AM 90%
+ default:
+ m_modSample.real((t*1.8f + 0.1f) * (m_settings.m_rfScalingFactor/2.0f)); // modulate and scale zero frequency carrier
+ m_modSample.imag(0.0f);
+ }
+}
+
+Complex& ATVModSource::modulateSSB(Real& sample)
+{
+ int n_out;
+ Complex ci(sample, 0.0f);
+ fftfilt::cmplx *filtered;
+
+ n_out = m_SSBFilter->runSSB(ci, &filtered, m_settings.m_atvModulation == ATVModSettings::ATVModulationUSB);
+
+ if (n_out > 0)
+ {
+ memcpy((void *) m_SSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
+ m_SSBFilterBufferIndex = 0;
+ }
+
+ m_SSBFilterBufferIndex++;
+
+ return m_SSBFilterBuffer[m_SSBFilterBufferIndex-1];
+}
+
+Complex& ATVModSource::modulateVestigialSSB(Real& sample)
+{
+ int n_out;
+ Complex ci(sample, 0.0f);
+ fftfilt::cmplx *filtered;
+
+ n_out = m_DSBFilter->runAsym(ci, &filtered, m_settings.m_atvModulation == ATVModSettings::ATVModulationVestigialUSB);
+
+ if (n_out > 0)
+ {
+ memcpy((void *) m_DSBFilterBuffer, (const void *) filtered, n_out*sizeof(Complex));
+ m_DSBFilterBufferIndex = 0;
+ }
+
+ m_DSBFilterBufferIndex++;
+
+ return m_DSBFilterBuffer[m_DSBFilterBufferIndex-1];
+}
+
+void ATVModSource::pullVideo(Real& sample)
+{
+ if ((m_settings.m_atvStd == ATVModSettings::ATVStdHSkip) && (m_lineCount == m_nbLines2)) // last line in skip mode
+ {
+ pullImageLine(sample, true); // pull image line without sync
+ }
+ else if (m_lineCount < m_nbLines2 + 1) // even image or non interlaced
+ {
+ int iLine = m_lineCount;
+
+ if (iLine < m_nbSyncLinesHeadE + m_nbBlankLines)
+ {
+ pullVSyncLine(sample);
+ }
+ else if (iLine > m_nbLines2 - m_nbSyncLinesBottom)
+ {
+ pullVSyncLine(sample);
+ }
+ else
+ {
+ pullImageLine(sample);
+ }
+ }
+ else // odd image
+ {
+ int iLine = m_lineCount - m_nbLines2 - 1;
+
+ if (iLine < m_nbSyncLinesHeadO + m_nbBlankLines)
+ {
+ pullVSyncLine(sample);
+ }
+ else if (iLine > m_nbLines2 - 1 - m_nbSyncLinesBottom)
+ {
+ pullVSyncLine(sample);
+ }
+ else
+ {
+ pullImageLine(sample);
+ }
+ }
+
+ if (m_horizontalCount < m_nbHorizPoints - 1)
+ {
+ m_horizontalCount++;
+ }
+ else
+ {
+ if (m_lineCount < m_nbLines - 1)
+ {
+ m_lineCount++;
+ if (m_lineCount > (m_nbLines/2)) m_evenImage = !m_evenImage;
+ }
+ else // new image
+ {
+ m_lineCount = 0;
+ m_evenImage = !m_evenImage;
+
+ if ((m_settings.m_atvModInput == ATVModSettings::ATVModInputVideo) && m_videoOK && (m_settings.m_videoPlay) && !m_videoEOF)
+ {
+ int grabOK = 0;
+ int fpsIncrement = (int) m_videoFPSCount - m_videoPrevFPSCount;
+
+ // move a number of frames according to increment
+ // use grab to test for EOF then retrieve to preserve last valid frame as the current original frame
+ // TODO: handle pause (no move)
+ for (int i = 0; i < fpsIncrement; i++)
+ {
+ grabOK = m_video.grab();
+ if (!grabOK) break;
+ }
+
+ if (grabOK)
+ {
+ cv::Mat colorFrame;
+ m_video.retrieve(colorFrame);
+
+ if (!colorFrame.empty()) // some frames may not come out properly
+ {
+ if (m_settings.m_showOverlayText) {
+ mixImageAndText(colorFrame);
+ }
+
+ cv::cvtColor(colorFrame, m_videoframeOriginal, CV_BGR2GRAY);
+ resizeVideo();
+ }
+ }
+ else
+ {
+ if (m_settings.m_videoPlayLoop) { // play loop
+ seekVideoFileStream(0);
+ } else { // stops
+ m_videoEOF = true;
+ }
+ }
+
+ if (m_videoFPSCount < m_videoFPS)
+ {
+ m_videoPrevFPSCount = (int) m_videoFPSCount;
+ m_videoFPSCount += m_videoFPSq;
+ }
+ else
+ {
+ m_videoPrevFPSCount = 0;
+ m_videoFPSCount = m_videoFPSq;
+ }
+ }
+ else if ((m_settings.m_atvModInput == ATVModSettings::ATVModInputCamera) && (m_settings.m_cameraPlay))
+ {
+ ATVCamera& camera = m_cameras[m_cameraIndex];
+
+ if (camera.m_videoFPS < 0.0f) // default frame rate when it could not be obtained via get
+ {
+ time_t start, end;
+ cv::Mat frame;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportCameraData *report;
+ report = ATVModReport::MsgReportCameraData::create(
+ camera.m_cameraNumber,
+ 0.0f,
+ camera.m_videoFPSManual,
+ camera.m_videoFPSManualEnable,
+ camera.m_videoWidth,
+ camera.m_videoHeight,
+ 1); // open splash screen on GUI side
+ getMessageQueueToGUI()->push(report);
+ }
+
+ int nbFrames = 0;
+
+ time(&start);
+
+ for (int i = 0; i < m_cameraFPSTestNbFrames; i++)
+ {
+ camera.m_camera >> frame;
+ if (!frame.empty()) nbFrames++;
+ }
+
+ time(&end);
+
+ double seconds = difftime (end, start);
+ // take a 10% guard and divide bandwidth between all cameras as a hideous hack
+ camera.m_videoFPS = ((nbFrames / seconds) * 0.9) / m_cameras.size();
+ camera.m_videoFPSq = camera.m_videoFPS / m_fps;
+ camera.m_videoFPSCount = camera.m_videoFPSq;
+ camera.m_videoPrevFPSCount = 0;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportCameraData *report;
+ report = ATVModReport::MsgReportCameraData::create(
+ camera.m_cameraNumber,
+ camera.m_videoFPS,
+ camera.m_videoFPSManual,
+ camera.m_videoFPSManualEnable,
+ camera.m_videoWidth,
+ camera.m_videoHeight,
+ 2); // close splash screen on GUI side
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+ else if (camera.m_videoFPS == 0.0f) // Hideous hack for windows
+ {
+ camera.m_videoFPS = 5.0f;
+ camera.m_videoFPSq = camera.m_videoFPS / m_fps;
+ camera.m_videoFPSCount = camera.m_videoFPSq;
+ camera.m_videoPrevFPSCount = 0;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportCameraData *report;
+ report = ATVModReport::MsgReportCameraData::create(
+ camera.m_cameraNumber,
+ camera.m_videoFPS,
+ camera.m_videoFPSManual,
+ camera.m_videoFPSManualEnable,
+ camera.m_videoWidth,
+ camera.m_videoHeight,
+ 0);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+
+ int fpsIncrement = (int) camera.m_videoFPSCount - camera.m_videoPrevFPSCount;
+
+ // move a number of frames according to increment
+ // use grab to test for EOF then retrieve to preserve last valid frame as the current original frame
+ cv::Mat colorFrame;
+ int grabOK = 0;
+
+ for (int i = 0; i < fpsIncrement; i++)
+ {
+ grabOK = camera.m_camera.grab();
+ if (!grabOK) break;
+ // camera.m_camera >> colorFrame;
+ // if (colorFrame.empty()) break;
+ }
+
+ if (grabOK) {
+ camera.m_camera.retrieve(colorFrame);
+ }
+
+ if (!colorFrame.empty()) // some frames may not come out properly
+ {
+ if (m_settings.m_showOverlayText) {
+ mixImageAndText(colorFrame);
+ }
+
+ cv::cvtColor(colorFrame, camera.m_videoframeOriginal, CV_BGR2GRAY);
+ resizeCamera();
+ }
+
+ if (camera.m_videoFPSCount < (camera.m_videoFPSManualEnable ? camera.m_videoFPSManual : camera.m_videoFPS))
+ {
+ camera.m_videoPrevFPSCount = (int) camera.m_videoFPSCount;
+ camera.m_videoFPSCount += (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq);
+ }
+ else
+ {
+ camera.m_videoPrevFPSCount = 0;
+ camera.m_videoFPSCount = (camera.m_videoFPSManualEnable ? camera.m_videoFPSqManual : camera.m_videoFPSq);
+ }
+ }
+ }
+
+ m_horizontalCount = 0;
+ }
+}
+
+void ATVModSource::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 = std::sqrt(m_levelSum / m_levelNbSamples);
+ m_peakLevelOut = m_peakLevel;
+ m_peakLevel = 0.0f;
+ m_levelSum = 0.0f;
+ m_levelCalcCount = 0;
+ }
+}
+
+void ATVModSource::getBaseValues(int outputSampleRate, int linesPerSecond, int& sampleRateUnits, uint32_t& nbPointsPerRateUnit)
+{
+ int maxPoints = outputSampleRate / linesPerSecond;
+ int i = maxPoints;
+
+ for (; i > 0; i--)
+ {
+ if ((i * linesPerSecond) % 10 == 0)
+ break;
+ }
+
+ nbPointsPerRateUnit = i == 0 ? maxPoints : i;
+ sampleRateUnits = nbPointsPerRateUnit * linesPerSecond;
+}
+
+float ATVModSource::getRFBandwidthDivisor(ATVModSettings::ATVModulation modulation)
+{
+ switch(modulation)
+ {
+ case ATVModSettings::ATVModulationLSB:
+ case ATVModSettings::ATVModulationUSB:
+ case ATVModSettings::ATVModulationVestigialLSB:
+ case ATVModSettings::ATVModulationVestigialUSB:
+ return 1.05f;
+ break;
+ case ATVModSettings::ATVModulationAM:
+ case ATVModSettings::ATVModulationFM:
+ default:
+ return 2.2f;
+ }
+}
+
+void ATVModSource::applyStandard()
+{
+ m_pointsPerSync = (uint32_t) ((4.7f / 64.0f) * m_pointsPerLine);
+ m_pointsPerBP = (uint32_t) ((4.7f / 64.0f) * m_pointsPerLine);
+ m_pointsPerFP = (uint32_t) ((2.6f / 64.0f) * m_pointsPerLine);
+ m_pointsPerFSync = (uint32_t) ((2.3f / 64.0f) * m_pointsPerLine);
+
+ m_pointsPerImgLine = m_pointsPerLine - m_pointsPerSync - m_pointsPerBP - m_pointsPerFP;
+ m_nbHorizPoints = m_pointsPerLine;
+
+ m_pointsPerHBar = m_pointsPerImgLine / m_nbBars;
+ m_hBarIncrement = m_spanLevel / (float) m_nbBars;
+ m_vBarIncrement = m_spanLevel / (float) m_nbBars;
+
+ m_nbLines = m_settings.m_nbLines;
+ m_nbLines2 = m_nbLines / 2;
+ m_fps = m_settings.m_fps * 1.0f;
+
+// qDebug() << "ATVMod::applyStandard: "
+// << " m_nbLines: " << m_config.m_nbLines
+// << " m_fps: " << m_config.m_fps
+// << " rateUnits: " << rateUnits
+// << " nbPointsPerRateUnit: " << nbPointsPerRateUnit
+// << " m_tvSampleRate: " << m_tvSampleRate
+// << " m_pointsPerTU: " << m_pointsPerTU;
+
+ switch(m_settings.m_atvStd)
+ {
+ case ATVModSettings::ATVStdHSkip:
+ m_nbImageLines = m_nbLines; // lines less the total number of sync lines
+ m_nbImageLines2 = m_nbImageLines; // force non interleaved for vbars
+ m_interleaved = false;
+ m_nbSyncLinesHeadE = 0; // number of sync lines on the top of a frame even
+ m_nbSyncLinesHeadO = 0; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = -1; // force no vsync in even block
+ m_nbLongSyncLines = 0;
+ m_nbHalfLongSync = 0;
+ m_nbWholeEqLines = 0;
+ m_singleLongSync = true;
+ m_nbBlankLines = 0;
+ m_blankLineLvel = 0.7f;
+ m_nbLines2 = m_nbLines - 1;
+ break;
+ case ATVModSettings::ATVStdShort:
+ m_nbImageLines = m_nbLines - 2; // lines less the total number of sync lines
+ m_nbImageLines2 = m_nbImageLines; // force non interleaved for vbars
+ m_interleaved = false;
+ m_nbSyncLinesHeadE = 1; // number of sync lines on the top of a frame even
+ m_nbSyncLinesHeadO = 1; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = 0;
+ m_nbLongSyncLines = 1;
+ m_nbHalfLongSync = 0;
+ m_nbWholeEqLines = 0;
+ m_singleLongSync = true;
+ m_nbBlankLines = 1;
+ m_blankLineLvel = 0.7f;
+ m_nbLines2 = m_nbLines; // force non interleaved => treated as even for all lines
+ break;
+ case ATVModSettings::ATVStdShortInterleaved:
+ m_nbImageLines = m_nbLines - 2; // lines less the total number of sync lines
+ m_nbImageLines2 = m_nbImageLines / 2;
+ m_interleaved = true;
+ m_nbSyncLinesHeadE = 1; // number of sync lines on the top of a frame even
+ m_nbSyncLinesHeadO = 1; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = 0;
+ m_nbLongSyncLines = 1;
+ m_nbHalfLongSync = 0;
+ m_nbWholeEqLines = 0;
+ m_singleLongSync = true;
+ m_nbBlankLines = 1;
+ m_blankLineLvel = 0.7f;
+ break;
+ case ATVModSettings::ATVStd405: // Follows loosely the 405 lines standard
+ m_nbImageLines = m_nbLines - 15; // lines less the total number of sync lines
+ m_nbImageLines2 = m_nbImageLines / 2;
+ m_interleaved = true;
+ m_nbSyncLinesHeadE = 5; // number of sync lines on the top of a frame even
+ m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = 3;
+ m_nbLongSyncLines = 2;
+ m_nbHalfLongSync = 1;
+ m_nbWholeEqLines = 2;
+ m_singleLongSync = false;
+ m_nbBlankLines = 7; // yields 376 lines (195 - 7) * 2
+ m_blankLineLvel = m_blackLevel;
+ break;
+ case ATVModSettings::ATVStdPAL525: // Follows PAL-M standard
+ m_nbImageLines = m_nbLines - 15;
+ m_nbImageLines2 = m_nbImageLines / 2;
+ m_interleaved = true;
+ m_nbSyncLinesHeadE = 5;
+ m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = 3;
+ m_nbLongSyncLines = 2;
+ m_nbHalfLongSync = 1;
+ m_nbWholeEqLines = 2;
+ m_singleLongSync = false;
+ m_nbBlankLines = 15; // yields 480 lines (255 - 15) * 2
+ m_blankLineLvel = m_blackLevel;
+ break;
+ case ATVModSettings::ATVStdPAL625: // Follows PAL-B/G/H standard
+ default:
+ m_nbImageLines = m_nbLines - 15;
+ m_nbImageLines2 = m_nbImageLines / 2;
+ m_interleaved = true;
+ m_nbSyncLinesHeadE = 5;
+ m_nbSyncLinesHeadO = 4; // number of sync lines on the top of a frame odd
+ m_nbSyncLinesBottom = 3;
+ m_nbLongSyncLines = 2;
+ m_nbHalfLongSync = 1;
+ m_nbWholeEqLines = 2;
+ m_singleLongSync = false;
+ m_nbBlankLines = 17; // yields 576 lines (305 - 17) * 2
+ m_blankLineLvel = m_blackLevel;
+ }
+
+ m_linesPerVBar = m_nbImageLines2 / m_nbBars;
+
+ if (m_imageOK)
+ {
+ resizeImage();
+ }
+
+ if (m_videoOK)
+ {
+ calculateVideoSizes();
+ resizeVideo();
+ }
+
+ calculateCamerasSizes();
+}
+
+void ATVModSource::openImage(const QString& fileName)
+{
+ m_imageFromFile = cv::imread(qPrintable(fileName), CV_LOAD_IMAGE_GRAYSCALE);
+ m_imageOK = m_imageFromFile.data != 0;
+
+ if (m_imageOK)
+ {
+ m_settings.m_imageFileName = fileName;
+ m_imageFromFile.copyTo(m_imageOriginal);
+
+ if (m_settings.m_showOverlayText) {
+ mixImageAndText(m_imageOriginal);
+ }
+
+ resizeImage();
+ }
+ else
+ {
+ m_settings.m_imageFileName.clear();
+ qDebug("ATVModSource::openImage: cannot open image file %s", qPrintable(fileName));
+ }
+}
+
+void ATVModSource::openVideo(const QString& fileName)
+{
+ //if (m_videoOK && m_video.isOpened()) m_video.release(); should be done by OpenCV in open method
+
+ m_videoOK = m_video.open(qPrintable(fileName));
+
+ if (m_videoOK)
+ {
+ m_settings.m_videoFileName = fileName;
+ m_videoFPS = m_video.get(CV_CAP_PROP_FPS);
+ m_videoWidth = (int) m_video.get(CV_CAP_PROP_FRAME_WIDTH);
+ m_videoHeight = (int) m_video.get(CV_CAP_PROP_FRAME_HEIGHT);
+ m_videoLength = (int) m_video.get(CV_CAP_PROP_FRAME_COUNT);
+ int ex = static_cast(m_video.get(CV_CAP_PROP_FOURCC));
+ char ext[] = {(char)(ex & 0XFF),(char)((ex & 0XFF00) >> 8),(char)((ex & 0XFF0000) >> 16),(char)((ex & 0XFF000000) >> 24),0};
+
+ qDebug("ATVModSource::openVideo: %s FPS: %f size: %d x %d #frames: %d codec: %s",
+ m_video.isOpened() ? "OK" : "KO",
+ m_videoFPS,
+ m_videoWidth,
+ m_videoHeight,
+ m_videoLength,
+ ext);
+
+ calculateVideoSizes();
+ m_videoEOF = false;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportVideoFileSourceStreamData *report;
+ report = ATVModReport::MsgReportVideoFileSourceStreamData::create(m_videoFPS, m_videoLength);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+ else
+ {
+ m_settings.m_videoFileName.clear();
+ qDebug("ATVModSource::openVideo: cannot open video file %s", qPrintable(fileName));
+ }
+}
+
+void ATVModSource::resizeImage()
+{
+ float fy = (m_nbImageLines - 2*m_nbBlankLines) / (float) m_imageOriginal.rows;
+ float fx = m_pointsPerImgLine / (float) m_imageOriginal.cols;
+ cv::resize(m_imageOriginal, m_image, cv::Size(), fx, fy);
+ qDebug("ATVModSource::resizeImage: %d x %d -> %d x %d", m_imageOriginal.cols, m_imageOriginal.rows, m_image.cols, m_image.rows);
+}
+
+void ATVModSource::calculateVideoSizes()
+{
+ m_videoFy = (m_nbImageLines - 2*m_nbBlankLines) / (float) m_videoHeight;
+ m_videoFx = m_pointsPerImgLine / (float) m_videoWidth;
+ m_videoFPSq = m_videoFPS / m_fps;
+ m_videoFPSCount = m_videoFPSq;
+ m_videoPrevFPSCount = 0;
+
+ qDebug("ATVModSource::calculateVideoSizes: factors: %f x %f FPSq: %f", m_videoFx, m_videoFy, m_videoFPSq);
+}
+
+void ATVModSource::resizeVideo()
+{
+ if (!m_videoframeOriginal.empty()) {
+ cv::resize(m_videoframeOriginal, m_videoFrame, cv::Size(), m_videoFx, m_videoFy); // resize current frame
+ }
+}
+
+void ATVModSource::calculateCamerasSizes()
+{
+ for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
+ {
+ it->m_videoFy = (m_nbImageLines - 2*m_nbBlankLines) / (float) it->m_videoHeight;
+ it->m_videoFx = m_pointsPerImgLine / (float) it->m_videoWidth;
+ it->m_videoFPSq = it->m_videoFPS / m_fps;
+ it->m_videoFPSqManual = it->m_videoFPSManual / m_fps;
+ it->m_videoFPSCount = 0; //it->m_videoFPSq;
+ it->m_videoPrevFPSCount = 0;
+
+ qDebug("ATVModSource::calculateCamerasSizes: [%d] factors: %f x %f FPSq: %f",
+ (int) (it - m_cameras.begin()), it->m_videoFx, it->m_videoFy, it->m_videoFPSq);
+ }
+}
+
+void ATVModSource::resizeCameras()
+{
+ for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
+ {
+ if (!it->m_videoframeOriginal.empty()) {
+ cv::resize(it->m_videoframeOriginal, it->m_videoFrame, cv::Size(), it->m_videoFx, it->m_videoFy); // resize current frame
+ }
+ }
+}
+
+void ATVModSource::resizeCamera()
+{
+ if (!m_cameras[m_cameraIndex].m_videoframeOriginal.empty()) {
+ cv::resize(m_cameras[m_cameraIndex].m_videoframeOriginal, m_cameras[m_cameraIndex].m_videoFrame, cv::Size(), m_cameras[m_cameraIndex].m_videoFx, m_cameras[m_cameraIndex].m_videoFy); // resize current frame
+ }
+}
+
+void ATVModSource::seekVideoFileStream(int seekPercentage)
+{
+ QMutexLocker mutexLocker(&m_settingsMutex);
+
+ if ((m_videoOK) && m_video.isOpened())
+ {
+ int seekPoint = ((m_videoLength * seekPercentage) / 100);
+ m_video.set(CV_CAP_PROP_POS_FRAMES, seekPoint);
+ m_videoFPSCount = m_videoFPSq;
+ m_videoPrevFPSCount = 0;
+ m_videoEOF = false;
+ }
+}
+
+void ATVModSource::scanCameras()
+{
+ for (int i = 0; i < 4; i++)
+ {
+ ATVCamera newCamera;
+ m_cameras.push_back(newCamera);
+ m_cameras.back().m_cameraNumber = i;
+ m_cameras.back().m_camera.open(i);
+
+ if (m_cameras.back().m_camera.isOpened())
+ {
+ m_cameras.back().m_videoFPS = m_cameras.back().m_camera.get(CV_CAP_PROP_FPS);
+ m_cameras.back().m_videoWidth = (int) m_cameras.back().m_camera.get(CV_CAP_PROP_FRAME_WIDTH);
+ m_cameras.back().m_videoHeight = (int) m_cameras.back().m_camera.get(CV_CAP_PROP_FRAME_HEIGHT);
+
+ //m_cameras.back().m_videoFPS = m_cameras.back().m_videoFPS < 0 ? 16.3f : m_cameras.back().m_videoFPS;
+
+ qDebug("ATVModSource::scanCameras: [%d] FPS: %f %dx%d",
+ i,
+ m_cameras.back().m_videoFPS,
+ m_cameras.back().m_videoWidth ,
+ m_cameras.back().m_videoHeight);
+ }
+ else
+ {
+ m_cameras.pop_back();
+ }
+ }
+
+ if (m_cameras.size() > 0)
+ {
+ calculateCamerasSizes();
+ m_cameraIndex = 0;
+ }
+}
+
+void ATVModSource::releaseCameras()
+{
+ for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it)
+ {
+ if (it->m_camera.isOpened()) it->m_camera.release();
+ }
+}
+
+void ATVModSource::getCameraNumbers(std::vector& numbers)
+{
+ for (std::vector::iterator it = m_cameras.begin(); it != m_cameras.end(); ++it) {
+ numbers.push_back(it->m_cameraNumber);
+ }
+
+ if (m_cameras.size() > 0)
+ {
+ m_cameraIndex = 0;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportCameraData *report;
+ report = ATVModReport::MsgReportCameraData::create(
+ m_cameras[0].m_cameraNumber,
+ m_cameras[0].m_videoFPS,
+ m_cameras[0].m_videoFPSManual,
+ m_cameras[0].m_videoFPSManualEnable,
+ m_cameras[0].m_videoWidth,
+ m_cameras[0].m_videoHeight,
+ 0);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+}
+
+void ATVModSource::mixImageAndText(cv::Mat& image)
+{
+ int fontFace = cv::FONT_HERSHEY_PLAIN;
+ double fontScale = image.rows / 100.0;
+ int thickness = image.cols / 160;
+ int baseline=0;
+
+ fontScale = fontScale < 4.0f ? 4.0f : fontScale; // minimum size
+ cv::Size textSize = cv::getTextSize(m_settings.m_overlayText.toStdString(), fontFace, fontScale, thickness, &baseline);
+ baseline += thickness;
+
+ // position the text in the top left corner
+ cv::Point textOrg(6, textSize.height+10);
+ // then put the text itself
+ cv::putText(image, m_settings.m_overlayText.toStdString(), textOrg, fontFace, fontScale, cv::Scalar::all(255*m_settings.m_uniformLevel), thickness, CV_AA);
+}
+
+void ATVModSource::applyChannelSettings(int outputSampleRate, int inputFrequencyOffset, bool force)
+{
+ qDebug() << "ATVModSource::applyChannelSettings:"
+ << " 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)
+ {
+ getBaseValues(outputSampleRate, m_settings.m_nbLines * m_settings.m_fps, m_tvSampleRate, m_pointsPerLine);
+
+ m_settingsMutex.lock();
+
+ if (m_tvSampleRate > 0)
+ {
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = (Real) m_tvSampleRate / (Real) outputSampleRate;
+ m_interpolator.create(32,
+ m_tvSampleRate,
+ m_settings.m_rfBandwidth / getRFBandwidthDivisor(m_settings.m_atvModulation),
+ 3.0);
+ }
+ else
+ {
+ m_tvSampleRate = outputSampleRate;
+ }
+
+ m_SSBFilter->create_filter(0, m_settings.m_rfBandwidth / m_tvSampleRate);
+ memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
+ m_SSBFilterBufferIndex = 0;
+
+ applyStandard(); // set all timings
+ m_settingsMutex.unlock();
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportEffectiveSampleRate *report;
+ report = ATVModReport::MsgReportEffectiveSampleRate::create(m_tvSampleRate, m_pointsPerLine);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+
+ m_outputSampleRate = outputSampleRate;
+ m_inputFrequencyOffset = inputFrequencyOffset;
+}
+
+void ATVModSource::applySettings(const ATVModSettings& settings, bool force)
+{
+ qDebug() << "ATVModSource::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;
+
+ if ((settings.m_atvStd != m_settings.m_atvStd)
+ || (settings.m_nbLines != m_settings.m_nbLines)
+ || (settings.m_fps != m_settings.m_fps)
+ || (settings.m_rfBandwidth != m_settings.m_rfBandwidth)
+ || (settings.m_atvModulation != m_settings.m_atvModulation) || force)
+ {
+ getBaseValues(m_outputSampleRate, settings.m_nbLines * settings.m_fps, m_tvSampleRate, m_pointsPerLine);
+
+ m_settingsMutex.lock();
+
+ if (m_tvSampleRate > 0)
+ {
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = (Real) m_tvSampleRate / (Real) m_outputSampleRate;
+ m_interpolator.create(32,
+ m_tvSampleRate,
+ settings.m_rfBandwidth / getRFBandwidthDivisor(settings.m_atvModulation),
+ 3.0);
+ }
+
+ m_SSBFilter->create_filter(0, settings.m_rfBandwidth / m_tvSampleRate);
+ memset(m_SSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen>>1));
+ m_SSBFilterBufferIndex = 0;
+
+ applyStandard(); // set all timings
+ m_settingsMutex.unlock();
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportEffectiveSampleRate *report;
+ report = ATVModReport::MsgReportEffectiveSampleRate::create(m_tvSampleRate, m_pointsPerLine);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+
+ if ((settings.m_rfOppBandwidth != m_settings.m_rfOppBandwidth)
+ || (settings.m_rfBandwidth != m_settings.m_rfBandwidth)
+ || (settings.m_nbLines != m_settings.m_nbLines) // difference in line period may have changed TV sample rate
+ || (settings.m_fps != m_settings.m_fps) //
+ || force)
+ {
+ m_settingsMutex.lock();
+
+ m_DSBFilter->create_asym_filter(settings.m_rfOppBandwidth / m_tvSampleRate, settings.m_rfBandwidth / m_tvSampleRate);
+ memset(m_DSBFilterBuffer, 0, sizeof(Complex)*(m_ssbFftLen));
+ m_DSBFilterBufferIndex = 0;
+
+ m_settingsMutex.unlock();
+ }
+
+ if ((settings.m_showOverlayText != m_settings.m_showOverlayText) || force)
+ {
+ if (!m_imageFromFile.empty())
+ {
+ m_imageFromFile.copyTo(m_imageOriginal);
+
+ if (settings.m_showOverlayText) {
+ qDebug("ATVModSource::applySettings: set overlay text");
+ mixImageAndText(m_imageOriginal);
+ } else{
+ qDebug("ATVModSource::applySettings: clear overlay text");
+ }
+
+ resizeImage();
+ }
+ }
+
+ m_settings = settings;
+}
+
+void ATVModSource::reportVideoFileSourceStreamTiming()
+{
+ int framesCount;
+
+ if (m_videoOK && m_video.isOpened())
+ {
+ framesCount = m_video.get(CV_CAP_PROP_POS_FRAMES);;
+ } else {
+ framesCount = 0;
+ }
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportVideoFileSourceStreamTiming *report;
+ report = ATVModReport::MsgReportVideoFileSourceStreamTiming::create(framesCount);
+ getMessageQueueToGUI()->push(report);
+ }
+}
+
+void ATVModSource::configureCameraIndex(int index)
+{
+ if (index < m_cameras.size())
+ {
+ m_cameraIndex = index;
+
+ if (getMessageQueueToGUI())
+ {
+ ATVModReport::MsgReportCameraData *report;
+ report = ATVModReport::MsgReportCameraData::create(
+ m_cameras[m_cameraIndex].m_cameraNumber,
+ m_cameras[m_cameraIndex].m_videoFPS,
+ m_cameras[m_cameraIndex].m_videoFPSManual,
+ m_cameras[m_cameraIndex].m_videoFPSManualEnable,
+ m_cameras[m_cameraIndex].m_videoWidth,
+ m_cameras[m_cameraIndex].m_videoHeight,
+ 0);
+ getMessageQueueToGUI()->push(report);
+ }
+ }
+}
+
+void ATVModSource::configureCameraData(uint32_t index, float mnaualFPS, bool manualFPSEnable)
+{
+ if (index < m_cameras.size())
+ {
+ m_cameras[index].m_videoFPSManual = mnaualFPS;
+ m_cameras[index].m_videoFPSManualEnable = manualFPSEnable;
+ }
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modatv/atvmodsource.h b/plugins/channeltx/modatv/atvmodsource.h
new file mode 100644
index 000000000..508c2c8ef
--- /dev/null
+++ b/plugins/channeltx/modatv/atvmodsource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef PLUGINS_CHANNELTX_MODATV_ATVMODSOURCE_H_
+#define PLUGINS_CHANNELTX_MODATV_ATVMODSOURCE_H_
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#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& 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 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 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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
+ } else {
+ pixv = m_image.at(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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
+ } else {
+ pixv = m_videoFrame.at(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(2*iLineImage + oddity, pointIndex); // row (y), col (x)
+ } else {
+ pixv = camera.m_videoFrame.at(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_ */
diff --git a/plugins/channeltx/modfreedv/CMakeLists.txt b/plugins/channeltx/modfreedv/CMakeLists.txt
index 49738672a..1e7af9440 100644
--- a/plugins/channeltx/modfreedv/CMakeLists.txt
+++ b/plugins/channeltx/modfreedv/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/modfreedv/freedvmod.cpp b/plugins/channeltx/modfreedv/freedvmod.cpp
index 16f1eb633..28018617a 100644
--- a/plugins/channeltx/modfreedv/freedvmod.cpp
+++ b/plugins/channeltx/modfreedv/freedvmod.cpp
@@ -15,32 +15,30 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#include "freedvmod.h"
-
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
-#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(&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(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 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& channelSettingsKeys, const FreeDVModSettings& settings, bool force)
@@ -1067,10 +557,10 @@ void FreeDVMod::webapiReverseSendSettings(QList& 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& 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)));
}
diff --git a/plugins/channeltx/modfreedv/freedvmod.h b/plugins/channeltx/modfreedv/freedvmod.h
index a03ae0fc1..5df6108f4 100644
--- a/plugins/channeltx/modfreedv/freedvmod.h
+++ b/plugins/channeltx/modfreedv/freedvmod.h
@@ -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 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);
diff --git a/plugins/channeltx/modfreedv/freedvmodbaseband.cpp b/plugins/channeltx/modfreedv/freedvmodbaseband.cpp
new file mode 100644
index 000000000..d20e9f20e
--- /dev/null
+++ b/plugins/channeltx/modfreedv/freedvmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modfreedv/freedvmodbaseband.h b/plugins/channeltx/modfreedv/freedvmodbaseband.h
new file mode 100644
index 000000000..f0580a1b3
--- /dev/null
+++ b/plugins/channeltx/modfreedv/freedvmodbaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FREEDVMODBASEBAND_H
+#define INCLUDE_FREEDVMODBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/modfreedv/freedvmodgui.cpp b/plugins/channeltx/modfreedv/freedvmodgui.cpp
index 4129837e4..d1d36c104 100644
--- a/plugins/channeltx/modfreedv/freedvmodgui.cpp
+++ b/plugins/channeltx/modfreedv/freedvmodgui.cpp
@@ -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)
diff --git a/plugins/channeltx/modfreedv/freedvmodplugin.cpp b/plugins/channeltx/modfreedv/freedvmodplugin.cpp
index 9e5e5fec1..38acf5490 100644
--- a/plugins/channeltx/modfreedv/freedvmodplugin.cpp
+++ b/plugins/channeltx/modfreedv/freedvmodplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/modfreedv/freedvmodsource.cpp b/plugins/channeltx/modfreedv/freedvmodsource.cpp
new file mode 100644
index 000000000..4dffd7149
--- /dev/null
+++ b/plugins/channeltx/modfreedv/freedvmodsource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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(&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(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;
+}
diff --git a/plugins/channeltx/modfreedv/freedvmodsource.h b/plugins/channeltx/modfreedv/freedvmodsource.h
new file mode 100644
index 000000000..eedaa6492
--- /dev/null
+++ b/plugins/channeltx/modfreedv/freedvmodsource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FREEDVMODSOURCE_H
+#define INCLUDE_FREEDVMODSOURCE_H
+
+#include
+
+#include
+#include
+
+#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 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
diff --git a/plugins/channeltx/modfreedv/freedvmodwebapiadapter.cpp b/plugins/channeltx/modfreedv/freedvmodwebapiadapter.cpp
index 0f9e81b22..5695e8f7d 100644
--- a/plugins/channeltx/modfreedv/freedvmodwebapiadapter.cpp
+++ b/plugins/channeltx/modfreedv/freedvmodwebapiadapter.cpp
@@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
+#include "dsp/cwkeyer.h"
#include "freedvmod.h"
#include "freedvmodwebapiadapter.h"
diff --git a/plugins/channeltx/modnfm/CMakeLists.txt b/plugins/channeltx/modnfm/CMakeLists.txt
index 6281c05d4..a0907f12e 100644
--- a/plugins/channeltx/modnfm/CMakeLists.txt
+++ b/plugins/channeltx/modnfm/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp
index 5b0cb0437..ebe22247a 100644
--- a/plugins/channeltx/modnfm/nfmmod.cpp
+++ b/plugins/channeltx/modnfm/nfmmod.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include "SWGChannelSettings.h"
#include "SWGCWKeyerSettings.h"
@@ -31,13 +32,13 @@
#include
#include
-#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(&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(&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& channelSettingsKeys, const NFMModSettings& settings, bool force)
@@ -963,10 +645,10 @@ void NFMMod::webapiReverseSendSettings(QList& 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& 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)));
+}
diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h
index fcdc3acc3..13f8da675 100644
--- a/plugins/channeltx/modnfm/nfmmod.h
+++ b/plugins/channeltx/modnfm/nfmmod.h
@@ -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 m_lowpass;
- Bandpass m_bandpass;
-
- double m_magsq;
- MovingAverageUtil 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);
diff --git a/plugins/channeltx/modnfm/nfmmodbaseband.cpp b/plugins/channeltx/modnfm/nfmmodbaseband.cpp
new file mode 100644
index 000000000..7d4e4fbb4
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modnfm/nfmmodbaseband.h b/plugins/channeltx/modnfm/nfmmodbaseband.h
new file mode 100644
index 000000000..eebbac663
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodbaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_NFMMODBASEBAND_H
+#define INCLUDE_NFMMODBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp
index 4a2d988a6..b36345ae0 100644
--- a/plugins/channeltx/modnfm/nfmmodgui.cpp
+++ b/plugins/channeltx/modnfm/nfmmodgui.cpp
@@ -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);
diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp
index 7ae2d4666..77539e87c 100644
--- a/plugins/channeltx/modnfm/nfmmodplugin.cpp
+++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/modnfm/nfmmodsettings.cpp b/plugins/channeltx/modnfm/nfmmodsettings.cpp
index 8050acd75..8b1f0b7d5 100644
--- a/plugins/channeltx/modnfm/nfmmodsettings.cpp
+++ b/plugins/channeltx/modnfm/nfmmodsettings.cpp
@@ -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);
diff --git a/plugins/channeltx/modnfm/nfmmodsource.cpp b/plugins/channeltx/modnfm/nfmmodsource.cpp
new file mode 100644
index 000000000..67f4bba69
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodsource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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(&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(&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;
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modnfm/nfmmodsource.h b/plugins/channeltx/modnfm/nfmmodsource.h
new file mode 100644
index 000000000..fa5a252d5
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodsource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_NFMMODSOURCE_H
+#define INCLUDE_NFMMODSOURCE_H
+
+#include
+
+#include
+#include
+
+#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 m_lowpass;
+ Bandpass m_bandpass;
+ HighPassFilterRC m_preemphasisFilter;
+
+ double m_magsq;
+ MovingAverageUtil 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
diff --git a/plugins/channeltx/modnfm/nfmmodwebapiadapter.cpp b/plugins/channeltx/modnfm/nfmmodwebapiadapter.cpp
index f2b303fc8..646d21f52 100644
--- a/plugins/channeltx/modnfm/nfmmodwebapiadapter.cpp
+++ b/plugins/channeltx/modnfm/nfmmodwebapiadapter.cpp
@@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
+#include "dsp/cwkeyer.h"
#include "nfmmod.h"
#include "nfmmodwebapiadapter.h"
diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt
index a010be184..47ef58163 100644
--- a/plugins/channeltx/modssb/CMakeLists.txt
+++ b/plugins/channeltx/modssb/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp
index babe750f1..43b1ac245 100644
--- a/plugins/channeltx/modssb/ssbmod.cpp
+++ b/plugins/channeltx/modssb/ssbmod.cpp
@@ -15,14 +15,13 @@
// along with this program. If not, see . //
///////////////////////////////////////////////////////////////////////////////////
-#include "ssbmod.h"
-
#include
#include
#include
#include
#include
#include
+#include
#include
#include
@@ -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(&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(&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(&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& channelSettingsKeys, const SSBModSettings& settings, bool force)
@@ -1275,10 +624,10 @@ void SSBMod::webapiReverseSendSettings(QList& 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& 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);
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h
index 2c6a137ac..2a31cfde8 100644
--- a/plugins/channeltx/modssb/ssbmod.h
+++ b/plugins/channeltx/modssb/ssbmod.h
@@ -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 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);
diff --git a/plugins/channeltx/modssb/ssbmodbaseband.cpp b/plugins/channeltx/modssb/ssbmodbaseband.cpp
new file mode 100644
index 000000000..269837320
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modssb/ssbmodbaseband.h b/plugins/channeltx/modssb/ssbmodbaseband.h
new file mode 100644
index 000000000..31294b59d
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodbaseband.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SSBMODBASEBAND_H
+#define INCLUDE_SSBMODBASEBAND_H
+
+#include
+#include
+
+#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
diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp
index a8c31aedc..aff4d602d 100644
--- a/plugins/channeltx/modssb/ssbmodgui.cpp
+++ b/plugins/channeltx/modssb/ssbmodgui.cpp
@@ -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);
diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp
index 3ead9f6cf..23e0b7452 100644
--- a/plugins/channeltx/modssb/ssbmodplugin.cpp
+++ b/plugins/channeltx/modssb/ssbmodplugin.cpp
@@ -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,
diff --git a/plugins/channeltx/modssb/ssbmodsource.cpp b/plugins/channeltx/modssb/ssbmodsource.cpp
new file mode 100644
index 000000000..89e831f20
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodsource.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#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(&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(&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(&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;
+}
diff --git a/plugins/channeltx/modssb/ssbmodsource.h b/plugins/channeltx/modssb/ssbmodsource.h
new file mode 100644
index 000000000..2146e7b71
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodsource.h
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_SSBMODSOURCE_H
+#define INCLUDE_SSBMODSOURCE_H
+
+#include
+
+#include
+#include
+
+#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 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
diff --git a/plugins/channeltx/modssb/ssbmodwebapiadapter.cpp b/plugins/channeltx/modssb/ssbmodwebapiadapter.cpp
index e34d72c16..902327cf3 100644
--- a/plugins/channeltx/modssb/ssbmodwebapiadapter.cpp
+++ b/plugins/channeltx/modssb/ssbmodwebapiadapter.cpp
@@ -16,6 +16,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
+#include "dsp/cwkeyer.h"
#include "ssbmod.h"
#include "ssbmodwebapiadapter.h"
diff --git a/plugins/channeltx/modwfm/CMakeLists.txt b/plugins/channeltx/modwfm/CMakeLists.txt
index 0bac9ed41..7ebc3310c 100644
--- a/plugins/channeltx/modwfm/CMakeLists.txt
+++ b/plugins/channeltx/modwfm/CMakeLists.txt
@@ -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
diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp
index fa1b9261f..8f2bc8248 100644
--- a/plugins/channeltx/modwfm/wfmmod.cpp
+++ b/plugins/channeltx/modwfm/wfmmod.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
@@ -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(&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(&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& channelSettingsKeys, const WFMModSettings& settings, bool force)
@@ -831,10 +556,10 @@ void WFMMod::webapiReverseSendSettings(QList& 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& 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)));
}
diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h
index 97c160172..be662a0c7 100644
--- a/plugins/channeltx/modwfm/wfmmod.h
+++ b/plugins/channeltx/modwfm/wfmmod.h
@@ -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 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);
diff --git a/plugins/channeltx/modwfm/wfmmodbaseband.cpp b/plugins/channeltx/modwfm/wfmmodbaseband.cpp
new file mode 100644
index 000000000..0582701c6
--- /dev/null
+++ b/plugins/channeltx/modwfm/wfmmodbaseband.cpp
@@ -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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "dsp/upsamplechannelizer.h"
+#include "dsp/dspengine.h"
+#include "dsp/dspcommands.h"
+
+#include "wfmmodbaseband.h"
+
+MESSAGE_CLASS_DEFINITION(WFMModBaseband::MsgConfigureWFMModBaseband, Message)
+MESSAGE_CLASS_DEFINITION(WFMModBaseband::MsgConfigureChannelizer, Message)
+
+WFMModBaseband::WFMModBaseband() :
+ m_mutex(QMutex::Recursive)
+{
+ m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
+ m_channelizer = new UpSampleChannelizer(&m_source);
+
+ qDebug("WFMModBaseband::WFMModBaseband");
+ QObject::connect(
+ &m_sampleFifo,
+ &SampleSourceFifo::dataRead,
+ this,
+ &WFMModBaseband::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()));
+}
+
+WFMModBaseband::~WFMModBaseband()
+{
+ DSPEngine::instance()->getAudioDeviceManager()->removeAudioSource(m_source.getAudioFifo());
+ delete m_channelizer;
+}
+
+void WFMModBaseband::reset()
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ m_sampleFifo.reset();
+}
+
+void WFMModBaseband::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 WFMModBaseband::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 WFMModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
+{
+ m_channelizer->prefetch(iEnd - iBegin);
+ m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
+}
+
+void WFMModBaseband::handleInputMessages()
+{
+ Message* message;
+
+ while ((message = m_inputMessageQueue.pop()) != nullptr)
+ {
+ if (handleMessage(*message)) {
+ delete message;
+ }
+ }
+}
+
+bool WFMModBaseband::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() << "WFMModBaseband::handleMessage: DSPConfigureAudio:"
+ << " sampleRate: " << sampleRate
+ << " audioType: " << audioType;
+
+ if (audioType == DSPConfigureAudio::AudioInput)
+ {
+ if (sampleRate != m_source.getAudioSampleRate()) {
+ m_source.applyAudioSampleRate(sampleRate);
+ }
+ }
+
+ return true;
+ }
+ else if (MsgConfigureWFMModBaseband::match(cmd))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ MsgConfigureWFMModBaseband& cfg = (MsgConfigureWFMModBaseband&) cmd;
+ qDebug() << "WFMModBaseband::handleMessage: MsgConfigureWFMModBaseband";
+
+ applySettings(cfg.getSettings(), cfg.getForce());
+
+ return true;
+ }
+ else if (MsgConfigureChannelizer::match(cmd))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
+ qDebug() << "WFMModBaseband::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() << "WFMModBaseband::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 WFMModBaseband::applySettings(const WFMModSettings& settings, bool force)
+{
+ m_source.applySettings(settings, force);
+ m_settings = settings;
+}
+
+int WFMModBaseband::getChannelSampleRate() const
+{
+ return m_channelizer->getChannelSampleRate();
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modwfm/wfmmodbaseband.h b/plugins/channeltx/modwfm/wfmmodbaseband.h
new file mode 100644
index 000000000..2b307a826
--- /dev/null
+++ b/plugins/channeltx/modwfm/wfmmodbaseband.h
@@ -0,0 +1,121 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_WFMMODBASEBAND_H
+#define INCLUDE_WFMMODBASEBAND_H
+
+#include
+#include
+
+#include "dsp/samplesourcefifo.h"
+#include "util/message.h"
+#include "util/messagequeue.h"
+
+#include "wfmmodsource.h"
+
+class UpSampleChannelizer;
+
+class WFMModBaseband : public QObject
+{
+ Q_OBJECT
+public:
+ class MsgConfigureWFMModBaseband : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const WFMModSettings& getSettings() const { return m_settings; }
+ bool getForce() const { return m_force; }
+
+ static MsgConfigureWFMModBaseband* create(const WFMModSettings& settings, bool force)
+ {
+ return new MsgConfigureWFMModBaseband(settings, force);
+ }
+
+ private:
+ WFMModSettings m_settings;
+ bool m_force;
+
+ MsgConfigureWFMModBaseband(const WFMModSettings& 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)
+ { }
+ };
+
+ WFMModBaseband();
+ ~WFMModBaseband();
+ 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(); }
+ int getChannelSampleRate() const;
+ void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
+ AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
+
+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;
+ WFMModSource m_source;
+ MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
+ WFMModSettings m_settings;
+ QMutex m_mutex;
+
+ void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
+ bool handleMessage(const Message& cmd);
+ void applySettings(const WFMModSettings& settings, bool force = false);
+
+private slots:
+ void handleInputMessages();
+ void handleData(); //!< Handle data when samples have to be processed
+};
+
+
+#endif // INCLUDE_WFMMODBASEBAND_H
diff --git a/plugins/channeltx/modwfm/wfmmodgui.cpp b/plugins/channeltx/modwfm/wfmmodgui.cpp
index c3ae5d565..1b3d33a54 100644
--- a/plugins/channeltx/modwfm/wfmmodgui.cpp
+++ b/plugins/channeltx/modwfm/wfmmodgui.cpp
@@ -22,12 +22,11 @@
#include
#include "device/deviceuiset.h"
-#include "dsp/upchannelizer.h"
-#include "dsp/threadedbasebandsamplesource.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"
@@ -382,7 +381,7 @@ WFMModGUI::WFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
m_settings.setCWKeyerGUI(ui->cwKeyerGUI);
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
- connect(m_wfmMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
+ m_wfmMod->setLevelMeter(ui->volumeMeter);
displaySettings();
applySettings(true);
diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp
index 39d074c6b..3deb38f36 100644
--- a/plugins/channeltx/modwfm/wfmmodplugin.cpp
+++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp
@@ -27,7 +27,7 @@
const PluginDescriptor WFMModPlugin::m_pluginDescriptor = {
QString("WFM Modulator"),
- QString("4.11.6"),
+ QString("4.12.0"),
QString("(c) Edouard Griffiths, F4EXB"),
QString("https://github.com/f4exb/sdrangel"),
true,
diff --git a/plugins/channeltx/modwfm/wfmmodsource.cpp b/plugins/channeltx/modwfm/wfmmodsource.cpp
new file mode 100644
index 000000000..cb370d7bb
--- /dev/null
+++ b/plugins/channeltx/modwfm/wfmmodsource.cpp
@@ -0,0 +1,298 @@
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "wfmmodsource.h"
+
+const int WFMModSource::m_rfFilterFFTLength = 1024;
+const int WFMModSource::m_levelNbSamples = 480; // every 10ms
+
+WFMModSource::WFMModSource() :
+ m_channelSampleRate(384000),
+ m_channelFrequencyOffset(0),
+ m_modPhasor(0.0f),
+ m_audioFifo(4800),
+ m_levelCalcCount(0),
+ m_peakLevel(0.0f),
+ m_levelSum(0.0f),
+ m_ifstream(nullptr),
+ m_audioSampleRate(48000)
+{
+ 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});
+ m_rfFilterBufferIndex = 0;
+ m_audioBuffer.resize(1<<14);
+ m_audioBufferFill = 0;
+ m_magsq = 0.0;
+
+ applySettings(m_settings, true);
+ applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
+}
+
+WFMModSource::~WFMModSource()
+{
+ delete m_rfFilter;
+ delete[] m_rfFilterBuffer;
+}
+
+void WFMModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
+{
+ std::for_each(
+ begin,
+ begin + nbSamples,
+ [this](Sample& s) {
+ pullOne(s);
+ }
+ );
+}
+
+void WFMModSource::pullOne(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;
+ Real t;
+
+ if ((m_settings.m_modAFInput == WFMModSettings::WFMModInputFile)
+ || (m_settings.m_modAFInput == WFMModSettings::WFMModInputAudio))
+ {
+ if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri))
+ {
+ pullAF(t);
+ calculateLevel(t);
+ m_modSample.real(t);
+ m_modSample.imag(0.0f);
+ m_audioBufferFill++;
+ }
+
+ t = ri.real();
+ m_interpolatorDistanceRemain += m_interpolatorDistance;
+ }
+ else
+ {
+ pullAF(t);
+ calculateLevel(t);
+ }
+
+ m_modPhasor += (m_settings.m_fmDeviation / (float) m_channelSampleRate) * t * M_PI * 2.0f;
+
+ // limit phasor range to ]-pi,pi]
+ if (m_modPhasor > M_PI) {
+ m_modPhasor -= (2.0f * M_PI);
+ }
+
+ 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++;
+
+ 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 WFMModSource::prefetch(unsigned int nbSamples)
+{
+ unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
+ pullAudio(nbSamplesAudio);
+}
+
+void WFMModSource::pullAudio(unsigned int nbSamplesAudio)
+{
+ if (nbSamplesAudio > m_audioBuffer.size()) {
+ m_audioBuffer.resize(nbSamplesAudio);
+ }
+
+ m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio);
+ m_audioBufferFill = 0;
+}
+
+void WFMModSource::pullAF(Real& sample)
+{
+ switch (m_settings.m_modAFInput)
+ {
+ case WFMModSettings::WFMModInputTone:
+ sample = m_toneNco.next() * m_settings.m_volumeFactor;
+ 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 && 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
+ {
+ Real s;
+ m_ifstream->read(reinterpret_cast(&s), sizeof(Real));
+ sample = s * m_settings.m_volumeFactor;
+ }
+ }
+ else
+ {
+ sample = 0.0f;
+ }
+ break;
+ case WFMModSettings::WFMModInputAudio:
+ {
+ sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
+ }
+ break;
+ case WFMModSettings::WFMModInputCWTone:
+ Real fadeFactor;
+
+ if (m_cwKeyer.getSample())
+ {
+ m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
+ sample = m_toneNco.next() * m_settings.m_volumeFactor * fadeFactor;
+ }
+ else
+ {
+ if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
+ {
+ sample = m_toneNco.next() * m_settings.m_volumeFactor * fadeFactor;
+ }
+ else
+ {
+ sample = 0.0f;
+ m_toneNco.setPhase(0);
+ }
+ }
+ break;
+ case WFMModSettings::WFMModInputNone:
+ default:
+ sample = 0.0f;
+ break;
+ }
+}
+
+void WFMModSource::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
+ {
+ m_rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
+ m_peakLevelOut = m_peakLevel;
+ m_peakLevel = 0.0f;
+ m_levelSum = 0.0f;
+ m_levelCalcCount = 0;
+ }
+}
+
+void WFMModSource::applyAudioSampleRate(unsigned int sampleRate)
+{
+ qDebug("WFMModSource::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_audioSampleRate = sampleRate;
+}
+
+void WFMModSource::applySettings(const WFMModSettings& settings, bool force)
+{
+ if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force)
+ {
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorConsumed = false;
+ m_interpolatorDistance = (Real) m_audioSampleRate / (Real) m_channelSampleRate;
+ m_interpolator.create(48, m_audioSampleRate, settings.m_afBandwidth / 2.2, 3.0);
+ }
+
+ if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
+ {
+ Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
+ Real hiCut = (settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
+ m_rfFilter->create_filter(lowCut, hiCut);
+ }
+
+ if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || force) {
+ m_toneNco.setFreq(settings.m_toneFrequency, m_channelSampleRate);
+ }
+
+ m_settings = settings;
+}
+
+void WFMModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
+{
+ qDebug() << "WFMModSource::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_afBandwidth / 2.2, 3.0);
+ Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
+ Real hiCut = (m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
+ m_rfFilter->create_filter(lowCut, hiCut);
+ m_toneNco.setFreq(m_settings.m_toneFrequency, channelSampleRate);
+ m_cwKeyer.setSampleRate(channelSampleRate);
+ m_cwKeyer.reset();
+ }
+
+ m_channelSampleRate = channelSampleRate;
+ m_channelFrequencyOffset = channelFrequencyOffset;
+}
\ No newline at end of file
diff --git a/plugins/channeltx/modwfm/wfmmodsource.h b/plugins/channeltx/modwfm/wfmmodsource.h
new file mode 100644
index 000000000..f75378ecd
--- /dev/null
+++ b/plugins/channeltx/modwfm/wfmmodsource.h
@@ -0,0 +1,108 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_WFMMODSOURCE_H
+#define INCLUDE_WFMMODSOURCE_H
+
+#include
+
+#include