diff --git a/plugins/samplesource/testsource/readme.md b/plugins/samplesource/testsource/readme.md
index 1327e1459..9520782cc 100644
--- a/plugins/samplesource/testsource/readme.md
+++ b/plugins/samplesource/testsource/readme.md
@@ -83,10 +83,10 @@ This controls the generator sample rate in samples per second.
- Sync pattern: 010 at full amplitude
- Binary pattern LSB first on 3 bits from 0 to 7 at 0.3 amplitude
- **P1**: Pattern 1 is a sawtooth pattern
- - Pulse width: 1000 samples
+ - Pulse width: Set by period setting
- Starts at full amplitude then amplitude decreases linearly down to zero
- - **P2**: Pattern 2 is a 50% duty cycle square pattern
- - Pulse width: 1000 samples
+ - **P2**: Pattern 2 is a square pattern
+ - Pulse width: Set by period and duty cycle settings
- Starts with a full amplitude pulse then down to zero for the duration of one pulse
5: Modulating tone frequency
@@ -105,6 +105,14 @@ This controls the AM modulation factor from 0 to 99%
This controls the frequency modulation deviation in kHz in 100 Hz steps. It cannot exceed the sample rate.
+Period
+
+Specifies the period of the P1 and P2 patterns in samples. The minimum is one sample.
+
+Duty Cycle
+
+Specifies the duty cycle for the P2 pattern. This is the percent of time period the signal is high.
+
9: Amplitude coarse control
This slider controls the number of amplitude bits by steps of 100 bits. The total number of amplitude bits appear on the right.
diff --git a/plugins/samplesource/testsource/testsourcegui.cpp b/plugins/samplesource/testsource/testsourcegui.cpp
index 2c1d7cdef..3cafa5304 100644
--- a/plugins/samplesource/testsource/testsourcegui.cpp
+++ b/plugins/samplesource/testsource/testsourcegui.cpp
@@ -290,6 +290,42 @@ void TestSourceGui::on_phaseImbalance_valueChanged(int value)
sendSettings();
}
+void TestSourceGui::on_period_valueChanged(int value)
+{
+ m_settings.m_period = value;
+ displayPeriod();
+ m_settingsKeys.append("period");
+ sendSettings();
+}
+
+void TestSourceGui::on_dutyCycle_valueChanged(int value)
+{
+ m_settings.m_dutyCycle = value;
+ ui->dutyCycleText->setText(QString("%1").arg(m_settings.m_dutyCycle));
+ m_settingsKeys.append("dutyCycle");
+ sendSettings();
+}
+
+void TestSourceGui::displayPeriod()
+{
+ float periodSecs = m_settings.m_period / (float) m_settings.m_sampleRate;
+ QString periodText;
+
+ if (periodSecs >= 1.0) {
+ periodText = QString("%1s").arg(periodSecs, 0, 'f', 1);
+ } else if (periodSecs >= 1e-3) {
+ periodText = QString("%1ms").arg(periodSecs * 1e3, 0, 'f', 1);
+ } else if (periodSecs >= 1e-6) {
+ periodText = QString("%1us").arg(periodSecs * 1e6, 0, 'f', 1);
+ } else if (periodSecs >= 1e-9) {
+ periodText = QString("%1ns").arg(periodSecs * 1e9, 0, 'f', 1);
+ } else {
+ periodText = QString("%1ps").arg(periodSecs * 1e12, 0, 'f', 1);
+ }
+
+ ui->periodText->setText(QString("(%1)").arg(periodText));
+}
+
void TestSourceGui::displayAmplitude()
{
int amplitudeInt = ui->amplitudeCoarse->value() * 100 + ui->amplitudeFine->value();
@@ -407,6 +443,10 @@ void TestSourceGui::displaySettings()
ui->amModulationText->setText(QString("%1").arg(m_settings.m_amModulation));
ui->fmDeviation->setValue(m_settings.m_fmDeviation);
ui->fmDeviationText->setText(QString("%1").arg(m_settings.m_fmDeviation / 10.0, 0, 'f', 1));
+ ui->period->setValue(m_settings.m_period);
+ displayPeriod();
+ ui->dutyCycle->setValue(m_settings.m_dutyCycle);
+ ui->dutyCycleText->setText(QString("%1").arg(m_settings.m_dutyCycle));
blockApplySettings(false);
}
@@ -583,4 +623,6 @@ void TestSourceGui::makeUIConnections()
QObject::connect(ui->iBias, &QSlider::valueChanged, this, &TestSourceGui::on_iBias_valueChanged);
QObject::connect(ui->qBias, &QSlider::valueChanged, this, &TestSourceGui::on_qBias_valueChanged);
QObject::connect(ui->phaseImbalance, &QSlider::valueChanged, this, &TestSourceGui::on_phaseImbalance_valueChanged);
+ QObject::connect(ui->period, QOverload::of(&QSpinBox::valueChanged), this, &TestSourceGui::on_period_valueChanged);
+ QObject::connect(ui->dutyCycle, &QDial::valueChanged, this, &TestSourceGui::on_dutyCycle_valueChanged);
}
diff --git a/plugins/samplesource/testsource/testsourcegui.h b/plugins/samplesource/testsource/testsourcegui.h
index 4151e9197..38df7bd4c 100644
--- a/plugins/samplesource/testsource/testsourcegui.h
+++ b/plugins/samplesource/testsource/testsourcegui.h
@@ -68,6 +68,7 @@ private:
void sendSettings();
void updateSampleRateAndFrequency();
void displayAmplitude();
+ void displayPeriod();
void updateAmpCoarseLimit();
void updateAmpFineLimit();
void updateFrequencyShiftLimit();
@@ -94,6 +95,8 @@ private slots:
void on_iBias_valueChanged(int value);
void on_qBias_valueChanged(int value);
void on_phaseImbalance_valueChanged(int value);
+ void on_period_valueChanged(int value);
+ void on_dutyCycle_valueChanged(int value);
void openDeviceSettingsDialog(const QPoint& p);
void updateStatus();
void updateHardware();
diff --git a/plugins/samplesource/testsource/testsourcegui.ui b/plugins/samplesource/testsource/testsourcegui.ui
index 34984691c..2b95861a5 100644
--- a/plugins/samplesource/testsource/testsourcegui.ui
+++ b/plugins/samplesource/testsource/testsourcegui.ui
@@ -7,7 +7,7 @@
0
0
360
- 331
+ 343
@@ -696,6 +696,116 @@
+ -
+
+
-
+
+
+ Period
+
+
+
+ -
+
+
+ Period for P1 and P2 patterns as number of samples
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 1
+
+
+ 1000000000
+
+
+
+ -
+
+
+ S
+
+
+
+ -
+
+
+ (0.0ms)
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Duty cycle
+
+
+
+ -
+
+
+
+ 22
+ 22
+
+
+
+ Duty cycle for P2 pattern in percent
+
+
+ 100
+
+
+ 1
+
+
+
+ -
+
+
+
+ 26
+ 0
+
+
+
+ 100
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ %
+
+
+
+
+
-
diff --git a/plugins/samplesource/testsource/testsourceinput.cpp b/plugins/samplesource/testsource/testsourceinput.cpp
index 093c5d5bd..12307a56c 100644
--- a/plugins/samplesource/testsource/testsourceinput.cpp
+++ b/plugins/samplesource/testsource/testsourceinput.cpp
@@ -102,7 +102,7 @@ bool TestSourceInput::start()
m_testSourceWorker->moveToThread(m_testSourceWorkerThread);
QObject::connect(m_testSourceWorkerThread, &QThread::started, m_testSourceWorker, &TestSourceWorker::startWork);
- QObject::connect(m_testSourceWorkerThread, &QThread::finished, m_testSourceWorker, &QObject::deleteLater, Qt::QueuedConnection);
+ QObject::connect(m_testSourceWorkerThread, &QThread::finished, m_testSourceWorker, &QObject::deleteLater);
QObject::connect(m_testSourceWorkerThread, &QThread::finished, m_testSourceWorkerThread, &QThread::deleteLater);
m_testSourceWorker->setSamplerate(m_settings.m_sampleRate);
@@ -128,9 +128,18 @@ void TestSourceInput::stop()
if (m_testSourceWorkerThread)
{
- m_testSourceWorker->stopWork();
+ // The worker's timer lives in the worker thread and Qt will not let this one stop it, so
+ // ask the worker to do it and wait for that before the event loop goes away. Where there is
+ // no waiting for the thread there is no blocking on it either: the worker's destructor,
+ // which does run in the worker thread, stops the timer there instead
+ TestSourceWorker *worker = m_testSourceWorker;
+#ifndef __EMSCRIPTEN__
+ QMetaObject::invokeMethod(worker, [worker]() { worker->stopWork(); }, Qt::BlockingQueuedConnection);
+#endif
m_testSourceWorkerThread->quit();
+#ifndef __EMSCRIPTEN__
m_testSourceWorkerThread->wait();
+#endif
m_testSourceWorker = nullptr;
m_testSourceWorkerThread = nullptr;
}
@@ -380,7 +389,9 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, const QL
}
}
- if (settingsKeys.contains("modulation") || force)
+ if (settingsKeys.contains("modulation")
+ || settingsKeys.contains("period")
+ || settingsKeys.contains("dutyCycle") || force)
{
if (m_testSourceWorker != 0)
{
@@ -389,9 +400,9 @@ bool TestSourceInput::applySettings(const TestSourceSettings& settings, const QL
if (settings.m_modulation == TestSourceSettings::ModulationPattern0) {
m_testSourceWorker->setPattern0();
} else if (settings.m_modulation == TestSourceSettings::ModulationPattern1) {
- m_testSourceWorker->setPattern1();
+ m_testSourceWorker->setPattern1(settings.m_period);
} else if (settings.m_modulation == TestSourceSettings::ModulationPattern2) {
- m_testSourceWorker->setPattern2();
+ m_testSourceWorker->setPattern2(settings.m_period, settings.m_dutyCycle);
}
}
}
@@ -554,6 +565,14 @@ void TestSourceInput::webapiUpdateDeviceSettings(
if (deviceSettingsKeys.contains("phaseImbalance")) {
settings.m_phaseImbalance = response.getTestSourceSettings()->getPhaseImbalance();
};
+ if (deviceSettingsKeys.contains("period")) {
+ int period = response.getTestSourceSettings()->getPeriod();
+ settings.m_period = period < 1 ? 1 : period;
+ };
+ if (deviceSettingsKeys.contains("dutyCycle")) {
+ int dutyCycle = response.getTestSourceSettings()->getDutyCycle();
+ settings.m_dutyCycle = dutyCycle < 0 ? 0 : dutyCycle > 100 ? 100 : dutyCycle;
+ };
if (deviceSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getTestSourceSettings()->getUseReverseApi() != 0;
}
@@ -592,6 +611,8 @@ void TestSourceInput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings&
response.getTestSourceSettings()->setIFactor(settings.m_iFactor);
response.getTestSourceSettings()->setQFactor(settings.m_qFactor);
response.getTestSourceSettings()->setPhaseImbalance(settings.m_phaseImbalance);
+ response.getTestSourceSettings()->setPeriod(settings.m_period);
+ response.getTestSourceSettings()->setDutyCycle(settings.m_dutyCycle);
response.getTestSourceSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
@@ -667,6 +688,12 @@ void TestSourceInput::webapiReverseSendSettings(const QList& deviceSett
if (deviceSettingsKeys.contains("phaseImbalance") || force) {
swgTestSourceSettings->setPhaseImbalance(settings.m_phaseImbalance);
};
+ if (deviceSettingsKeys.contains("period") || force) {
+ swgTestSourceSettings->setPeriod(settings.m_period);
+ };
+ if (deviceSettingsKeys.contains("dutyCycle") || force) {
+ swgTestSourceSettings->setDutyCycle(settings.m_dutyCycle);
+ };
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings")
.arg(settings.m_reverseAPIAddress)
diff --git a/plugins/samplesource/testsource/testsourcesettings.cpp b/plugins/samplesource/testsource/testsourcesettings.cpp
index 4b946b488..50c9c46b1 100644
--- a/plugins/samplesource/testsource/testsourcesettings.cpp
+++ b/plugins/samplesource/testsource/testsourcesettings.cpp
@@ -45,6 +45,8 @@ void TestSourceSettings::resetToDefaults()
m_iFactor = 0.0f;
m_qFactor = 0.0f;
m_phaseImbalance = 0.0f;
+ m_period = 1000;
+ m_dutyCycle = 50;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
@@ -75,6 +77,8 @@ QByteArray TestSourceSettings::serialize() const
s.writeString(19, m_reverseAPIAddress);
s.writeU32(20, m_reverseAPIPort);
s.writeU32(21, m_reverseAPIDeviceIndex);
+ s.writeS32(22, m_period);
+ s.writeS32(23, m_dutyCycle);
return s.final();
}
@@ -139,6 +143,11 @@ bool TestSourceSettings::deserialize(const QByteArray& data)
d.readU32(21, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
+ d.readS32(22, &m_period, 1000);
+ m_period = m_period < 1 ? 1 : m_period;
+ d.readS32(23, &m_dutyCycle, 50);
+ m_dutyCycle = m_dutyCycle < 0 ? 0 : m_dutyCycle > 100 ? 100 : m_dutyCycle;
+
return true;
}
else
@@ -201,6 +210,12 @@ void TestSourceSettings::applySettings(const QStringList& settingsKeys, const Te
if (settingsKeys.contains("phaseImbalance")) {
m_phaseImbalance = settings.m_phaseImbalance;
}
+ if (settingsKeys.contains("period")) {
+ m_period = settings.m_period;
+ }
+ if (settingsKeys.contains("dutyCycle")) {
+ m_dutyCycle = settings.m_dutyCycle;
+ }
if (settingsKeys.contains("useReverseAPI")) {
m_useReverseAPI = settings.m_useReverseAPI;
}
@@ -270,6 +285,12 @@ QString TestSourceSettings::getDebugString(const QStringList& settingsKeys, bool
if (settingsKeys.contains("phaseImbalance") || force) {
ostr << " m_phaseImbalance: " << m_phaseImbalance;
}
+ if (settingsKeys.contains("period") || force) {
+ ostr << " m_period: " << m_period;
+ }
+ if (settingsKeys.contains("dutyCycle") || force) {
+ ostr << " m_dutyCycle: " << m_dutyCycle;
+ }
if (settingsKeys.contains("useReverseAPI") || force) {
ostr << " m_useReverseAPI: " << m_useReverseAPI;
}
diff --git a/plugins/samplesource/testsource/testsourcesettings.h b/plugins/samplesource/testsource/testsourcesettings.h
index 4888faf36..fbe3cd1f7 100644
--- a/plugins/samplesource/testsource/testsourcesettings.h
+++ b/plugins/samplesource/testsource/testsourcesettings.h
@@ -64,6 +64,8 @@ struct TestSourceSettings {
float m_iFactor; //!< -1.0 < x < 1.0
float m_qFactor; //!< -1.0 < x < 1.0
float m_phaseImbalance; //!< -1.0 < x < 1.0
+ qint32 m_period; //!< Period of the P1 and P2 patterns in samples
+ qint32 m_dutyCycle; //!< Percentage of the period the P2 pattern is high
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
diff --git a/plugins/samplesource/testsource/testsourceworker.cpp b/plugins/samplesource/testsource/testsourceworker.cpp
index c868ac992..2cd14b5e1 100644
--- a/plugins/samplesource/testsource/testsourceworker.cpp
+++ b/plugins/samplesource/testsource/testsourceworker.cpp
@@ -29,7 +29,6 @@
TestSourceWorker::TestSourceWorker(SampleSinkFifo* sampleFifo, QObject* parent) :
QObject(parent),
- m_running(false),
m_buf(0),
m_bufsize(0),
m_chunksize(0),
@@ -42,6 +41,7 @@ TestSourceWorker::TestSourceWorker(SampleSinkFifo* sampleFifo, QObject* parent)
m_fmDeviationUnit(0.0f),
m_fmPhasor(0.0f),
m_pulseWidth(150),
+ m_period(2000),
m_pulseSampleCount(0),
m_pulsePatternCount(0),
m_pulsePatternCycle(8),
@@ -62,31 +62,32 @@ TestSourceWorker::TestSourceWorker(SampleSinkFifo* sampleFifo, QObject* parent)
m_frequency(435*1000),
m_fcPosShift(0),
m_throttlems(TESTSOURCE_THROTTLE_MS),
+ m_timer(this),
m_throttleToggle(false),
m_histoCounter(0)
{
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection);
connect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
m_timer.setTimerType(Qt::PreciseTimer);
- m_timer.start(50);
}
TestSourceWorker::~TestSourceWorker()
{
- m_timer.stop();
+ qDebug("TestSourceWorker::~TestSourceWorker");
+ stopWork();
disconnect(&m_timer, SIGNAL(timeout()), this, SLOT(tick()));
}
void TestSourceWorker::startWork()
{
qDebug("TestSourceWorker::startWork");
- m_running = true;
+ m_timer.start(50);
}
void TestSourceWorker::stopWork()
{
qDebug("TestSourceWorker::stopWork");
- m_running = false;
+ m_timer.stop();
}
void TestSourceWorker::setSamplerate(int samplerate)
@@ -316,7 +317,7 @@ void TestSourceWorker::generate(quint32 chunksize)
m_buf[i++] = 0;
}
- if (m_pulseSampleCount < 2*m_pulseWidth - 1) {
+ if (m_pulseSampleCount < m_period - 1) {
m_pulseSampleCount++;
} else {
m_pulseSampleCount = 0;
@@ -366,39 +367,36 @@ void TestSourceWorker::callback(const qint16* buf, qint32 len)
void TestSourceWorker::tick()
{
- if (m_running)
- {
- qint64 throttlems = m_elapsedTimer.restart();
+ qint64 throttlems = m_elapsedTimer.restart();
- std::map::iterator it;
- it = m_timerHistogram.find(throttlems);
+ std::map::iterator it;
+ it = m_timerHistogram.find(throttlems);
- if (it == m_timerHistogram.end()) {
- m_timerHistogram[throttlems] = 1;
- } else {
- it->second++;
- }
-
- if (m_histoCounter < 49) {
- m_histoCounter++;
- } else {
- // qDebug("TestSourceWorker::tick: -----------");
- // for (std::map::iterator it = m_timerHistogram.begin(); it != m_timerHistogram.end(); ++it) {
- // qDebug("TestSourceWorker::tick: %d: %d", it->first, it->second);
- // }
- m_histoCounter = 0;
- }
-
- if ((throttlems > 45) && (throttlems < 55) && (throttlems != m_throttlems))
- {
- QMutexLocker mutexLocker(&m_mutex);
- m_throttlems = throttlems;
- m_chunksize = 4 * ((m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000);
- m_throttleToggle = !m_throttleToggle;
- }
-
- generate(m_chunksize);
+ if (it == m_timerHistogram.end()) {
+ m_timerHistogram[throttlems] = 1;
+ } else {
+ it->second++;
}
+
+ if (m_histoCounter < 49) {
+ m_histoCounter++;
+ } else {
+ // qDebug("TestSourceWorker::tick: -----------");
+ // for (std::map::iterator it = m_timerHistogram.begin(); it != m_timerHistogram.end(); ++it) {
+ // qDebug("TestSourceWorker::tick: %d: %d", it->first, it->second);
+ // }
+ m_histoCounter = 0;
+ }
+
+ if ((throttlems > 45) && (throttlems < 55) && (throttlems != m_throttlems))
+ {
+ QMutexLocker mutexLocker(&m_mutex);
+ m_throttlems = throttlems;
+ m_chunksize = 4 * ((m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000);
+ m_throttleToggle = !m_throttleToggle;
+ }
+
+ generate(m_chunksize);
}
void TestSourceWorker::handleInputMessages()
@@ -414,14 +412,16 @@ void TestSourceWorker::setPattern0()
m_pulsePatternPlaces = 3;
}
-void TestSourceWorker::setPattern1()
+void TestSourceWorker::setPattern1(int period)
{
- m_pulseWidth = 1000;
+ m_pulseWidth = period < 1 ? 1 : period;
m_pulseSampleCount = 0;
}
-void TestSourceWorker::setPattern2()
+void TestSourceWorker::setPattern2(int period, int dutyCycle)
{
- m_pulseWidth = 1000;
+ m_period = period < 1 ? 1 : period;
+ dutyCycle = dutyCycle < 0 ? 0 : dutyCycle > 100 ? 100 : dutyCycle;
+ m_pulseWidth = m_period * (dutyCycle / 100.0f);
m_pulseSampleCount = 0;
}
diff --git a/plugins/samplesource/testsource/testsourceworker.h b/plugins/samplesource/testsource/testsourceworker.h
index e8084c78f..378f45f06 100644
--- a/plugins/samplesource/testsource/testsourceworker.h
+++ b/plugins/samplesource/testsource/testsourceworker.h
@@ -59,12 +59,10 @@ public:
void setAMModulation(float amModulation);
void setFMDeviation(float deviation);
void setPattern0();
- void setPattern1();
- void setPattern2();
+ void setPattern1(int period);
+ void setPattern2(int period, int dutyCycle);
private:
- volatile bool m_running;
-
qint16 *m_buf;
quint32 m_bufsize;
quint32 m_chunksize;
@@ -79,6 +77,7 @@ private:
float m_fmDeviationUnit;
float m_fmPhasor;
uint32_t m_pulseWidth; //!< pulse width in number of samples
+ uint32_t m_period; //!< period of the P2 pattern in number of samples
uint32_t m_pulseSampleCount;
uint32_t m_pulsePatternCount;
uint32_t m_pulsePatternCycle;