1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 17:28:50 -05:00

Morse Decoder: basic functionality

This commit is contained in:
f4exb 2024-05-17 18:24:36 +02:00
parent 4387f54f0d
commit 39bc799509
11 changed files with 437 additions and 81 deletions

View File

@ -109,7 +109,6 @@ public:
private: private:
DataFifo *m_dataFifo; DataFifo *m_dataFifo;
int m_channelSampleRate;
int m_sinkSampleRate; int m_sinkSampleRate;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object

View File

@ -18,6 +18,7 @@ set(morsedecoder_HEADERS
include_directories( include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${GGMORSE_INCLUDE_DIR}
) )
if(NOT SERVER_MODE) if(NOT SERVER_MODE)
@ -51,6 +52,7 @@ target_link_libraries(${TARGET_NAME}
${TARGET_LIB} ${TARGET_LIB}
sdrbase sdrbase
${TARGET_LIB_GUI} ${TARGET_LIB_GUI}
${GGMORSE_LIBRARIES}
) )
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -38,6 +38,7 @@ MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgReportChannels, Message) MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgReportChannels, Message)
MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgSelectChannel, Message) MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgSelectChannel, Message)
MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgReportSampleRate, Message) MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgReportSampleRate, Message)
MESSAGE_CLASS_DEFINITION(MorseDecoder::MsgReportText, Message)
const char* const MorseDecoder::m_featureIdURI = "sdrangel.feature.morsedecoder"; const char* const MorseDecoder::m_featureIdURI = "sdrangel.feature.morsedecoder";
const char* const MorseDecoder::m_featureId = "MorseDecoder"; const char* const MorseDecoder::m_featureId = "MorseDecoder";
@ -238,6 +239,26 @@ bool MorseDecoder::handleMessage(const Message& cmd)
return true; return true;
} }
else if (MsgReportText::match(cmd))
{
MsgReportText& report = (MsgReportText&) cmd;
// Write to log file
if (m_logFile.isOpen())
{
// Format text
m_logStream << MorseDecoderSettings::formatText(report.getText());
}
if (getMessageQueueToGUI())
{
MsgReportText *msg = new MsgReportText(report);
getMessageQueueToGUI()->push(msg);
}
// TODO: send via UDP
return true;
}
else else
{ {
return false; return false;
@ -278,6 +299,29 @@ void MorseDecoder::applySettings(const MorseDecoderSettings& settings, const QLi
m_worker->getInputMessageQueue()->push(msg); m_worker->getInputMessageQueue()->push(msg);
} }
if (settingsKeys.contains("logEnabled")
|| settingsKeys.contains("logFilename")
|| force)
{
if (m_logFile.isOpen())
{
m_logStream.flush();
m_logFile.close();
}
if (settings.m_logEnabled && !settings.m_logFilename.isEmpty())
{
m_logFile.setFileName(settings.m_logFilename);
if (m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
{
qDebug() << "RttyDemod::applySettings - Logging to: " << settings.m_logFilename;
m_logStream.setDevice(&m_logFile);
}
else
{
qDebug() << "RttyDemod::applySettings - Unable to open log file: " << settings.m_logFilename;
}
}
}
if (settings.m_useReverseAPI) if (settings.m_useReverseAPI)
{ {

View File

@ -21,6 +21,8 @@
#include <QHash> #include <QHash>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QRecursiveMutex> #include <QRecursiveMutex>
#include <QFile>
#include <QTextStream>
#include "feature/feature.h" #include "feature/feature.h"
#include "util/message.h" #include "util/message.h"
@ -148,6 +150,29 @@ public:
{} {}
}; };
class MsgReportText : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getText() const { return m_text; }
static MsgReportText* create(const QString& text) {
return new MsgReportText(text);
}
float m_estimatedPitchHz;
float m_estimatedSpeedWPM;
float m_signalThreshold;
float m_costFunction;
private:
QString m_text;
MsgReportText(const QString& text) :
m_text(text)
{}
};
MorseDecoder(WebAPIAdapterInterface *webAPIAdapterInterface); MorseDecoder(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~MorseDecoder(); virtual ~MorseDecoder();
virtual void destroy() { delete this; } virtual void destroy() { delete this; }
@ -202,6 +227,8 @@ private:
ChannelAPI *m_selectedChannel; ChannelAPI *m_selectedChannel;
ObjectPipe *m_dataPipe; ObjectPipe *m_dataPipe;
int m_sampleRate; int m_sampleRate;
QFile m_logFile;
QTextStream m_logStream;
QNetworkAccessManager *m_networkManager; QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest; QNetworkRequest m_networkRequest;

View File

@ -17,6 +17,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QFileDialog> #include <QFileDialog>
#include <QScrollBar>
#include "feature/featureuiset.h" #include "feature/featureuiset.h"
#include "dsp/spectrumvis.h" #include "dsp/spectrumvis.h"
@ -109,6 +110,12 @@ bool MorseDecoderGUI::handleMessage(const Message& message)
return true; return true;
} }
else if (MorseDecoder::MsgReportText::match(message))
{
MorseDecoder::MsgReportText& report = (MorseDecoder::MsgReportText&) message;
textReceived(report.getText());
updateMorseStats(report.m_estimatedPitchHz, report.m_estimatedSpeedWPM, report.m_costFunction);
}
return false; return false;
} }
@ -125,6 +132,35 @@ void MorseDecoderGUI::handleInputMessages()
} }
} }
void MorseDecoderGUI::textReceived(const QString& text)
{
// Is the scroll bar at the bottom?
int scrollPos = ui->text->verticalScrollBar()->value();
bool atBottom = scrollPos >= ui->text->verticalScrollBar()->maximum();
// Move cursor to end where we want to append new text
// (user may have moved it by clicking / highlighting text)
ui->text->moveCursor(QTextCursor::End);
// Restore scroll position
ui->text->verticalScrollBar()->setValue(scrollPos);
// Format and insert text
ui->text->insertPlainText(MorseDecoderSettings::formatText(text));
// Scroll to bottom, if we we're previously at the bottom
if (atBottom) {
ui->text->verticalScrollBar()->setValue(ui->text->verticalScrollBar()->maximum());
}
}
void MorseDecoderGUI::updateMorseStats(float estPitch, float estWPM, float cost)
{
ui->pitchText->setText(QString("%1 Hz").arg(estPitch, 0, 'f', 1));
ui->speedText->setText(QString("%1 WPM").arg(estWPM, 0, 'f', 0));
ui->cText->setText(QString("%1").arg(cost, 0, 'f', 3));
}
void MorseDecoderGUI::onWidgetRolled(QWidget* widget, bool rollDown) void MorseDecoderGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{ {
(void) widget; (void) widget;
@ -157,6 +193,7 @@ MorseDecoderGUI::MorseDecoderGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISe
m_morseDecoder = reinterpret_cast<MorseDecoder*>(feature); m_morseDecoder = reinterpret_cast<MorseDecoder*>(feature);
m_morseDecoder->setMessageQueueToGUI(&m_inputMessageQueue); m_morseDecoder->setMessageQueueToGUI(&m_inputMessageQueue);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
@ -199,6 +236,9 @@ void MorseDecoderGUI::displaySettings()
setTitle(m_settings.m_title); setTitle(m_settings.m_title);
blockApplySettings(true); blockApplySettings(true);
getRollupContents()->restoreState(m_rollupState); getRollupContents()->restoreState(m_rollupState);
ui->statLock->setChecked(!m_settings.m_auto);
ui->logFilename->setToolTip(QString(".txt log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled);
blockApplySettings(false); blockApplySettings(false);
} }
@ -304,6 +344,44 @@ void MorseDecoderGUI::on_channelApply_clicked()
} }
} }
void MorseDecoderGUI::on_statLock_toggled(bool checked)
{
m_settings.m_auto = !checked;
m_settingsKeys.append("auto");
applySettings();
}
void MorseDecoderGUI::on_logEnable_clicked(bool checked)
{
m_settings.m_logEnabled = checked;
m_settingsKeys.append("logEnabled");
applySettings();
}
void MorseDecoderGUI::on_logFilename_clicked()
{
// Get filename to save to
QFileDialog fileDialog(nullptr, "Select file to log received text to", "", "*.txt");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
m_settings.m_logFilename = fileNames[0];
ui->logFilename->setToolTip(QString(".txt log filename: %1").arg(m_settings.m_logFilename));
m_settingsKeys.append("logFilename");
applySettings();
}
}
}
void MorseDecoderGUI::on_clearTable_clicked()
{
ui->text->clear();
}
void MorseDecoderGUI::tick() void MorseDecoderGUI::tick()
{ {
} }
@ -353,4 +431,8 @@ void MorseDecoderGUI::makeUIConnections()
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &MorseDecoderGUI::on_startStop_toggled); QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &MorseDecoderGUI::on_startStop_toggled);
QObject::connect(ui->channels, qOverload<int>(&QComboBox::currentIndexChanged), this, &MorseDecoderGUI::on_channels_currentIndexChanged); QObject::connect(ui->channels, qOverload<int>(&QComboBox::currentIndexChanged), this, &MorseDecoderGUI::on_channels_currentIndexChanged);
QObject::connect(ui->channelApply, &QPushButton::clicked, this, &MorseDecoderGUI::on_channelApply_clicked); QObject::connect(ui->channelApply, &QPushButton::clicked, this, &MorseDecoderGUI::on_channelApply_clicked);
QObject::connect(ui->statLock, &QToolButton::toggled, this, &MorseDecoderGUI::on_statLock_toggled);
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &MorseDecoderGUI::on_logEnable_clicked);
QObject::connect(ui->logFilename, &QToolButton::clicked, this, &MorseDecoderGUI::on_logFilename_clicked);
QObject::connect(ui->clearTable, &QPushButton::clicked, this, &MorseDecoderGUI::on_clearTable_clicked);
} }

View File

@ -78,6 +78,8 @@ private:
void displaySampleRate(int sampleRate); void displaySampleRate(int sampleRate);
void updateChannelList(); void updateChannelList();
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void textReceived(const QString& text);
void updateMorseStats(float estPitch, float estWPM, float cost);
void makeUIConnections(); void makeUIConnections();
private slots: private slots:
@ -87,6 +89,10 @@ private slots:
void on_startStop_toggled(bool checked); void on_startStop_toggled(bool checked);
void on_channels_currentIndexChanged(int index); void on_channels_currentIndexChanged(int index);
void on_channelApply_clicked(); void on_channelApply_clicked();
void on_statLock_toggled(bool checked);
void on_clearTable_clicked();
void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked();
void updateStatus(); void updateStatus();
void tick(); void tick();

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>407</width> <width>422</width>
<height>407</height> <height>810</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -18,7 +18,7 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>407</width> <width>422</width>
<height>407</height> <height>407</height>
</size> </size>
</property> </property>
@ -36,13 +36,13 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>10</y> <y>10</y>
<width>401</width> <width>420</width>
<height>121</height> <height>121</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>401</width> <width>420</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -307,13 +307,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item> <item>
<widget class="QToolButton" name="statLock"> <widget class="QToolButton" name="statLock">
<property name="toolTip"> <property name="toolTip">
@ -332,6 +325,42 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="cLabel">
<property name="text">
<string>Cost</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="cText">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>3000.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="ButtonSwitch" name="logEnable"> <widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize"> <property name="maximumSize">
@ -383,42 +412,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="cLabel">
<property name="text">
<string>C</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="cText">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>3000.000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>
@ -428,19 +421,22 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>130</y> <y>130</y>
<width>401</width> <width>420</width>
<height>271</height> <height>271</height>
</rect> </rect>
</property> </property>
<property name="windowTitle">
<string>Morse Text</string>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>420</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QTextEdit" name="text"/> <widget class="QTextEdit" name="text"/>

View File

@ -51,6 +51,7 @@ void MorseDecoderSettings::resetToDefaults()
m_udpPort = 9999; m_udpPort = 9999;
m_logFilename = "cw_log.txt"; m_logFilename = "cw_log.txt";
m_logEnabled = false; m_logEnabled = false;
m_auto = true;
} }
QByteArray MorseDecoderSettings::serialize() const QByteArray MorseDecoderSettings::serialize() const
@ -77,6 +78,7 @@ QByteArray MorseDecoderSettings::serialize() const
s.writeU32(24, m_udpPort); s.writeU32(24, m_udpPort);
s.writeString(25, m_logFilename); s.writeString(25, m_logFilename);
s.writeBool(26, m_logEnabled); s.writeBool(26, m_logEnabled);
s.writeBool(27, m_auto);
return s.final(); return s.final();
} }
@ -135,6 +137,7 @@ bool MorseDecoderSettings::deserialize(const QByteArray& data)
d.readString(25, &m_logFilename, "cw_log.txt"); d.readString(25, &m_logFilename, "cw_log.txt");
d.readBool(26, &m_logEnabled, false); d.readBool(26, &m_logEnabled, false);
d.readBool(27, &m_auto, true);
return true; return true;
} }
@ -186,6 +189,15 @@ void MorseDecoderSettings::applySettings(const QStringList& settingsKeys, const
if (settingsKeys.contains("logFilename")) { if (settingsKeys.contains("logFilename")) {
m_logFilename = settings.m_logFilename; m_logFilename = settings.m_logFilename;
} }
if (settingsKeys.contains("auto")) {
m_auto = settings.m_auto;
}
if (settingsKeys.contains("logEnabled")) {
m_logEnabled = settings.m_logEnabled;
}
if (settingsKeys.contains("logFilename")) {
m_logFilename = settings.m_logFilename;
}
} }
QString MorseDecoderSettings::getDebugString(const QStringList& settingsKeys, bool force) const QString MorseDecoderSettings::getDebugString(const QStringList& settingsKeys, bool force) const
@ -231,6 +243,33 @@ QString MorseDecoderSettings::getDebugString(const QStringList& settingsKeys, bo
if (settingsKeys.contains("logFilename") || force) { if (settingsKeys.contains("logFilename") || force) {
ostr << " m_logFilename: " << m_logFilename.toStdString(); ostr << " m_logFilename: " << m_logFilename.toStdString();
} }
if (settingsKeys.contains("auto") || force) {
ostr << " m_auto: " << m_auto;
}
if (settingsKeys.contains("logEnabled") || force) {
ostr << " m_logEnabled: " << m_logEnabled;
}
if (settingsKeys.contains("logFilename") || force) {
ostr << " m_logFilename: " << m_logFilename.toStdString();
}
return QString(ostr.str().c_str()); return QString(ostr.str().c_str());
} }
QString MorseDecoderSettings::formatText(const QString& text)
{
// Format text
QString showText = text.simplified();
if (text.size() > 3)
{
if (text.right(1)[0].isSpace()) {
showText.append(text.right(1));
}
if (text.left(1)[0].isSpace()) {
showText = text.left(1) + showText;
}
}
return showText;
}

View File

@ -40,6 +40,7 @@ struct MorseDecoderSettings
uint16_t m_udpPort; uint16_t m_udpPort;
QString m_logFilename; QString m_logFilename;
bool m_logEnabled; bool m_logEnabled;
bool m_auto; //!< Auto pitch and speed
MorseDecoderSettings(); MorseDecoderSettings();
void resetToDefaults(); void resetToDefaults();
@ -50,6 +51,8 @@ struct MorseDecoderSettings
void applySettings(const QStringList& settingsKeys, const MorseDecoderSettings& settings); void applySettings(const QStringList& settingsKeys, const MorseDecoderSettings& settings);
QString getDebugString(const QStringList& settingsKeys, bool force=false) const; QString getDebugString(const QStringList& settingsKeys, bool force=false) const;
static QString formatText(const QString& text);
static const QStringList m_channelURIs; static const QStringList m_channelURIs;
}; };

View File

@ -20,6 +20,7 @@
#include "dsp/scopevis.h" #include "dsp/scopevis.h"
#include "dsp/datafifo.h" #include "dsp/datafifo.h"
#include "morsedecoder.h"
#include "morsedecoderworker.h" #include "morsedecoderworker.h"
MESSAGE_CLASS_DEFINITION(MorseDecoderWorker::MsgConfigureMorseDecoderWorker, Message) MESSAGE_CLASS_DEFINITION(MorseDecoderWorker::MsgConfigureMorseDecoderWorker, Message)
@ -28,15 +29,30 @@ MESSAGE_CLASS_DEFINITION(MorseDecoderWorker::MsgConnectFifo, Message)
MorseDecoderWorker::MorseDecoderWorker() : MorseDecoderWorker::MorseDecoderWorker() :
m_dataFifo(nullptr), m_dataFifo(nullptr),
m_msgQueueToFeature(nullptr), m_msgQueueToFeature(nullptr),
m_sampleBufferSize(0), m_auto(false),
m_nbBytes(0) m_pitchHz(-1),
m_speedWPM(-1)
{ {
qDebug("MorseDecoderWorker::MorseDecoderWorker"); qDebug("MorseDecoderWorker::MorseDecoderWorker");
m_ggMorseParameters = new GGMorse::Parameters{
48000.0f, // sampleRateInp: capture sample rate
48000.0f, // sampleRateOut: playback sample rate
GGMorse::kDefaultSamplesPerFrame, // samplesPerFrame: number of samples per audio frame (128)
GGMORSE_SAMPLE_FORMAT_I16, // sampleFormatInp: format of the captured audio samples
GGMORSE_SAMPLE_FORMAT_I16 // sampleFormatOut: format of the playback audio samples
};
m_ggMorse = new GGMorse(*m_ggMorseParameters);
auto parametersDecode = m_ggMorse->getDefaultParametersDecode(); // auto pitch [200, 1200 Hz], auto speed, apply low pass and high pass
parametersDecode.applyFilterHighPass = false;
m_ggMorse->setParametersDecode(parametersDecode);
applySampleRate(48000);
} }
MorseDecoderWorker::~MorseDecoderWorker() MorseDecoderWorker::~MorseDecoderWorker()
{ {
m_inputMessageQueue.clear(); m_inputMessageQueue.clear();
delete m_ggMorse;
delete m_ggMorseParameters;
} }
void MorseDecoderWorker::reset() void MorseDecoderWorker::reset()
@ -63,31 +79,113 @@ void MorseDecoderWorker::feedPart(
DataFifo::DataType dataType DataFifo::DataType dataType
) )
{ {
int nbBytes; int countBytes = end - begin;
int bytesLeft = m_bytesBufferSize - m_bytesBufferCount;
switch(dataType) if (dataType == DataFifo::DataTypeCI16) // (re, im) -> one sample conversion
{ {
case DataFifo::DataTypeCI16: countBytes /= 2;
nbBytes = 4;
break; if (countBytes != m_convBuffer.size()) {
case DataFifo::DataTypeI16: m_convBuffer.resize(countBytes);
default: }
nbBytes = 2;
int16_t *s = (int16_t*) begin;
int16_t *b = (int16_t*) m_convBuffer.begin();
for (int is = 0; is < countBytes; is++)
{
int32_t re = s[2*is];
int32_t im = s[2*is+1];
b[is] = (int16_t) ((re+im) / 2);
}
if (countBytes >= bytesLeft)
{
std::copy(m_convBuffer.begin(), m_convBuffer.begin() + bytesLeft, m_bytesBuffer.begin() + m_bytesBufferCount); // fill buffer
int unprocessedBytes = processBuffer(m_convBuffer);
std::copy(m_convBuffer.begin() + bytesLeft - unprocessedBytes, m_convBuffer.end(), m_bytesBuffer.begin());
m_bytesBufferCount = bytesLeft + unprocessedBytes;
}
else
{
std::copy(m_convBuffer.begin(), m_convBuffer.end(), m_bytesBuffer.begin() + m_bytesBufferCount);
m_bytesBufferCount += countBytes;
}
}
else
{
if (countBytes >= bytesLeft)
{
std::copy(begin, begin + bytesLeft, m_bytesBuffer.begin() + m_bytesBufferCount); // fill buffer
int unprocessedBytes = processBuffer(m_bytesBuffer);
std::copy(begin + bytesLeft - unprocessedBytes, end, m_bytesBuffer.begin());
m_bytesBufferCount = bytesLeft + unprocessedBytes;
}
else
{
std::copy(begin, end, m_bytesBuffer.begin() + m_bytesBufferCount);
m_bytesBufferCount += countBytes;
}
}
}
int MorseDecoderWorker::processBuffer(QByteArray& bytesBuffer)
{
uint32_t samplesHave = bytesBuffer.size() / 2;
uint32_t samplesTotal = bytesBuffer.size() / 2;
int bytesLeft = 0;
GGMorse::CBWaveformInp cbWaveformInp = [&](void * data, uint32_t nMaxBytes)
{
if (samplesHave*2 < nMaxBytes)
{
if (samplesHave != 0)
{
bytesLeft = samplesHave*2;
qDebug("MorseDecoderWorker::processBuffer::cbWaveformInp: nMaxBytes: %u / %u samples left buffer size: %u",
nMaxBytes, samplesHave, bytesBuffer.size());
}
return 0;
}
samplesHave -= nMaxBytes/2;
// qDebug("MorseDecoderWorker::processBuffer::cbWaveformInp: samplesTotal: %u samplesHave: %u nMaxBytes: %u",
// samplesTotal, samplesHave, nMaxBytes);
memcpy(data, bytesBuffer.data() + (samplesTotal - samplesHave)*2, nMaxBytes);
return (int) nMaxBytes;
};
bool result = m_ggMorse->decode(cbWaveformInp);
if (result)
{
GGMorse::TxRx dst;
m_ggMorse->takeRxData(dst);
QString text;
std::for_each(
dst.begin(),
dst.end(),
[&](const uint8_t c) { text.append(c); }
);
const GGMorse::Statistics& stats = m_ggMorse->getStatistics();
m_pitchHz = stats.estimatedPitch_Hz;
m_speedWPM = stats.estimatedSpeed_wpm;
if (m_msgQueueToFeature)
{
MorseDecoder::MsgReportText *msg = MorseDecoder::MsgReportText::create(text);
msg->m_costFunction = stats.costFunction;
msg->m_estimatedPitchHz = m_settings.m_auto ? stats.estimatedPitch_Hz : m_pitchHz;
msg->m_estimatedSpeedWPM = m_settings.m_auto ? stats.estimatedSpeed_wpm : m_speedWPM;
msg->m_signalThreshold = stats.signalThreshold;
m_msgQueueToFeature->push(msg);
}
} }
m_nbBytes = nbBytes; return bytesLeft;
int countSamples = (end - begin) / nbBytes;
if (countSamples > m_sampleBufferSize)
{
m_sampleBuffer.resize(countSamples);
m_sampleBufferSize = countSamples;
}
// TODO
// for (int i = 0; i < countSamples; i++) {
// processSample(dataType, begin, countSamples, i);
// }
} }
void MorseDecoderWorker::handleInputMessages() void MorseDecoderWorker::handleInputMessages()
@ -120,7 +218,7 @@ bool MorseDecoderWorker::handleMessage(const Message& cmd)
MsgConnectFifo& msg = (MsgConnectFifo&) cmd; MsgConnectFifo& msg = (MsgConnectFifo&) cmd;
m_dataFifo = msg.getFifo(); m_dataFifo = msg.getFifo();
bool doConnect = msg.getConnect(); bool doConnect = msg.getConnect();
qDebug("DemodAnalyzerWorker::handleMessage: MsgConnectFifo: %s", (doConnect ? "connect" : "disconnect")); qDebug("MorseDecoderWorker::handleMessage: MsgConnectFifo: %s", (doConnect ? "connect" : "disconnect"));
if (doConnect) { if (doConnect) {
QObject::connect( QObject::connect(
@ -153,6 +251,25 @@ void MorseDecoderWorker::applySettings(const MorseDecoderSettings& settings, con
{ {
qDebug() << "MorseDecoderWorker::applySettings:" << settings.getDebugString(settingsKeys, force) << force; qDebug() << "MorseDecoderWorker::applySettings:" << settings.getDebugString(settingsKeys, force) << force;
if (settingsKeys.contains("auto") || force)
{
auto parametersDecode = m_ggMorse->getDefaultParametersDecode();
parametersDecode.applyFilterHighPass = false;
if (settings.m_auto)
{
parametersDecode.frequency_hz = -1.0f; // auto
parametersDecode.speed_wpm = -1.0f; // auto
}
else
{
parametersDecode.frequency_hz = m_pitchHz;
parametersDecode.speed_wpm = m_speedWPM;
}
m_ggMorse->setParametersDecode(parametersDecode);
}
if (force) { if (force) {
m_settings = settings; m_settings = settings;
} else { } else {
@ -163,7 +280,16 @@ void MorseDecoderWorker::applySettings(const MorseDecoderSettings& settings, con
void MorseDecoderWorker::applySampleRate(int sampleRate) void MorseDecoderWorker::applySampleRate(int sampleRate)
{ {
QMutexLocker mutexLocker(&m_mutex);
m_sinkSampleRate = sampleRate; m_sinkSampleRate = sampleRate;
m_ggMorseParameters->sampleRateInp = sampleRate;
int ggMorseBlockSize = (sampleRate / GGMorse::kBaseSampleRate)*GGMorse::kDefaultSamplesPerFrame;
// m_bytesBufferSize = (GGMorse::kBaseSampleRate/GGMorse::kDefaultSamplesPerFrame)*ggMorseBlockSize*10; // ~10s
m_bytesBufferSize = sampleRate*10 + ggMorseBlockSize;
m_bytesBuffer.resize(m_bytesBufferSize);
m_bytesBufferCount = 0;
qDebug("MorseDecoderWorker::applySampleRate: m_sinkSampleRate: %d ggMorseBlockSize: %d m_bytesBufferSize: %d",
m_sinkSampleRate, ggMorseBlockSize, m_bytesBufferSize);
} }
void MorseDecoderWorker::handleData() void MorseDecoderWorker::handleData()

View File

@ -20,6 +20,8 @@
#include <vector> #include <vector>
#include "ggmorse/ggmorse.h"
#include <QObject> #include <QObject>
#include <QRecursiveMutex> #include <QRecursiveMutex>
#include <QByteArray> #include <QByteArray>
@ -31,7 +33,6 @@
#include "morsedecodersettings.h" #include "morsedecodersettings.h"
class MorseDecoderWorker : public QObject { class MorseDecoderWorker : public QObject {
Q_OBJECT Q_OBJECT
public: public:
@ -100,10 +101,16 @@ private:
MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object
MorseDecoderSettings m_settings; MorseDecoderSettings m_settings;
double m_magsq; double m_magsq;
SampleVector m_sampleBuffer; QByteArray m_bytesBuffer;
int m_sampleBufferSize; int m_bytesBufferSize;
int m_nbBytes; int m_bytesBufferCount;
QByteArray m_convBuffer;
QRecursiveMutex m_mutex; QRecursiveMutex m_mutex;
GGMorse::Parameters *m_ggMorseParameters;
GGMorse *m_ggMorse;
bool m_auto;
float m_pitchHz;
float m_speedWPM;
void feedPart( void feedPart(
const QByteArray::const_iterator& begin, const QByteArray::const_iterator& begin,
@ -112,6 +119,31 @@ private:
); );
bool handleMessage(const Message& cmd); bool handleMessage(const Message& cmd);
int processBuffer(QByteArray& bytesBuffer); //!< return the number of bytes left
// inline void processSample(
// DataFifo::DataType dataType,
// const QByteArray::const_iterator& begin,
// int nbBytesPerSample,
// int i
// )
// {
// int16_t *s = (int16_t*) begin + (i*nbBytesPerSample);
// switch(dataType)
// {
// case DataFifo::DataTypeI16: {
// m_sampleBuffer[i] = *s;
// }
// break;
// case DataFifo::DataTypeCI16: {
// int32_t re = s[2*i];
// int32_t im = s[2*i+1];
// m_sampleBuffer[i] = (int16_t) ((re+im) / 2);
// }
// break;
// }
// }
private slots: private slots:
void handleInputMessages(); void handleInputMessages();