mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-25 09:18:54 -05:00
Add instant replay
This commit is contained in:
parent
cbab429395
commit
7cc9cd1bf1
@ -235,6 +235,10 @@ void AirspyHFGui::displaySettings()
|
||||
ui->dcOffset->setChecked(m_settings.m_dcBlock);
|
||||
ui->iqImbalance->setChecked(m_settings.m_iqCorrection);
|
||||
displayAGC();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
ui->replayLoop->setChecked(m_settings.m_replayLoop);
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
@ -500,6 +504,9 @@ void AirspyHFGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
if (m_contextMenuType == ContextMenuDeviceSettings)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(this);
|
||||
dialog.setReplayBytesPerSecond(getDevSampleRate(m_settings.m_devSampleRateIndex) * 2 * sizeof(float));
|
||||
dialog.setReplayLength(m_settings.m_replayLength);
|
||||
dialog.setReplayStep(m_settings.m_replayStep);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
@ -513,10 +520,17 @@ void AirspyHFGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
m_settings.m_replayLength = dialog.getReplayLength();
|
||||
m_settings.m_replayStep = dialog.getReplayStep();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
m_settingsKeys.append("useReverseAPI");
|
||||
m_settingsKeys.append("reverseAPIAddress");
|
||||
m_settingsKeys.append("reverseAPIPort");
|
||||
m_settingsKeys.append("reverseAPIDeviceIndex");
|
||||
m_settingsKeys.append("replayLength");
|
||||
m_settingsKeys.append("replayStep");
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
@ -524,6 +538,91 @@ void AirspyHFGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void AirspyHFGui::displayReplayLength()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
if (!replayEnabled) {
|
||||
ui->replayOffset->setMaximum(0);
|
||||
} else {
|
||||
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
|
||||
}
|
||||
ui->replayLabel->setEnabled(replayEnabled);
|
||||
ui->replayOffset->setEnabled(replayEnabled);
|
||||
ui->replayOffsetText->setEnabled(replayEnabled);
|
||||
ui->replaySave->setEnabled(replayEnabled);
|
||||
}
|
||||
|
||||
void AirspyHFGui::displayReplayOffset()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
|
||||
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
|
||||
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
|
||||
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
}
|
||||
|
||||
void AirspyHFGui::displayReplayStep()
|
||||
{
|
||||
QString step;
|
||||
float intpart;
|
||||
float frac = modf(m_settings.m_replayStep, &intpart);
|
||||
if (frac == 0.0f) {
|
||||
step = QString::number((int)intpart);
|
||||
} else {
|
||||
step = QString::number(m_settings.m_replayStep, 'f', 1);
|
||||
}
|
||||
ui->replayPlus->setText(QString("+%1s").arg(step));
|
||||
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
|
||||
ui->replayMinus->setText(QString("-%1s").arg(step));
|
||||
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replayOffset_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_replayOffset = value / 10.0f;
|
||||
displayReplayOffset();
|
||||
m_settingsKeys.append("replayOffset");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replayNow_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(0);
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replayPlus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replayMinus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replaySave_clicked()
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
AirspyHFInput::MsgSaveReplay *message = AirspyHFInput::MsgSaveReplay::create(fileNames[0]);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AirspyHFGui::on_replayLoop_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_replayLoop = checked;
|
||||
m_settingsKeys.append("replayLoop");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void AirspyHFGui::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->centerFrequency, &ValueDial::changed, this, &AirspyHFGui::on_centerFrequency_changed);
|
||||
@ -540,4 +639,10 @@ void AirspyHFGui::makeUIConnections()
|
||||
QObject::connect(ui->lna, &ButtonSwitch::toggled, this, &AirspyHFGui::on_lna_toggled);
|
||||
QObject::connect(ui->agc, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AirspyHFGui::on_agc_currentIndexChanged);
|
||||
QObject::connect(ui->att, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AirspyHFGui::on_att_currentIndexChanged);
|
||||
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &AirspyHFGui::on_replayOffset_valueChanged);
|
||||
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &AirspyHFGui::on_replayNow_clicked);
|
||||
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &AirspyHFGui::on_replayPlus_clicked);
|
||||
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &AirspyHFGui::on_replayMinus_clicked);
|
||||
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &AirspyHFGui::on_replaySave_clicked);
|
||||
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &AirspyHFGui::on_replayLoop_toggled);
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ private:
|
||||
void displaySettings();
|
||||
void displaySampleRates();
|
||||
void displayAGC();
|
||||
void displayReplayLength();
|
||||
void displayReplayOffset();
|
||||
void displayReplayStep();
|
||||
void sendSettings();
|
||||
void updateSampleRateAndFrequency();
|
||||
void updateFrequencyLimits();
|
||||
@ -94,6 +97,12 @@ private slots:
|
||||
void updateHardware();
|
||||
void updateStatus();
|
||||
void handleInputMessages();
|
||||
void on_replayOffset_valueChanged(int value);
|
||||
void on_replayNow_clicked();
|
||||
void on_replayPlus_clicked();
|
||||
void on_replayMinus_clicked();
|
||||
void on_replaySave_clicked();
|
||||
void on_replayLoop_toggled(bool checked);
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>137</height>
|
||||
<height>175</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,13 +19,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>137</height>
|
||||
<height>175</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>380</width>
|
||||
<height>177</height>
|
||||
<height>175</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -584,6 +584,111 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time Delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="replayOffset">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayOffsetText">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayNow">
|
||||
<property name="toolTip">
|
||||
<string>Set time delay to 0 seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayPlus">
|
||||
<property name="toolTip">
|
||||
<string>Add displayed number of seconds to time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayMinus">
|
||||
<property name="toolTip">
|
||||
<string>Remove displayed number of seconds from time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="replayLoop">
|
||||
<property name="toolTip">
|
||||
<string>Repeatedly replay data in replay buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replaySave">
|
||||
<property name="toolTip">
|
||||
<string>Save replay buffer to a file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
@ -44,6 +44,7 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(AirspyHFInput::MsgConfigureAirspyHF, Message)
|
||||
MESSAGE_CLASS_DEFINITION(AirspyHFInput::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(AirspyHFInput::MsgSaveReplay, Message)
|
||||
|
||||
const qint64 AirspyHFInput::loLowLimitFreqHF = 9000L;
|
||||
const qint64 AirspyHFInput::loHighLimitFreqHF = 31000000L;
|
||||
@ -181,7 +182,7 @@ bool AirspyHFInput::start()
|
||||
}
|
||||
|
||||
m_airspyHFWorkerThread = new QThread();
|
||||
m_airspyHFWorker = new AirspyHFWorker(m_dev, &m_sampleFifo);
|
||||
m_airspyHFWorker = new AirspyHFWorker(m_dev, &m_sampleFifo, &m_replayBuffer);
|
||||
m_airspyHFWorker->moveToThread(m_airspyHFWorkerThread);
|
||||
int sampleRateIndex = m_settings.m_devSampleRateIndex;
|
||||
|
||||
@ -289,6 +290,18 @@ int AirspyHFInput::getSampleRate() const
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AirspyHFInput::getSampleRateFromIndex(int devSampleRateIndex) const
|
||||
{
|
||||
if (devSampleRateIndex >= m_sampleRates.size()) {
|
||||
devSampleRateIndex = m_sampleRates.size() - 1;
|
||||
}
|
||||
if (devSampleRateIndex >= 0) {
|
||||
return m_sampleRates[devSampleRateIndex];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
quint64 AirspyHFInput::getCenterFrequency() const
|
||||
{
|
||||
return m_settings.m_centerFrequency;
|
||||
@ -346,6 +359,12 @@ bool AirspyHFInput::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSaveReplay::match(message))
|
||||
{
|
||||
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
|
||||
m_replayBuffer.save(cmd.getFilename(), getSampleRateFromIndex(m_settings.m_devSampleRateIndex), getCenterFrequency());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -416,6 +435,10 @@ bool AirspyHFInput::applySettings(const AirspyHFSettings& settings, const QList<
|
||||
m_airspyHFWorker->setSamplerate(m_sampleRates[sampleRateIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.m_devSampleRateIndex != m_settings.m_devSampleRateIndex) {
|
||||
m_replayBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("log2Decim") || force)
|
||||
@ -568,6 +591,18 @@ bool AirspyHFInput::applySettings(const AirspyHFSettings& settings, const QList<
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setSize(m_settings.m_replayLength, getSampleRateFromIndex(m_settings.m_devSampleRateIndex));
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * getSampleRateFromIndex(m_settings.m_devSampleRateIndex))) * 2);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
m_replayBuffer.setLoop(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <QThread>
|
||||
|
||||
#include <libairspyhf/airspyhf.h>
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include <dsp/devicesamplesource.h>
|
||||
|
||||
#include "airspyhfsettings.h"
|
||||
@ -85,6 +86,25 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSaveReplay : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getFilename() const { return m_filename; }
|
||||
|
||||
static MsgSaveReplay* create(const QString& filename) {
|
||||
return new MsgSaveReplay(filename);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
|
||||
MsgSaveReplay(const QString& filename) :
|
||||
Message(),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
};
|
||||
|
||||
AirspyHFInput(DeviceAPI *deviceAPI);
|
||||
virtual ~AirspyHFInput();
|
||||
virtual void destroy();
|
||||
@ -155,6 +175,7 @@ private:
|
||||
bool m_running;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
ReplayBuffer<float> m_replayBuffer;
|
||||
|
||||
bool openDevice();
|
||||
void closeDevice();
|
||||
@ -164,6 +185,7 @@ private:
|
||||
void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response);
|
||||
void webapiReverseSendSettings(const QList<QString>& deviceSettingsKeys, const AirspyHFSettings& settings, bool force);
|
||||
void webapiReverseSendStartStop(bool start);
|
||||
uint32_t getSampleRateFromIndex(int devSampleRateIndex) const;
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
|
@ -46,6 +46,10 @@ void AirspyHFSettings::resetToDefaults()
|
||||
m_attenuatorSteps = 0;
|
||||
m_dcBlock = false;
|
||||
m_iqCorrection = false;
|
||||
m_replayOffset = 0.0f;
|
||||
m_replayLength = 20.0f;
|
||||
m_replayStep = 5.0f;
|
||||
m_replayLoop = false;
|
||||
}
|
||||
|
||||
QByteArray AirspyHFSettings::serialize() const
|
||||
@ -70,6 +74,10 @@ QByteArray AirspyHFSettings::serialize() const
|
||||
s.writeBool(19, m_dcBlock);
|
||||
s.writeBool(20, m_iqCorrection);
|
||||
s.writeBool(21, m_iqOrder);
|
||||
s.writeFloat(22, m_replayOffset);
|
||||
s.writeFloat(23, m_replayLength);
|
||||
s.writeFloat(24, m_replayStep);
|
||||
s.writeBool(25, m_replayLoop);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -117,6 +125,10 @@ bool AirspyHFSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(19, &m_dcBlock, false);
|
||||
d.readBool(20, &m_iqCorrection, false);
|
||||
d.readBool(21, &m_iqOrder, true);
|
||||
d.readFloat(22, &m_replayOffset, 0.0f);
|
||||
d.readFloat(23, &m_replayLength, 20.0f);
|
||||
d.readFloat(24, &m_replayStep, 5.0f);
|
||||
d.readBool(25, &m_replayLoop, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -186,6 +198,18 @@ void AirspyHFSettings::applySettings(const QStringList& settingsKeys, const Airs
|
||||
if (settingsKeys.contains("iqCorrection")) {
|
||||
m_iqCorrection = settings.m_iqCorrection;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset")) {
|
||||
m_replayOffset = settings.m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength")) {
|
||||
m_replayLength = settings.m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep")) {
|
||||
m_replayStep = settings.m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop")) {
|
||||
m_replayLoop = settings.m_replayLoop;
|
||||
}
|
||||
}
|
||||
|
||||
QString AirspyHFSettings::getDebugString(const QStringList& settingsKeys, bool force) const
|
||||
@ -249,6 +273,18 @@ QString AirspyHFSettings::getDebugString(const QStringList& settingsKeys, bool f
|
||||
if (settingsKeys.contains("iqCorrection") || force) {
|
||||
ostr << " m_iqCorrection: " << m_iqCorrection;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset") || force) {
|
||||
ostr << " m_replayOffset: " << m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength") || force) {
|
||||
ostr << " m_replayLength: " << m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep") || force) {
|
||||
ostr << " m_replayStep: " << m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
ostr << " m_replayLoop: " << m_replayLoop;
|
||||
}
|
||||
|
||||
return QString(ostr.str().c_str());
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ struct AirspyHFSettings
|
||||
quint32 m_attenuatorSteps;
|
||||
bool m_dcBlock;
|
||||
bool m_iqCorrection;
|
||||
float m_replayOffset; //!< Replay offset in seconds
|
||||
float m_replayLength; //!< Replay buffer size in seconds
|
||||
float m_replayStep; //!< Replay forward/back step size in seconds
|
||||
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
|
||||
|
||||
AirspyHFSettings();
|
||||
void resetToDefaults();
|
||||
|
@ -24,11 +24,12 @@
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "airspyhfworker.h"
|
||||
|
||||
AirspyHFWorker::AirspyHFWorker(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent) :
|
||||
AirspyHFWorker::AirspyHFWorker(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<float> *replayBuffer, QObject* parent) :
|
||||
QObject(parent),
|
||||
m_dev(dev),
|
||||
m_convertBuffer(AIRSPYHF_BLOCKSIZE),
|
||||
m_sampleFifo(sampleFifo),
|
||||
m_replayBuffer(replayBuffer),
|
||||
m_samplerate(10),
|
||||
m_log2Decim(0),
|
||||
m_iqOrder(true)
|
||||
@ -74,10 +75,30 @@ void AirspyHFWorker::setLog2Decimation(unsigned int log2_decim)
|
||||
}
|
||||
|
||||
// Decimate according to specified log2 (ex: log2=4 => decim=16)
|
||||
void AirspyHFWorker::callbackIQ(const float* buf, qint32 len)
|
||||
void AirspyHFWorker::callbackIQ(const float* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const float* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
switch (m_log2Decim)
|
||||
{
|
||||
case 0:
|
||||
@ -110,14 +131,37 @@ void AirspyHFWorker::callbackIQ(const float* buf, qint32 len)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
}
|
||||
|
||||
void AirspyHFWorker::callbackQI(const float* buf, qint32 len)
|
||||
void AirspyHFWorker::callbackQI(const float* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const float* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
switch (m_log2Decim)
|
||||
{
|
||||
case 0:
|
||||
@ -150,6 +194,9 @@ void AirspyHFWorker::callbackQI(const float* buf, qint32 len)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#define INCLUDE_AIRSPYHFWORKER_H
|
||||
|
||||
#include <dsp/decimatorsfi.h>
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include <QObject>
|
||||
#include <libairspyhf/airspyhf.h>
|
||||
|
||||
@ -34,7 +35,7 @@ class AirspyHFWorker : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AirspyHFWorker(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, QObject* parent = 0);
|
||||
AirspyHFWorker(airspyhf_device_t* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<float> *replayBuffer, QObject* parent = 0);
|
||||
~AirspyHFWorker();
|
||||
|
||||
void startWork();
|
||||
@ -49,6 +50,7 @@ private:
|
||||
qint16 m_buf[2*AIRSPYHF_BLOCKSIZE];
|
||||
SampleVector m_convertBuffer;
|
||||
SampleSinkFifo* m_sampleFifo;
|
||||
ReplayBuffer<float> *m_replayBuffer;
|
||||
|
||||
int m_samplerate;
|
||||
unsigned int m_log2Decim;
|
||||
|
@ -334,6 +334,10 @@ void RTLSDRGui::displaySettings()
|
||||
ui->lowSampleRate->setChecked(m_settings.m_lowSampleRate);
|
||||
ui->offsetTuning->setChecked(m_settings.m_offsetTuning);
|
||||
ui->biasT->setChecked(m_settings.m_biasTee);
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
ui->replayLoop->setChecked(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
void RTLSDRGui::sendSettings()
|
||||
@ -568,6 +572,9 @@ void RTLSDRGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
if (m_contextMenuType == ContextMenuDeviceSettings)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(this);
|
||||
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2);
|
||||
dialog.setReplayLength(m_settings.m_replayLength);
|
||||
dialog.setReplayStep(m_settings.m_replayStep);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
@ -581,10 +588,17 @@ void RTLSDRGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
m_settings.m_replayLength = dialog.getReplayLength();
|
||||
m_settings.m_replayStep = dialog.getReplayStep();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
m_settingsKeys.append("useReverseAPI");
|
||||
m_settingsKeys.append("reverseAPIAddress");
|
||||
m_settingsKeys.append("reverseAPIPort");
|
||||
m_settingsKeys.append("reverseAPIDeviceIndex");
|
||||
m_settingsKeys.append("replayLength");
|
||||
m_settingsKeys.append("replayStep");
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
@ -592,6 +606,91 @@ void RTLSDRGui::openDeviceSettingsDialog(const QPoint& p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void RTLSDRGui::displayReplayLength()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
if (!replayEnabled) {
|
||||
ui->replayOffset->setMaximum(0);
|
||||
} else {
|
||||
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
|
||||
}
|
||||
ui->replayLabel->setEnabled(replayEnabled);
|
||||
ui->replayOffset->setEnabled(replayEnabled);
|
||||
ui->replayOffsetText->setEnabled(replayEnabled);
|
||||
ui->replaySave->setEnabled(replayEnabled);
|
||||
}
|
||||
|
||||
void RTLSDRGui::displayReplayOffset()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
|
||||
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
|
||||
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
|
||||
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
}
|
||||
|
||||
void RTLSDRGui::displayReplayStep()
|
||||
{
|
||||
QString step;
|
||||
float intpart;
|
||||
float frac = modf(m_settings.m_replayStep, &intpart);
|
||||
if (frac == 0.0f) {
|
||||
step = QString::number((int)intpart);
|
||||
} else {
|
||||
step = QString::number(m_settings.m_replayStep, 'f', 1);
|
||||
}
|
||||
ui->replayPlus->setText(QString("+%1s").arg(step));
|
||||
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
|
||||
ui->replayMinus->setText(QString("-%1s").arg(step));
|
||||
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replayOffset_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_replayOffset = value / 10.0f;
|
||||
displayReplayOffset();
|
||||
m_settingsKeys.append("replayOffset");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replayNow_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(0);
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replayPlus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replayMinus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replaySave_clicked()
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
RTLSDRInput::MsgSaveReplay *message = RTLSDRInput::MsgSaveReplay::create(fileNames[0]);
|
||||
m_sampleSource->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RTLSDRGui::on_replayLoop_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_replayLoop = checked;
|
||||
m_settingsKeys.append("replayLoop");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void RTLSDRGui::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->centerFrequency, &ValueDial::changed, this, &RTLSDRGui::on_centerFrequency_changed);
|
||||
@ -611,4 +710,10 @@ void RTLSDRGui::makeUIConnections()
|
||||
QObject::connect(ui->transverter, &TransverterButton::clicked, this, &RTLSDRGui::on_transverter_clicked);
|
||||
QObject::connect(ui->sampleRateMode, &QToolButton::toggled, this, &RTLSDRGui::on_sampleRateMode_toggled);
|
||||
QObject::connect(ui->biasT, &QCheckBox::stateChanged, this, &RTLSDRGui::on_biasT_stateChanged);
|
||||
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &RTLSDRGui::on_replayOffset_valueChanged);
|
||||
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &RTLSDRGui::on_replayNow_clicked);
|
||||
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &RTLSDRGui::on_replayPlus_clicked);
|
||||
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &RTLSDRGui::on_replayMinus_clicked);
|
||||
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &RTLSDRGui::on_replaySave_clicked);
|
||||
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &RTLSDRGui::on_replayLoop_toggled);
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ private:
|
||||
void displaySampleRate();
|
||||
void displayFcTooltip();
|
||||
void displaySettings();
|
||||
void displayReplayLength();
|
||||
void displayReplayOffset();
|
||||
void displayReplayStep();
|
||||
void sendSettings();
|
||||
void updateSampleRateAndFrequency();
|
||||
void updateFrequencyLimits();
|
||||
@ -96,6 +99,12 @@ private slots:
|
||||
void on_transverter_clicked();
|
||||
void on_sampleRateMode_toggled(bool checked);
|
||||
void on_biasT_stateChanged(int state);
|
||||
void on_replayOffset_valueChanged(int value);
|
||||
void on_replayNow_clicked();
|
||||
void on_replayPlus_clicked();
|
||||
void on_replayMinus_clicked();
|
||||
void on_replaySave_clicked();
|
||||
void on_replayLoop_toggled(bool checked);
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
void updateHardware();
|
||||
void updateStatus();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>217</height>
|
||||
<height>240</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,13 +19,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>217</height>
|
||||
<height>240</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>380</width>
|
||||
<height>229</height>
|
||||
<height>240</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -598,29 +598,6 @@
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="agc">
|
||||
<property name="toolTip">
|
||||
<string>Toggles RTLSDR AGC</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AGC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gainLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Gain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSlider" name="gain">
|
||||
<property name="enabled">
|
||||
@ -640,6 +617,19 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gainLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Gain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="gainText">
|
||||
<property name="minimumSize">
|
||||
@ -656,6 +646,121 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="agc">
|
||||
<property name="toolTip">
|
||||
<string>Toggles RTLSDR AGC</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AGC</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time Delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="replayOffset">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayOffsetText">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayNow">
|
||||
<property name="toolTip">
|
||||
<string>Set time delay to 0 seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayPlus">
|
||||
<property name="toolTip">
|
||||
<string>Add displayed number of seconds to time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayMinus">
|
||||
<property name="toolTip">
|
||||
<string>Remove displayed number of seconds from time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="replayLoop">
|
||||
<property name="toolTip">
|
||||
<string>Repeatedly replay data in replay buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replaySave">
|
||||
<property name="toolTip">
|
||||
<string>Save replay buffer to a file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -45,6 +45,7 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgConfigureRTLSDR, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(RTLSDRInput::MsgSaveReplay, Message)
|
||||
|
||||
const quint64 RTLSDRInput::frequencyLowRangeMin = 0UL;
|
||||
const quint64 RTLSDRInput::frequencyLowRangeMax = 275000UL;
|
||||
@ -238,7 +239,7 @@ bool RTLSDRInput::start()
|
||||
|
||||
if (m_running) stop();
|
||||
|
||||
m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo);
|
||||
m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo, &m_replayBuffer);
|
||||
m_rtlSDRThread->setSamplerate(m_settings.m_devSampleRate);
|
||||
m_rtlSDRThread->setLog2Decimation(m_settings.m_log2Decim);
|
||||
m_rtlSDRThread->setFcPos((int) m_settings.m_fcPos);
|
||||
@ -374,6 +375,12 @@ bool RTLSDRInput::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSaveReplay::match(message))
|
||||
{
|
||||
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
|
||||
m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -432,6 +439,9 @@ bool RTLSDRInput::applySettings(const RTLSDRSettings& settings, const QList<QStr
|
||||
|
||||
qDebug("RTLSDRInput::applySettings: sample rate set to %d", settings.m_devSampleRate);
|
||||
}
|
||||
if (settings.m_devSampleRate != m_settings.m_devSampleRate) {
|
||||
m_replayBuffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,6 +584,18 @@ bool RTLSDRInput::applySettings(const RTLSDRSettings& settings, const QList<QStr
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setSize(m_settings.m_replayLength, m_settings.m_devSampleRate);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * m_settings.m_devSampleRate)) * 2);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
m_replayBuffer.setLoop(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
if (forwardChange)
|
||||
{
|
||||
int sampleRate = m_settings.m_devSampleRate/(1<<m_settings.m_log2Decim);
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "rtlsdrsettings.h"
|
||||
#include <rtl-sdr.h>
|
||||
|
||||
@ -83,6 +84,25 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSaveReplay : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getFilename() const { return m_filename; }
|
||||
|
||||
static MsgSaveReplay* create(const QString& filename) {
|
||||
return new MsgSaveReplay(filename);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
|
||||
MsgSaveReplay(const QString& filename) :
|
||||
Message(),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
};
|
||||
|
||||
RTLSDRInput(DeviceAPI *deviceAPI);
|
||||
virtual ~RTLSDRInput();
|
||||
virtual void destroy();
|
||||
@ -161,6 +181,7 @@ private:
|
||||
bool m_running;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
ReplayBuffer<quint8> m_replayBuffer;
|
||||
|
||||
bool openDevice();
|
||||
void closeDevice();
|
||||
|
@ -43,6 +43,10 @@ void RTLSDRSettings::resetToDefaults()
|
||||
m_rfBandwidth = 2500 * 1000; // Hz
|
||||
m_offsetTuning = false;
|
||||
m_biasTee = false;
|
||||
m_replayOffset = 0.0f;
|
||||
m_replayLength = 20.0f;
|
||||
m_replayStep = 5.0f;
|
||||
m_replayLoop = false;
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
@ -73,6 +77,10 @@ QByteArray RTLSDRSettings::serialize() const
|
||||
s.writeU32(19, m_reverseAPIDeviceIndex);
|
||||
s.writeBool(20, m_iqOrder);
|
||||
s.writeBool(21, m_biasTee);
|
||||
s.writeFloat(22, m_replayOffset);
|
||||
s.writeFloat(23, m_replayLength);
|
||||
s.writeFloat(24, m_replayStep);
|
||||
s.writeBool(25, m_replayLoop);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -121,6 +129,10 @@ bool RTLSDRSettings::deserialize(const QByteArray& data)
|
||||
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readBool(20, &m_iqOrder, true);
|
||||
d.readBool(21, &m_biasTee, false);
|
||||
d.readFloat(22, &m_replayOffset, 0.0f);
|
||||
d.readFloat(23, &m_replayLength, 20.0f);
|
||||
d.readFloat(24, &m_replayStep, 5.0f);
|
||||
d.readBool(25, &m_replayLoop, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -187,6 +199,18 @@ void RTLSDRSettings::applySettings(const QStringList& settingsKeys, const RTLSDR
|
||||
if (settingsKeys.contains("biasTee")) {
|
||||
m_biasTee = settings.m_biasTee;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset")) {
|
||||
m_replayOffset = settings.m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength")) {
|
||||
m_replayLength = settings.m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep")) {
|
||||
m_replayStep = settings.m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop")) {
|
||||
m_replayLoop = settings.m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI")) {
|
||||
m_useReverseAPI = settings.m_useReverseAPI;
|
||||
}
|
||||
@ -256,6 +280,18 @@ QString RTLSDRSettings::getDebugString(const QStringList& settingsKeys, bool for
|
||||
if (settingsKeys.contains("biasTee") || force) {
|
||||
ostr << " m_biasTee: " << m_biasTee;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset") || force) {
|
||||
ostr << " m_replayOffset: " << m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength") || force) {
|
||||
ostr << " m_replayLength: " << m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep") || force) {
|
||||
ostr << " m_replayStep: " << m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
ostr << " m_replayLoop: " << m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI") || force) {
|
||||
ostr << " m_useReverseAPI: " << m_useReverseAPI;
|
||||
}
|
||||
|
@ -47,6 +47,10 @@ struct RTLSDRSettings {
|
||||
quint32 m_rfBandwidth; //!< RF filter bandwidth in Hz
|
||||
bool m_offsetTuning;
|
||||
bool m_biasTee;
|
||||
float m_replayOffset; //!< Replay offset in seconds
|
||||
float m_replayLength; //!< Replay buffer size in seconds
|
||||
float m_replayStep; //!< Replay forward/back step size in seconds
|
||||
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
|
@ -18,6 +18,8 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include "rtlsdrthread.h"
|
||||
@ -26,12 +28,13 @@
|
||||
|
||||
#define FCD_BLOCKSIZE 16384
|
||||
|
||||
RTLSDRThread::RTLSDRThread(rtlsdr_dev_t* dev, SampleSinkFifo* sampleFifo, QObject* parent) :
|
||||
RTLSDRThread::RTLSDRThread(rtlsdr_dev_t* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<quint8> *replayBuffer, QObject* parent) :
|
||||
QThread(parent),
|
||||
m_running(false),
|
||||
m_dev(dev),
|
||||
m_convertBuffer(FCD_BLOCKSIZE),
|
||||
m_sampleFifo(sampleFifo),
|
||||
m_replayBuffer(replayBuffer),
|
||||
m_samplerate(288000),
|
||||
m_log2Decim(4),
|
||||
m_fcPos(0),
|
||||
@ -92,10 +95,33 @@ void RTLSDRThread::run()
|
||||
}
|
||||
|
||||
// Decimate according to specified log2 (ex: log2=4 => decim=16)
|
||||
void RTLSDRThread::callbackIQ(const quint8* buf, qint32 len)
|
||||
// Len is total samples (i.e. one I and Q pair will have len=2)
|
||||
void RTLSDRThread::callbackIQ(const quint8* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const quint8* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
qDebug() << "Replay";
|
||||
} else {
|
||||
len = remaining;
|
||||
qDebug() << "Live" << m_replayBuffer->useReplay();
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
if (m_log2Decim == 0)
|
||||
{
|
||||
m_decimatorsIQ.decimate1(&it, buf, len);
|
||||
@ -181,6 +207,9 @@ void RTLSDRThread::callbackIQ(const quint8* buf, qint32 len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
|
||||
@ -188,10 +217,30 @@ void RTLSDRThread::callbackIQ(const quint8* buf, qint32 len)
|
||||
rtlsdr_cancel_async(m_dev);
|
||||
}
|
||||
|
||||
void RTLSDRThread::callbackQI(const quint8* buf, qint32 len)
|
||||
void RTLSDRThread::callbackQI(const quint8* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const quint8* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
if (m_log2Decim == 0)
|
||||
{
|
||||
m_decimatorsQI.decimate1(&it, buf, len);
|
||||
@ -277,6 +326,9 @@ void RTLSDRThread::callbackQI(const quint8* buf, qint32 len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
|
||||
@ -294,4 +346,3 @@ void RTLSDRThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx)
|
||||
thread->callbackQI(buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <QWaitCondition>
|
||||
#include <rtl-sdr.h>
|
||||
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "dsp/decimatorsu.h"
|
||||
|
||||
@ -33,7 +34,7 @@ class RTLSDRThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RTLSDRThread(rtlsdr_dev_t* dev, SampleSinkFifo* sampleFifo, QObject* parent = NULL);
|
||||
RTLSDRThread(rtlsdr_dev_t* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<quint8> *replayBuffer, QObject* parent = NULL);
|
||||
~RTLSDRThread();
|
||||
|
||||
void startWork();
|
||||
@ -51,6 +52,7 @@ private:
|
||||
rtlsdr_dev_t* m_dev;
|
||||
SampleVector m_convertBuffer;
|
||||
SampleSinkFifo* m_sampleFifo;
|
||||
ReplayBuffer<quint8> *m_replayBuffer;
|
||||
|
||||
int m_samplerate;
|
||||
unsigned int m_log2Decim;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "sdrplayv3gui.h"
|
||||
#include "sdrplayv3input.h"
|
||||
@ -306,6 +307,10 @@ void SDRPlayV3Gui::displaySettings()
|
||||
ui->gainIF->setValue(gain);
|
||||
QString gainText = QStringLiteral("%1").arg(gain, 2, 10, QLatin1Char('0'));
|
||||
ui->gainIFText->setText(gainText);
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
ui->replayLoop->setChecked(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::updateLNAValues()
|
||||
@ -563,6 +568,9 @@ void SDRPlayV3Gui::openDeviceSettingsDialog(const QPoint& p)
|
||||
if (m_contextMenuType == ContextMenuDeviceSettings)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(this);
|
||||
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2 * sizeof(qint16));
|
||||
dialog.setReplayLength(m_settings.m_replayLength);
|
||||
dialog.setReplayStep(m_settings.m_replayStep);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
@ -576,10 +584,17 @@ void SDRPlayV3Gui::openDeviceSettingsDialog(const QPoint& p)
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
m_settings.m_replayLength = dialog.getReplayLength();
|
||||
m_settings.m_replayStep = dialog.getReplayStep();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
m_settingsKeys.append("useReverseAPI");
|
||||
m_settingsKeys.append("reverseAPIAddress");
|
||||
m_settingsKeys.append("reverseAPIPort");
|
||||
m_settingsKeys.append("reverseAPIDeviceIndex");
|
||||
m_settingsKeys.append("replayLength");
|
||||
m_settingsKeys.append("replayStep");
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
@ -587,6 +602,91 @@ void SDRPlayV3Gui::openDeviceSettingsDialog(const QPoint& p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::displayReplayLength()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
if (!replayEnabled) {
|
||||
ui->replayOffset->setMaximum(0);
|
||||
} else {
|
||||
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
|
||||
}
|
||||
ui->replayLabel->setEnabled(replayEnabled);
|
||||
ui->replayOffset->setEnabled(replayEnabled);
|
||||
ui->replayOffsetText->setEnabled(replayEnabled);
|
||||
ui->replaySave->setEnabled(replayEnabled);
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::displayReplayOffset()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
|
||||
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
|
||||
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
|
||||
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::displayReplayStep()
|
||||
{
|
||||
QString step;
|
||||
float intpart;
|
||||
float frac = modf(m_settings.m_replayStep, &intpart);
|
||||
if (frac == 0.0f) {
|
||||
step = QString::number((int)intpart);
|
||||
} else {
|
||||
step = QString::number(m_settings.m_replayStep, 'f', 1);
|
||||
}
|
||||
ui->replayPlus->setText(QString("+%1s").arg(step));
|
||||
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
|
||||
ui->replayMinus->setText(QString("-%1s").arg(step));
|
||||
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replayOffset_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_replayOffset = value / 10.0f;
|
||||
displayReplayOffset();
|
||||
m_settingsKeys.append("replayOffset");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replayNow_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(0);
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replayPlus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replayMinus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replaySave_clicked()
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
SDRPlayV3Input::MsgSaveReplay *message = SDRPlayV3Input::MsgSaveReplay::create(fileNames[0]);
|
||||
m_sdrPlayV3Input->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::on_replayLoop_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_replayLoop = checked;
|
||||
m_settingsKeys.append("replayLoop");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void SDRPlayV3Gui::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->centerFrequency, &ValueDial::changed, this, &SDRPlayV3Gui::on_centerFrequency_changed);
|
||||
@ -610,4 +710,10 @@ void SDRPlayV3Gui::makeUIConnections()
|
||||
QObject::connect(ui->gainIF, &QDial::valueChanged, this, &SDRPlayV3Gui::on_gainIF_valueChanged);
|
||||
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &SDRPlayV3Gui::on_startStop_toggled);
|
||||
QObject::connect(ui->transverter, &TransverterButton::clicked, this, &SDRPlayV3Gui::on_transverter_clicked);
|
||||
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &SDRPlayV3Gui::on_replayOffset_valueChanged);
|
||||
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &SDRPlayV3Gui::on_replayNow_clicked);
|
||||
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &SDRPlayV3Gui::on_replayPlus_clicked);
|
||||
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &SDRPlayV3Gui::on_replayMinus_clicked);
|
||||
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &SDRPlayV3Gui::on_replaySave_clicked);
|
||||
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &SDRPlayV3Gui::on_replayLoop_toggled);
|
||||
}
|
||||
|
@ -64,6 +64,9 @@ private:
|
||||
|
||||
void blockApplySettings(bool block) { m_doApplySettings = !block; }
|
||||
void displaySettings();
|
||||
void displayReplayLength();
|
||||
void displayReplayOffset();
|
||||
void displayReplayStep();
|
||||
void updateLNAValues();
|
||||
void sendSettings();
|
||||
void updateSampleRateAndFrequency();
|
||||
@ -96,6 +99,12 @@ private slots:
|
||||
void on_gainIF_valueChanged(int value);
|
||||
void on_startStop_toggled(bool checked);
|
||||
void on_transverter_clicked();
|
||||
void on_replayOffset_valueChanged(int value);
|
||||
void on_replayNow_clicked();
|
||||
void on_replayPlus_clicked();
|
||||
void on_replayMinus_clicked();
|
||||
void on_replaySave_clicked();
|
||||
void on_replayLoop_toggled(bool checked);
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>234</height>
|
||||
<height>280</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,13 +19,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>234</height>
|
||||
<height>280</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>409</width>
|
||||
<height>297</height>
|
||||
<height>280</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -636,7 +636,6 @@
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
@ -764,20 +763,125 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time Delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="replayOffset">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayOffsetText">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayNow">
|
||||
<property name="toolTip">
|
||||
<string>Set time delay to 0 seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayPlus">
|
||||
<property name="toolTip">
|
||||
<string>Add displayed number of seconds to time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayMinus">
|
||||
<property name="toolTip">
|
||||
<string>Remove displayed number of seconds from time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="replayLoop">
|
||||
<property name="toolTip">
|
||||
<string>Repeatedly replay data in replay buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replaySave">
|
||||
<property name="toolTip">
|
||||
<string>Save replay buffer to a file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDial</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedial.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TransverterButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
|
@ -42,6 +42,7 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(SDRPlayV3Input::MsgConfigureSDRPlayV3, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SDRPlayV3Input::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(SDRPlayV3Input::MsgSaveReplay, Message)
|
||||
|
||||
SDRPlayV3Input::SDRPlayV3Input(DeviceAPI *deviceAPI) :
|
||||
m_deviceAPI(deviceAPI),
|
||||
@ -155,7 +156,7 @@ bool SDRPlayV3Input::start()
|
||||
|
||||
if (m_running) stop();
|
||||
|
||||
m_sdrPlayThread = new SDRPlayV3Thread(m_dev, &m_sampleFifo);
|
||||
m_sdrPlayThread = new SDRPlayV3Thread(m_dev, &m_sampleFifo, &m_replayBuffer);
|
||||
m_sdrPlayThread->setLog2Decimation(m_settings.m_log2Decim);
|
||||
m_sdrPlayThread->setFcPos((int) m_settings.m_fcPos);
|
||||
m_sdrPlayThread->startWork();
|
||||
@ -306,6 +307,12 @@ bool SDRPlayV3Input::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSaveReplay::match(message))
|
||||
{
|
||||
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
|
||||
m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -408,6 +415,9 @@ bool SDRPlayV3Input::applySettings(const SDRPlayV3Settings& settings, const QLis
|
||||
else
|
||||
qDebug() << "SDRPlayV3Input::applySettings: sample rate set to " << sampleRate;
|
||||
forwardChange = true;
|
||||
if (settings.m_devSampleRate != m_settings.m_devSampleRate) {
|
||||
m_replayBuffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -696,6 +706,18 @@ bool SDRPlayV3Input::applySettings(const SDRPlayV3Settings& settings, const QLis
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setSize(m_settings.m_replayLength, m_settings.m_devSampleRate);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * m_settings.m_devSampleRate)) * 2);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
m_replayBuffer.setLoop(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
if (forwardChange)
|
||||
{
|
||||
int sampleRate = getSampleRate();
|
||||
|
@ -27,6 +27,7 @@
|
||||
|
||||
#include <sdrplay_api.h>
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "sdrplayv3settings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
@ -82,6 +83,25 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSaveReplay : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getFilename() const { return m_filename; }
|
||||
|
||||
static MsgSaveReplay* create(const QString& filename) {
|
||||
return new MsgSaveReplay(filename);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
|
||||
MsgSaveReplay(const QString& filename) :
|
||||
Message(),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
};
|
||||
|
||||
SDRPlayV3Input(DeviceAPI *deviceAPI);
|
||||
virtual ~SDRPlayV3Input();
|
||||
virtual void destroy();
|
||||
@ -149,6 +169,7 @@ private:
|
||||
bool m_running;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
ReplayBuffer<qint16> m_replayBuffer;
|
||||
|
||||
bool openDevice();
|
||||
void closeDevice();
|
||||
|
@ -50,6 +50,10 @@ void SDRPlayV3Settings::resetToDefaults()
|
||||
m_transverterMode = false;
|
||||
m_iqOrder = true;
|
||||
m_transverterDeltaFrequency = 0;
|
||||
m_replayOffset = 0.0f;
|
||||
m_replayLength = 20.0f;
|
||||
m_replayStep = 5.0f;
|
||||
m_replayLoop = false;
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
@ -85,6 +89,10 @@ QByteArray SDRPlayV3Settings::serialize() const
|
||||
s.writeBool(26, m_transverterMode);
|
||||
s.writeS64(27, m_transverterDeltaFrequency);
|
||||
s.writeBool(28, m_iqOrder);
|
||||
s.writeFloat(29, m_replayOffset);
|
||||
s.writeFloat(30, m_replayLength);
|
||||
s.writeFloat(31, m_replayStep);
|
||||
s.writeBool(32, m_replayLoop);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -138,6 +146,10 @@ bool SDRPlayV3Settings::deserialize(const QByteArray& data)
|
||||
d.readBool(26, &m_transverterMode, false);
|
||||
d.readS64(27, &m_transverterDeltaFrequency, 0);
|
||||
d.readBool(28, &m_iqOrder, true);
|
||||
d.readFloat(29, &m_replayOffset, 0.0f);
|
||||
d.readFloat(30, &m_replayLength, 20.0f);
|
||||
d.readFloat(31, &m_replayStep, 5.0f);
|
||||
d.readBool(32, &m_replayLoop, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -216,6 +228,18 @@ void SDRPlayV3Settings::applySettings(const QStringList& settingsKeys, const SDR
|
||||
if (settingsKeys.contains("m_transverterDeltaFrequency")) {
|
||||
m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset")) {
|
||||
m_replayOffset = settings.m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength")) {
|
||||
m_replayLength = settings.m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep")) {
|
||||
m_replayStep = settings.m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop")) {
|
||||
m_replayLoop = settings.m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI")) {
|
||||
m_useReverseAPI = settings.m_useReverseAPI;
|
||||
}
|
||||
@ -300,6 +324,18 @@ QString SDRPlayV3Settings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("transverterDeltaFrequency") || force) {
|
||||
ostr << " m_transverterDeltaFrequency: " << m_transverterDeltaFrequency;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset") || force) {
|
||||
ostr << " m_replayOffset: " << m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength") || force) {
|
||||
ostr << " m_replayLength: " << m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep") || force) {
|
||||
ostr << " m_replayStep: " << m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
ostr << " m_replayLoop: " << m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI") || force) {
|
||||
ostr << " m_useReverseAPI: " << m_useReverseAPI;
|
||||
}
|
||||
|
@ -53,6 +53,10 @@ struct SDRPlayV3Settings {
|
||||
bool m_transverterMode;
|
||||
bool m_iqOrder;
|
||||
qint64 m_transverterDeltaFrequency;
|
||||
float m_replayOffset; //!< Replay offset in seconds
|
||||
float m_replayLength; //!< Replay buffer size in seconds
|
||||
float m_replayStep; //!< Replay forward/back step size in seconds
|
||||
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
|
@ -27,13 +27,14 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
SDRPlayV3Thread::SDRPlayV3Thread(sdrplay_api_DeviceT* dev, SampleSinkFifo* sampleFifo, QObject* parent) :
|
||||
SDRPlayV3Thread::SDRPlayV3Thread(sdrplay_api_DeviceT* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<qint16> *replayBuffer, QObject* parent) :
|
||||
QThread(parent),
|
||||
m_running(false),
|
||||
m_dev(dev),
|
||||
m_convertBuffer(SDRPLAYV3_INIT_NBSAMPLES),
|
||||
m_sampleFifo(sampleFifo),
|
||||
m_samplerate(2000000),
|
||||
m_replayBuffer(replayBuffer),
|
||||
m_log2Decim(0),
|
||||
m_fcPos(0),
|
||||
m_iqOrder(true),
|
||||
@ -171,10 +172,30 @@ void SDRPlayV3Thread::callbackHelper(short *xi, short *xq, sdrplay_api_StreamCbP
|
||||
}
|
||||
}
|
||||
|
||||
void SDRPlayV3Thread::callbackIQ(const qint16* buf, qint32 len)
|
||||
void SDRPlayV3Thread::callbackIQ(const qint16* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const qint16* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
if (m_log2Decim == 0)
|
||||
{
|
||||
m_decimatorsIQ.decimate1(&it, buf, len);
|
||||
@ -260,14 +281,37 @@ void SDRPlayV3Thread::callbackIQ(const qint16* buf, qint32 len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
}
|
||||
|
||||
void SDRPlayV3Thread::callbackQI(const qint16* buf, qint32 len)
|
||||
void SDRPlayV3Thread::callbackQI(const qint16* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const qint16* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
if (m_log2Decim == 0)
|
||||
{
|
||||
m_decimatorsQI.decimate1(&it, buf, len);
|
||||
@ -353,6 +397,9 @@ void SDRPlayV3Thread::callbackQI(const qint16* buf, qint32 len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <sdrplay_api.h>
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "dsp/decimators.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
|
||||
#define SDRPLAYV3_INIT_NBSAMPLES (1<<14)
|
||||
|
||||
@ -36,7 +37,7 @@ class SDRPlayV3Thread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SDRPlayV3Thread(sdrplay_api_DeviceT* dev, SampleSinkFifo* sampleFifo, QObject* parent = NULL);
|
||||
SDRPlayV3Thread(sdrplay_api_DeviceT* dev, SampleSinkFifo* sampleFifo, ReplayBuffer<qint16> *replayBuffer, QObject* parent = NULL);
|
||||
~SDRPlayV3Thread();
|
||||
|
||||
void startWork();
|
||||
@ -58,6 +59,7 @@ private:
|
||||
sdrplay_api_DeviceT *m_dev;
|
||||
SampleVector m_convertBuffer;
|
||||
SampleSinkFifo* m_sampleFifo;
|
||||
ReplayBuffer<qint16> *m_replayBuffer;
|
||||
|
||||
int m_samplerate;
|
||||
unsigned int m_log2Decim;
|
||||
|
@ -47,6 +47,7 @@ MESSAGE_CLASS_DEFINITION(USRPInput::MsgGetStreamInfo, Message)
|
||||
MESSAGE_CLASS_DEFINITION(USRPInput::MsgGetDeviceInfo, Message)
|
||||
MESSAGE_CLASS_DEFINITION(USRPInput::MsgReportStreamInfo, Message)
|
||||
MESSAGE_CLASS_DEFINITION(USRPInput::MsgStartStop, Message)
|
||||
MESSAGE_CLASS_DEFINITION(USRPInput::MsgSaveReplay, Message)
|
||||
|
||||
USRPInput::USRPInput(DeviceAPI *deviceAPI) :
|
||||
m_deviceAPI(deviceAPI),
|
||||
@ -427,7 +428,7 @@ bool USRPInput::start()
|
||||
|
||||
// start / stop streaming is done in the thread.
|
||||
|
||||
m_usrpInputThread = new USRPInputThread(m_streamId, m_bufSamples, &m_sampleFifo);
|
||||
m_usrpInputThread = new USRPInputThread(m_streamId, m_bufSamples, &m_sampleFifo, &m_replayBuffer);
|
||||
qDebug("USRPInput::start: thread created");
|
||||
|
||||
m_usrpInputThread->setLog2Decimation(m_settings.m_log2SoftDecim);
|
||||
@ -705,6 +706,12 @@ bool USRPInput::handleMessage(const Message& message)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgSaveReplay::match(message))
|
||||
{
|
||||
MsgSaveReplay& cmd = (MsgSaveReplay&) message;
|
||||
m_replayBuffer.save(cmd.getFilename(), m_settings.m_devSampleRate, getCenterFrequency());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -768,6 +775,10 @@ bool USRPInput::applySettings(const USRPInputSettings& settings, const QList<QSt
|
||||
checkRates = true;
|
||||
reapplySomeSettings = true;
|
||||
}
|
||||
|
||||
if (settings.m_devSampleRate != m_settings.m_devSampleRate) {
|
||||
m_replayBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("centerFrequency")
|
||||
@ -914,6 +925,18 @@ bool USRPInput::applySettings(const USRPInputSettings& settings, const QList<QSt
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLength") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setSize(m_settings.m_replayLength, m_settings.m_devSampleRate);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayOffset") || settingsKeys.contains("devSampleRate") || force) {
|
||||
m_replayBuffer.setReadOffset(((unsigned)(m_settings.m_replayOffset * m_settings.m_devSampleRate)) * 2);
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
m_replayBuffer.setLoop(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
if (checkRates)
|
||||
{
|
||||
// Check if requested rate could actually be met and what master clock rate we ended up with
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <uhd/usrp/multi_usrp.hpp>
|
||||
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "usrp/deviceusrpshared.h"
|
||||
#include "usrpinputsettings.h"
|
||||
|
||||
@ -161,6 +162,25 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgSaveReplay : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getFilename() const { return m_filename; }
|
||||
|
||||
static MsgSaveReplay* create(const QString& filename) {
|
||||
return new MsgSaveReplay(filename);
|
||||
}
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
|
||||
MsgSaveReplay(const QString& filename) :
|
||||
Message(),
|
||||
m_filename(filename)
|
||||
{ }
|
||||
};
|
||||
|
||||
USRPInput(DeviceAPI *deviceAPI);
|
||||
virtual ~USRPInput();
|
||||
virtual void destroy();
|
||||
@ -235,6 +255,7 @@ private:
|
||||
size_t m_bufSamples;
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
ReplayBuffer<qint16> m_replayBuffer;
|
||||
|
||||
bool openDevice();
|
||||
void closeDevice();
|
||||
|
@ -407,6 +407,10 @@ void USRPInputGUI::displaySettings()
|
||||
} else {
|
||||
ui->gain->setEnabled(true);
|
||||
}
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
ui->replayLoop->setChecked(m_settings.m_replayLoop);
|
||||
}
|
||||
|
||||
void USRPInputGUI::setCenterFrequencyDisplay()
|
||||
@ -646,6 +650,9 @@ void USRPInputGUI::openDeviceSettingsDialog(const QPoint& p)
|
||||
if (m_contextMenuType == ContextMenuDeviceSettings)
|
||||
{
|
||||
BasicDeviceSettingsDialog dialog(this);
|
||||
dialog.setReplayBytesPerSecond(m_settings.m_devSampleRate * 2 * sizeof(qint16));
|
||||
dialog.setReplayLength(m_settings.m_replayLength);
|
||||
dialog.setReplayStep(m_settings.m_replayStep);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
@ -659,10 +666,17 @@ void USRPInputGUI::openDeviceSettingsDialog(const QPoint& p)
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
m_settings.m_replayLength = dialog.getReplayLength();
|
||||
m_settings.m_replayStep = dialog.getReplayStep();
|
||||
displayReplayLength();
|
||||
displayReplayOffset();
|
||||
displayReplayStep();
|
||||
m_settingsKeys.append("useReverseAPI");
|
||||
m_settingsKeys.append("reverseAPIAddress");
|
||||
m_settingsKeys.append("reverseAPIPort");
|
||||
m_settingsKeys.append("reverseAPIDeviceIndex");
|
||||
m_settingsKeys.append("replayLength");
|
||||
m_settingsKeys.append("replayStep");
|
||||
|
||||
sendSettings();
|
||||
}
|
||||
@ -670,6 +684,91 @@ void USRPInputGUI::openDeviceSettingsDialog(const QPoint& p)
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
void USRPInputGUI::displayReplayLength()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
if (!replayEnabled) {
|
||||
ui->replayOffset->setMaximum(0);
|
||||
} else {
|
||||
ui->replayOffset->setMaximum(m_settings.m_replayLength * 10 - 1);
|
||||
}
|
||||
ui->replayLabel->setEnabled(replayEnabled);
|
||||
ui->replayOffset->setEnabled(replayEnabled);
|
||||
ui->replayOffsetText->setEnabled(replayEnabled);
|
||||
ui->replaySave->setEnabled(replayEnabled);
|
||||
}
|
||||
|
||||
void USRPInputGUI::displayReplayOffset()
|
||||
{
|
||||
bool replayEnabled = m_settings.m_replayLength > 0.0f;
|
||||
ui->replayOffset->setValue(m_settings.m_replayOffset * 10);
|
||||
ui->replayOffsetText->setText(QString("%1s").arg(m_settings.m_replayOffset, 0, 'f', 1));
|
||||
ui->replayNow->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
ui->replayPlus->setEnabled(replayEnabled && (std::round(m_settings.m_replayOffset * 10) < ui->replayOffset->maximum()));
|
||||
ui->replayMinus->setEnabled(replayEnabled && (m_settings.m_replayOffset > 0.0f));
|
||||
}
|
||||
|
||||
void USRPInputGUI::displayReplayStep()
|
||||
{
|
||||
QString step;
|
||||
float intpart;
|
||||
float frac = modf(m_settings.m_replayStep, &intpart);
|
||||
if (frac == 0.0f) {
|
||||
step = QString::number((int)intpart);
|
||||
} else {
|
||||
step = QString::number(m_settings.m_replayStep, 'f', 1);
|
||||
}
|
||||
ui->replayPlus->setText(QString("+%1s").arg(step));
|
||||
ui->replayPlus->setToolTip(QString("Add %1 seconds to time delay").arg(step));
|
||||
ui->replayMinus->setText(QString("-%1s").arg(step));
|
||||
ui->replayMinus->setToolTip(QString("Remove %1 seconds from time delay").arg(step));
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replayOffset_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_replayOffset = value / 10.0f;
|
||||
displayReplayOffset();
|
||||
m_settingsKeys.append("replayOffset");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replayNow_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(0);
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replayPlus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() + m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replayMinus_clicked()
|
||||
{
|
||||
ui->replayOffset->setValue(ui->replayOffset->value() - m_settings.m_replayStep * 10);
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replaySave_clicked()
|
||||
{
|
||||
QFileDialog fileDialog(nullptr, "Select file to save IQ data to", "", "*.wav");
|
||||
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
if (fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0)
|
||||
{
|
||||
USRPInput::MsgSaveReplay *message = USRPInput::MsgSaveReplay::create(fileNames[0]);
|
||||
m_usrpInput->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USRPInputGUI::on_replayLoop_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_replayLoop = checked;
|
||||
m_settingsKeys.append("replayLoop");
|
||||
sendSettings();
|
||||
}
|
||||
|
||||
void USRPInputGUI::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &USRPInputGUI::on_startStop_toggled);
|
||||
@ -686,4 +785,10 @@ void USRPInputGUI::makeUIConnections()
|
||||
QObject::connect(ui->clockSource, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &USRPInputGUI::on_clockSource_currentIndexChanged);
|
||||
QObject::connect(ui->transverter, &TransverterButton::clicked, this, &USRPInputGUI::on_transverter_clicked);
|
||||
QObject::connect(ui->sampleRateMode, &QToolButton::toggled, this, &USRPInputGUI::on_sampleRateMode_toggled);
|
||||
QObject::connect(ui->replayOffset, &QSlider::valueChanged, this, &USRPInputGUI::on_replayOffset_valueChanged);
|
||||
QObject::connect(ui->replayNow, &QToolButton::clicked, this, &USRPInputGUI::on_replayNow_clicked);
|
||||
QObject::connect(ui->replayPlus, &QToolButton::clicked, this, &USRPInputGUI::on_replayPlus_clicked);
|
||||
QObject::connect(ui->replayMinus, &QToolButton::clicked, this, &USRPInputGUI::on_replayMinus_clicked);
|
||||
QObject::connect(ui->replaySave, &QToolButton::clicked, this, &USRPInputGUI::on_replaySave_clicked);
|
||||
QObject::connect(ui->replayLoop, &ButtonSwitch::toggled, this, &USRPInputGUI::on_replayLoop_toggled);
|
||||
}
|
||||
|
@ -71,6 +71,9 @@ private:
|
||||
|
||||
void displaySettings();
|
||||
void displaySampleRate();
|
||||
void displayReplayLength();
|
||||
void displayReplayOffset();
|
||||
void displayReplayStep();
|
||||
void setCenterFrequencyDisplay();
|
||||
void setCenterFrequencySetting(uint64_t kHzValue);
|
||||
void sendSettings();
|
||||
@ -97,6 +100,12 @@ private slots:
|
||||
void on_clockSource_currentIndexChanged(int index);
|
||||
void on_transverter_clicked();
|
||||
void on_sampleRateMode_toggled(bool checked);
|
||||
void on_replayOffset_valueChanged(int value);
|
||||
void on_replayNow_clicked();
|
||||
void on_replayPlus_clicked();
|
||||
void on_replayMinus_clicked();
|
||||
void on_replaySave_clicked();
|
||||
void on_replayLoop_toggled(bool checked);
|
||||
void openDeviceSettingsDialog(const QPoint& p);
|
||||
|
||||
void updateHardware();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>192</height>
|
||||
<height>230</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -19,13 +19,13 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>360</width>
|
||||
<height>192</height>
|
||||
<height>230</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>380</width>
|
||||
<height>192</height>
|
||||
<height>230</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -767,6 +767,111 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Time Delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="replayOffset">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayOffsetText">
|
||||
<property name="toolTip">
|
||||
<string>Replay time delay in seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayNow">
|
||||
<property name="toolTip">
|
||||
<string>Set time delay to 0 seconds</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Now</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayPlus">
|
||||
<property name="toolTip">
|
||||
<string>Add displayed number of seconds to time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replayMinus">
|
||||
<property name="toolTip">
|
||||
<string>Remove displayed number of seconds from time delay</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-5s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="replayLoop">
|
||||
<property name="toolTip">
|
||||
<string>Repeatedly replay data in replay buffer</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="replaySave">
|
||||
<property name="toolTip">
|
||||
<string>Save replay buffer to a file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@ -781,17 +886,17 @@
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TransverterButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>gui/transverterbutton.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDialZ</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedialz.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TransverterButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>gui/transverterbutton.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
|
@ -42,6 +42,10 @@ void USRPInputSettings::resetToDefaults()
|
||||
m_clockSource = "internal";
|
||||
m_transverterMode = false;
|
||||
m_transverterDeltaFrequency = 0;
|
||||
m_replayOffset = 0.0f;
|
||||
m_replayLength = 20.0f;
|
||||
m_replayStep = 5.0f;
|
||||
m_replayLoop = false;
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
@ -68,6 +72,10 @@ QByteArray USRPInputSettings::serialize() const
|
||||
s.writeU32(14, m_reverseAPIPort);
|
||||
s.writeU32(15, m_reverseAPIDeviceIndex);
|
||||
s.writeS32(16, m_loOffset);
|
||||
s.writeFloat(17, m_replayOffset);
|
||||
s.writeFloat(18, m_replayLength);
|
||||
s.writeFloat(19, m_replayStep);
|
||||
s.writeBool(20, m_replayLoop);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -112,6 +120,10 @@ bool USRPInputSettings::deserialize(const QByteArray& data)
|
||||
d.readU32(15, &uintval, 0);
|
||||
m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval;
|
||||
d.readS32(16, &m_loOffset, 0);
|
||||
d.readFloat(17, &m_replayOffset, 0.0f);
|
||||
d.readFloat(18, &m_replayLength, 20.0f);
|
||||
d.readFloat(19, &m_replayStep, 5.0f);
|
||||
d.readBool(20, &m_replayLoop, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -167,6 +179,18 @@ void USRPInputSettings::applySettings(const QStringList& settingsKeys, const USR
|
||||
if (settingsKeys.contains("transverterDeltaFrequency")) {
|
||||
m_transverterDeltaFrequency = settings.m_transverterDeltaFrequency;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset")) {
|
||||
m_replayOffset = settings.m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength")) {
|
||||
m_replayLength = settings.m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep")) {
|
||||
m_replayStep = settings.m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop")) {
|
||||
m_replayLoop = settings.m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI")) {
|
||||
m_useReverseAPI = settings.m_useReverseAPI;
|
||||
}
|
||||
@ -227,6 +251,18 @@ QString USRPInputSettings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("transverterDeltaFrequency") || force) {
|
||||
ostr << " m_transverterDeltaFrequency: " << m_transverterDeltaFrequency;
|
||||
}
|
||||
if (settingsKeys.contains("replayOffset") || force) {
|
||||
ostr << " m_replayOffset: " << m_replayOffset;
|
||||
}
|
||||
if (settingsKeys.contains("replayLength") || force) {
|
||||
ostr << " m_replayLength: " << m_replayLength;
|
||||
}
|
||||
if (settingsKeys.contains("replayStep") || force) {
|
||||
ostr << " m_replayStep: " << m_replayStep;
|
||||
}
|
||||
if (settingsKeys.contains("replayLoop") || force) {
|
||||
ostr << " m_replayLoop: " << m_replayLoop;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI") || force) {
|
||||
ostr << " m_useReverseAPI: " << m_useReverseAPI;
|
||||
}
|
||||
|
@ -52,6 +52,10 @@ struct USRPInputSettings
|
||||
QString m_clockSource;
|
||||
bool m_transverterMode;
|
||||
qint64 m_transverterDeltaFrequency;
|
||||
float m_replayOffset; //!< Replay offset in seconds
|
||||
float m_replayLength; //!< Replay buffer size in seconds
|
||||
float m_replayStep; //!< Replay forward/back step size in seconds
|
||||
bool m_replayLoop; //!< Replay buffer repeatedly without recording new data
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
|
@ -26,13 +26,15 @@
|
||||
#include "usrpinputsettings.h"
|
||||
#include "usrpinputthread.h"
|
||||
|
||||
USRPInputThread::USRPInputThread(uhd::rx_streamer::sptr stream, size_t bufSamples, SampleSinkFifo* sampleFifo, QObject* parent) :
|
||||
USRPInputThread::USRPInputThread(uhd::rx_streamer::sptr stream, size_t bufSamples,
|
||||
SampleSinkFifo* sampleFifo, ReplayBuffer<qint16> *replayBuffer, QObject* parent) :
|
||||
QThread(parent),
|
||||
m_running(false),
|
||||
m_stream(stream),
|
||||
m_bufSamples(bufSamples),
|
||||
m_convertBuffer(bufSamples),
|
||||
m_sampleFifo(sampleFifo),
|
||||
m_replayBuffer(replayBuffer),
|
||||
m_log2Decim(0)
|
||||
{
|
||||
// *2 as samples are I+Q
|
||||
@ -53,8 +55,15 @@ void USRPInputThread::issueStreamCmd(bool start)
|
||||
stream_cmd.stream_now = true;
|
||||
stream_cmd.time_spec = uhd::time_spec_t();
|
||||
|
||||
if (m_stream)
|
||||
{
|
||||
m_stream->issue_stream_cmd(stream_cmd);
|
||||
qDebug() << "USRPInputThread::issueStreamCmd " << (start ? "start" : "stop");
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "USRPInputThread::issueStreamCmd m_stream is null";
|
||||
}
|
||||
}
|
||||
|
||||
void USRPInputThread::startWork()
|
||||
@ -184,10 +193,30 @@ void USRPInputThread::run()
|
||||
}
|
||||
|
||||
// Decimate according to specified log2 (ex: log2=4 => decim=16)
|
||||
void USRPInputThread::callbackIQ(const qint16* buf, qint32 len)
|
||||
void USRPInputThread::callbackIQ(const qint16* inBuf, qint32 len)
|
||||
{
|
||||
SampleVector::iterator it = m_convertBuffer.begin();
|
||||
|
||||
// Save data to replay buffer
|
||||
m_replayBuffer->lock();
|
||||
bool replayEnabled = m_replayBuffer->getSize() > 0;
|
||||
if (replayEnabled) {
|
||||
m_replayBuffer->write(inBuf, len);
|
||||
}
|
||||
|
||||
const qint16* buf = inBuf;
|
||||
qint32 remaining = len;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
// Choose between live data or replayed data
|
||||
if (replayEnabled && m_replayBuffer->useReplay()) {
|
||||
len = m_replayBuffer->read(remaining, buf);
|
||||
} else {
|
||||
len = remaining;
|
||||
}
|
||||
remaining -= len;
|
||||
|
||||
switch (m_log2Decim)
|
||||
{
|
||||
case 0:
|
||||
@ -214,6 +243,9 @@ void USRPInputThread::callbackIQ(const qint16* buf, qint32 len)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_replayBuffer->unlock();
|
||||
|
||||
m_sampleFifo->write(m_convertBuffer.begin(), it);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "dsp/decimators.h"
|
||||
#include "dsp/replaybuffer.h"
|
||||
#include "usrp/deviceusrpshared.h"
|
||||
#include "usrp/deviceusrp.h"
|
||||
|
||||
@ -39,7 +40,8 @@ class USRPInputThread : public QThread, public DeviceUSRPShared::ThreadInterface
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
USRPInputThread(uhd::rx_streamer::sptr stream, size_t bufSamples, SampleSinkFifo* sampleFifo, QObject* parent = 0);
|
||||
USRPInputThread(uhd::rx_streamer::sptr stream, size_t bufSamples, SampleSinkFifo* sampleFifo,
|
||||
ReplayBuffer<qint16> *replayBuffer, QObject* parent = 0);
|
||||
~USRPInputThread();
|
||||
|
||||
virtual void startWork();
|
||||
@ -64,6 +66,7 @@ private:
|
||||
size_t m_bufSamples;
|
||||
SampleVector m_convertBuffer;
|
||||
SampleSinkFifo* m_sampleFifo;
|
||||
ReplayBuffer<qint16> *m_replayBuffer;
|
||||
|
||||
unsigned int m_log2Decim; // soft decimation
|
||||
|
||||
|
@ -399,6 +399,7 @@ set(sdrbase_HEADERS
|
||||
dsp/projector.h
|
||||
dsp/raisedcosine.h
|
||||
dsp/recursivefilters.h
|
||||
dsp/replaybuffer.h
|
||||
dsp/rootraisedcosine.h
|
||||
dsp/samplemififo.h
|
||||
dsp/samplemofifo.h
|
||||
|
218
sdrbase/dsp/replaybuffer.h
Normal file
218
sdrbase/dsp/replaybuffer.h
Normal file
@ -0,0 +1,218 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_REPLAYBUFFER_H
|
||||
#define INCLUDE_REPLAYBUFFER_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "dsp/wavfilerecord.h"
|
||||
|
||||
// Circular buffer for storing and replaying IQ samples
|
||||
// lock/unlock should be called manually before write/read/getSize
|
||||
// (so it only needs to be locked once for write then multiple reads).
|
||||
template <typename T>
|
||||
class ReplayBuffer {
|
||||
|
||||
public:
|
||||
ReplayBuffer() :
|
||||
m_data(1000000*2, 0),
|
||||
m_write(0),
|
||||
m_read(0),
|
||||
m_readOffset(0),
|
||||
m_count(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool useReplay() const
|
||||
{
|
||||
return (m_readOffset > 0) || m_loop;
|
||||
}
|
||||
|
||||
void setSize(float lengthInSeconds, int sampleRate)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
unsigned int newSize = lengthInSeconds * sampleRate * 2;
|
||||
unsigned int oldSize = m_data.size();
|
||||
|
||||
if (newSize == oldSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save most recent data
|
||||
if (m_write >= newSize)
|
||||
{
|
||||
memmove(&m_data[0], &m_data[m_write-newSize], newSize);
|
||||
m_write = 0;
|
||||
m_count = newSize;
|
||||
m_data.resize(newSize);
|
||||
}
|
||||
else if (newSize < oldSize)
|
||||
{
|
||||
memmove(&m_data[m_write], &m_data[oldSize-(newSize-m_write)], newSize-m_write);
|
||||
m_count = std::min(m_count, newSize);
|
||||
m_data.resize(newSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_data.resize(newSize);
|
||||
memmove(&m_data[newSize-(oldSize-m_write)], &m_data[m_write], oldSize-m_write);
|
||||
}
|
||||
}
|
||||
|
||||
// lock()/unlock() should be called before/after calling this function
|
||||
int getSize() const {
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
void setLoop(bool loop) {
|
||||
m_loop = loop;
|
||||
}
|
||||
|
||||
bool getLoop() const {
|
||||
return m_loop;
|
||||
}
|
||||
|
||||
// Copy count samples into circular buffer (1 I and Q pair should have count = 2)
|
||||
// When loop is set, samples aren't copied, but write pointer is still updated
|
||||
// lock()/unlock() should be called before/after calling this function
|
||||
void write(const T* data, unsigned int count)
|
||||
{
|
||||
unsigned int totalLen = count;
|
||||
while (totalLen > 0)
|
||||
{
|
||||
unsigned int len = std::min((unsigned int)m_data.size() - m_write, totalLen);
|
||||
if (!m_loop) {
|
||||
memcpy(&m_data[m_write], data, len * sizeof(T));
|
||||
}
|
||||
m_write += len;
|
||||
if (m_write >= m_data.size()) {
|
||||
m_write = 0;
|
||||
}
|
||||
m_count += len;
|
||||
if (m_count > m_data.size()) {
|
||||
m_count = m_data.size();
|
||||
}
|
||||
data += len;
|
||||
totalLen -= len;
|
||||
}
|
||||
}
|
||||
|
||||
// Get pointer to count samples - actual number available is returned
|
||||
// lock()/unlock() should be called before/after calling this function
|
||||
unsigned int read(unsigned int count, const T*& ptr)
|
||||
{
|
||||
unsigned int totalLen = count;
|
||||
unsigned int len = std::min((unsigned int)m_data.size() - m_read, totalLen);
|
||||
ptr = &m_data[m_read];
|
||||
m_read += len;
|
||||
if (m_read >= m_data.size()) {
|
||||
m_read = 0;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void setReadOffset(unsigned int offset)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_readOffset = offset;
|
||||
offset = std::min(offset, (unsigned int)(m_data.size() - 1));
|
||||
int read = m_write - offset;
|
||||
while (read < 0) {
|
||||
read += m_data.size();
|
||||
}
|
||||
m_read = (unsigned int) read;
|
||||
}
|
||||
|
||||
unsigned int getReadOffset()
|
||||
{
|
||||
return m_readOffset;
|
||||
}
|
||||
|
||||
// Save buffer to .wav file
|
||||
void save(const QString& filename, quint32 sampleRate, quint64 centerFrequency)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
WavFileRecord wavFile(sampleRate, centerFrequency);
|
||||
QString baseName = filename;
|
||||
QFileInfo fileInfo(baseName);
|
||||
QString suffix = fileInfo.suffix();
|
||||
if (!suffix.isEmpty()) {
|
||||
baseName.chop(suffix.length() + 1);
|
||||
}
|
||||
wavFile.setFileName(baseName);
|
||||
|
||||
wavFile.startRecording();
|
||||
int offset = m_write + m_data.size() - m_count;
|
||||
for (int i = 0; i < m_count; i += 2)
|
||||
{
|
||||
int idx = (i + offset) % m_data.size();
|
||||
qint16 l = conv(m_data[idx]);
|
||||
qint16 r = conv(m_data[idx+1]);
|
||||
wavFile.write(l, r);
|
||||
}
|
||||
wavFile.stopRecording();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
std::fill(m_data.begin(), m_data.end(), 0);
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
void lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
void unlock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_data;
|
||||
unsigned int m_write; // Write index
|
||||
unsigned int m_read; // Read index
|
||||
unsigned int m_readOffset;
|
||||
unsigned int m_count; // Count of number of valid samples in the buffer
|
||||
bool m_loop;
|
||||
QMutex m_mutex;
|
||||
|
||||
qint16 conv(quint8 data) const
|
||||
{
|
||||
return (data - 128) << 8;
|
||||
}
|
||||
|
||||
qint16 conv(qint16 data) const
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
qint16 conv(float data) const
|
||||
{
|
||||
return (qint16)(data * SDR_RX_SCALEF);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // INCLUDE_REPLAYBUFFER_H
|
@ -15,6 +15,9 @@
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "gui/pluginpresetsdialog.h"
|
||||
#include "gui/dialogpositioner.h"
|
||||
#include "device/deviceapi.h"
|
||||
@ -35,6 +38,8 @@ BasicDeviceSettingsDialog::BasicDeviceSettingsDialog(QWidget *parent) :
|
||||
setReverseAPIAddress("127.0.0.1");
|
||||
setReverseAPIPort(8888);
|
||||
setReverseAPIDeviceIndex(0);
|
||||
setReplayBytesPerSecond(0);
|
||||
setReplayStep(5.0f);
|
||||
}
|
||||
|
||||
BasicDeviceSettingsDialog::~BasicDeviceSettingsDialog()
|
||||
@ -42,6 +47,48 @@ BasicDeviceSettingsDialog::~BasicDeviceSettingsDialog()
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::setReplayBytesPerSecond(int bytesPerSecond)
|
||||
{
|
||||
bool enabled = bytesPerSecond > 0;
|
||||
ui->replayLengthLabel->setEnabled(enabled);
|
||||
ui->replayLength->setEnabled(enabled);
|
||||
ui->replayLengthUnits->setEnabled(enabled);
|
||||
ui->replayLengthSize->setEnabled(enabled);
|
||||
ui->replayStepLabel->setEnabled(enabled);
|
||||
ui->replayStep->setEnabled(enabled);
|
||||
ui->replayStepUnits->setEnabled(enabled);
|
||||
m_replayBytesPerSecond = bytesPerSecond;
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::setReplayLength(float replayLength)
|
||||
{
|
||||
m_replayLength = replayLength;
|
||||
ui->replayLength->setValue(replayLength);
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::on_replayLength_valueChanged(double value)
|
||||
{
|
||||
m_replayLength = (float)value;
|
||||
float size = m_replayLength * m_replayBytesPerSecond;
|
||||
if (size < 1e6) {
|
||||
ui->replayLengthSize->setText("(<1MB)");
|
||||
} else {
|
||||
ui->replayLengthSize->setText(QString("(%1MB)").arg(std::ceil(size/1e6)));
|
||||
}
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::setReplayStep(float replayStep)
|
||||
{
|
||||
m_replayStep = replayStep;
|
||||
ui->replayStep->setValue(replayStep);
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::on_replayStep_valueChanged(double value)
|
||||
|
||||
{
|
||||
m_replayStep = value;
|
||||
}
|
||||
|
||||
void BasicDeviceSettingsDialog::setUseReverseAPI(bool useReverseAPI)
|
||||
{
|
||||
m_useReverseAPI = useReverseAPI;
|
||||
|
@ -42,6 +42,11 @@ public:
|
||||
void setReverseAPIAddress(const QString& address);
|
||||
void setReverseAPIPort(uint16_t port);
|
||||
void setReverseAPIDeviceIndex(uint16_t deviceIndex);
|
||||
void setReplayBytesPerSecond(int bytesPerSecond);
|
||||
void setReplayLength(float replayLength);
|
||||
float getReplayLength() const { return m_replayLength; }
|
||||
void setReplayStep(float replayStep);
|
||||
float getReplayStep() const { return m_replayStep; }
|
||||
|
||||
private slots:
|
||||
void on_reverseAPI_toggled(bool checked);
|
||||
@ -49,6 +54,8 @@ private slots:
|
||||
void on_reverseAPIPort_editingFinished();
|
||||
void on_reverseAPIDeviceIndex_editingFinished();
|
||||
void on_presets_clicked();
|
||||
void on_replayLength_valueChanged(double value);
|
||||
void on_replayStep_valueChanged(double value);
|
||||
void accept();
|
||||
|
||||
private:
|
||||
@ -58,6 +65,9 @@ private:
|
||||
uint16_t m_reverseAPIPort;
|
||||
uint16_t m_reverseAPIDeviceIndex;
|
||||
bool m_hasChanged;
|
||||
int m_replayBytesPerSecond;
|
||||
float m_replayLength;
|
||||
float m_replayStep;
|
||||
};
|
||||
|
||||
#endif // BASICDEVICESETTINGSDIALOG_H
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>394</width>
|
||||
<height>77</height>
|
||||
<height>137</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -133,6 +133,131 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayOffsetLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLengthLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Replay buffer length</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="replayLength">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Length of replay buffer in seconds</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLengthUnits">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayLengthSize">
|
||||
<property name="toolTip">
|
||||
<string>Size of replay buffer in megabytes</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(100MB)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="replayOffsetSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="replayStepLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="replayStepLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Replay step</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="replayStep">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Step time in seconds</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="replayStepUnits">
|
||||
<property name="text">
|
||||
<string>s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="replayStepSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
|
Loading…
Reference in New Issue
Block a user