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;