diff --git a/plugins/channelrx/demodnfm/CMakeLists.txt b/plugins/channelrx/demodnfm/CMakeLists.txt
index 450fbe549..ad48b8d3a 100644
--- a/plugins/channelrx/demodnfm/CMakeLists.txt
+++ b/plugins/channelrx/demodnfm/CMakeLists.txt
@@ -1,6 +1,7 @@
project(nfm)
set(nfm_SOURCES
+ dcsdetector.cpp
nfmdemod.cpp
nfmdemodsettings.cpp
nfmdemodwebapiadapter.cpp
@@ -11,6 +12,7 @@ set(nfm_SOURCES
)
set(nfm_HEADERS
+ dcsdetector.h
nfmdemod.h
nfmdemodsettings.h
nfmdemodwebapiadapter.h
diff --git a/plugins/channelrx/demodnfm/dcsdetector.cpp b/plugins/channelrx/demodnfm/dcsdetector.cpp
new file mode 100644
index 000000000..1f6149fcb
--- /dev/null
+++ b/plugins/channelrx/demodnfm/dcsdetector.cpp
@@ -0,0 +1,124 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+
+#include "dcsdetector.h"
+
+DCSDetector::DCSDetector() :
+ m_bitIndex(0.0),
+ m_sampleRate(48000),
+ m_eqSamples(nullptr),
+ m_high(0.0f),
+ m_low(0.0f),
+ m_mid(0.0f),
+ m_prevSample(0.0f),
+ m_dcsWord(0),
+ m_mutex(QMutex::Recursive)
+{
+ setBitrate(134.3);
+ setEqWindow(23);
+}
+
+bool DCSDetector::analyze(Real *sample, unsigned int& dcsCode)
+{
+ QMutexLocker mlock(&m_mutex);
+ bool codeAvailable = false;
+
+ if (!m_eqSamples) {
+ return false;
+ }
+ // Equalizer
+ m_eqSamples[m_eqIndex++] = *sample;
+
+ if (m_eqIndex == m_eqSize)
+ {
+ m_high = *std::max_element(m_eqSamples, m_eqSamples + m_eqSize);
+ m_low = *std::min_element(m_eqSamples, m_eqSamples + m_eqSize);
+ m_mid = (m_high + m_low) / 2.0f;
+ // qDebug("DCSDetector::analyze: %f %f %f", m_low, m_mid, m_high);
+ m_eqIndex = 0;
+ }
+
+ // Edge detection
+ if (((m_prevSample < m_mid) && (*sample >= m_mid)) || ((m_prevSample > m_mid) && (*sample <= m_mid))) {
+ m_bitIndex = 0.0;
+ }
+
+ // Symbol detection
+ m_prevSample = *sample;
+ float fprev = m_bitIndex;
+ m_bitIndex += m_bitsPerSample;
+
+ if ((fprev < 0.5f) && (m_bitIndex >= 0.5f)) // mid point detection
+ {
+ unsigned int bit = *sample > m_mid ? 1 : 0; // always work in positive mode
+ m_dcsWord = (bit << 23) + (m_dcsWord >> 1);
+
+ if (((m_dcsWord >> 9) & 0x07) == 4) // magic signature
+ {
+ codeAvailable = m_golay2312.decodeParityFirst(&m_dcsWord);
+
+ if (codeAvailable) {
+ dcsCode = m_dcsWord & 0x1FF;
+ }
+ }
+ }
+
+ if (m_bitIndex > 1.0f) {
+ m_bitIndex -= 1.0f;
+ }
+
+ return codeAvailable;
+}
+
+void DCSDetector::setBitrate(float bitrate)
+{
+ m_bitrate = bitrate;
+ m_bitsPerSample = m_bitrate / m_sampleRate;
+ m_samplesPerBit = m_sampleRate / m_bitrate;
+}
+
+void DCSDetector::setSampleRate(int sampleRate)
+{
+ m_sampleRate = sampleRate;
+ m_bitsPerSample = m_bitrate / m_sampleRate;
+ m_samplesPerBit = m_sampleRate / m_bitrate;
+}
+
+void DCSDetector::setEqWindow(int nbBits)
+{
+ QMutexLocker mlock(&m_mutex);
+
+ m_eqBits = nbBits;
+ m_eqSize = (int) m_samplesPerBit * m_eqBits;
+
+ if (m_eqSamples) {
+ delete[] m_eqSamples;
+ }
+
+ m_eqSamples = new float[m_eqSize];
+ m_eqIndex = 0;
+}
+
+DCSDetector::~DCSDetector()
+{
+ if (m_eqSamples) {
+ delete[] m_eqSamples;
+ }
+}
diff --git a/plugins/channelrx/demodnfm/dcsdetector.h b/plugins/channelrx/demodnfm/dcsdetector.h
new file mode 100644
index 000000000..f58179b88
--- /dev/null
+++ b/plugins/channelrx/demodnfm/dcsdetector.h
@@ -0,0 +1,55 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_DSP_DCSDETECTOR_H_
+#define INCLUDE_DSP_DCSDETECTOR_H_
+
+#include
+
+#include "dsp/dsptypes.h"
+#include "util/golay2312.h"
+
+class DCSDetector {
+public:
+ DCSDetector();
+ ~DCSDetector();
+
+ bool analyze(Real *sample, unsigned int& dcsCode); //!< input signal sample
+ void setBitrate(float bitrate);
+ void setSampleRate(int sampleRate);
+ void setEqWindow(int nbBits);
+
+private:
+ float m_bitsPerSample;
+ float m_samplesPerBit;
+ float m_bitIndex;
+ float m_bitrate;
+ float m_sampleRate;
+ float *m_eqSamples;
+ int m_eqBits;
+ int m_eqSize;
+ int m_eqIndex;
+ float m_high;
+ float m_low;
+ float m_mid;
+ float m_prevSample;
+ unsigned int m_dcsWord; //!< 23 bit DCS code word
+ Golay2312 m_golay2312;
+ QMutex m_mutex;
+};
+
+#endif // INCLUDE_DSP_DCSDETECTOR_H_
diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp
index 8765de855..a920e9887 100644
--- a/plugins/channelrx/demodnfm/nfmdemod.cpp
+++ b/plugins/channelrx/demodnfm/nfmdemod.cpp
@@ -151,6 +151,9 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
<< " m_squelch: " << settings.m_squelch
<< " m_ctcssIndex: " << settings.m_ctcssIndex
<< " m_ctcssOn: " << settings.m_ctcssOn
+ << " m_dcsOn: " << settings.m_dcsOn
+ << " m_dcsCode: " << oct << settings.m_dcsCode << dec
+ << " m_dcsPositive: " << settings.m_dcsPositive
<< " m_highPass: " << settings.m_highPass
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp
index 0d3ad55f5..cfbc49e87 100644
--- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp
+++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp
@@ -12,6 +12,7 @@
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "dsp/dspengine.h"
+#include "dsp/dcscodes.h"
#include "maincore.h"
#include "nfmdemodreport.h"
@@ -61,6 +62,12 @@ bool NFMDemodGUI::handleMessage(const Message& message)
setCtcssFreq(report.getFrequency());
return true;
}
+ else if (NFMDemodReport::MsgReportDCSCode::match(message))
+ {
+ NFMDemodReport::MsgReportDCSCode& report = (NFMDemodReport::MsgReportDCSCode&) message;
+ setDcsCode(report.getCode());
+ return true;
+ }
else if (NFMDemod::MsgConfigureNFMDemod::match(message))
{
qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgConfigureNFMDemod");
@@ -214,6 +221,42 @@ void NFMDemodGUI::on_ctcssOn_toggled(bool checked)
applySettings();
}
+void NFMDemodGUI::on_dcsOn_toggled(bool checked)
+{
+ m_settings.m_dcsOn = checked;
+ applySettings();
+}
+
+void NFMDemodGUI::on_dcsPositive_toggled(bool checked)
+{
+ m_dcsShowPositive = checked;
+ setDcsCode(m_reportedDcsCode);
+}
+
+void NFMDemodGUI::on_dcsCode_currentIndexChanged(int index)
+{
+ if (index == 0)
+ {
+ m_settings.m_dcsCode = 0;
+ applySettings();
+ }
+ else
+ {
+ QString dcsText = ui->dcsCode->currentText();
+ bool positive = (dcsText[3] == 'P');
+ dcsText.chop(1);
+ bool ok;
+ int dcsCode = dcsText.toInt(&ok, 8);
+
+ if (ok)
+ {
+ m_settings.m_dcsCode = dcsCode;
+ m_settings.m_dcsPositive = positive;
+ applySettings();
+ }
+ }
+}
+
void NFMDemodGUI::on_highPassFilter_toggled(bool checked)
{
m_settings.m_highPass = checked;
@@ -297,6 +340,7 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_doApplySettings(true),
m_squelchOpen(false),
m_audioSampleRate(-1),
+ m_reportedDcsCode(0),
m_tickCount(0)
{
ui->setupUi(this);
@@ -328,11 +372,22 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
ui->ctcss->addItem("--");
- for (int i=0; ictcss->addItem(QString("%1").arg(ctcss_tones[i]));
}
+ ui->dcsOn->setChecked(m_settings.m_dcsOn);
+ ui->dscPositive->setChecked(m_settings.m_dcsPositive);
+ QList dcsCodes;
+ DCSCodes::getCanonicalCodes(dcsCodes);
+ ui->dcsCode->addItem("--");
+
+ for (auto dcsCode : dcsCodes)
+ {
+ ui->dcsCode->addItem(QString("%1P").arg(dcsCode, 3, 8, QLatin1Char('0')));
+ ui->dcsCode->addItem(QString("%1N").arg(dcsCode, 3, 8, QLatin1Char('0')));
+ }
+
blockApplySettings(false);
ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); // squelch closed
@@ -440,6 +495,16 @@ void NFMDemodGUI::displaySettings()
ui->ctcss->setCurrentIndex(m_settings.m_ctcssIndex);
+ if (m_settings.m_dcsCode == 0) {
+ ui->dcsCode->setCurrentText(tr("--"));
+ } else {
+ ui->dcsCode->setCurrentText(tr("%1%2")
+ .arg(m_settings.m_dcsCode, 3, 8, QLatin1Char('0'))
+ .arg(m_settings.m_dcsPositive ? "P" : "N")
+ );
+ }
+
+ setDcsCode(m_reportedDcsCode);
displayStreamIndex();
blockApplySettings(false);
@@ -466,16 +531,28 @@ void NFMDemodGUI::enterEvent(QEvent*)
void NFMDemodGUI::setCtcssFreq(Real ctcssFreq)
{
- if (ctcssFreq == 0)
- {
+ if (ctcssFreq == 0) {
ui->ctcssText->setText("--");
- }
- else
- {
+ } else {
ui->ctcssText->setText(QString("%1").arg(ctcssFreq));
}
}
+void NFMDemodGUI::setDcsCode(unsigned int dcsCode)
+{
+ if (dcsCode == 0)
+ {
+ ui->dcsText->setText("--");
+ }
+ else
+ {
+ unsigned int normalizedCode;
+ normalizedCode = DCSCodes::m_toCanonicalCode[dcsCode];
+ normalizedCode = m_dcsShowPositive ? normalizedCode : DCSCodes::m_signFlip[normalizedCode];
+ ui->dcsText->setText(tr("%1").arg(normalizedCode, 3, 8, QLatin1Char('0')));
+ }
+}
+
void NFMDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.h b/plugins/channelrx/demodnfm/nfmdemodgui.h
index 8b86015de..7828d0936 100644
--- a/plugins/channelrx/demodnfm/nfmdemodgui.h
+++ b/plugins/channelrx/demodnfm/nfmdemodgui.h
@@ -29,7 +29,6 @@ public:
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
- void setCtcssFreq(Real ctcssFreq);
public slots:
void channelMarkerChangedByCursor();
@@ -47,6 +46,8 @@ private:
NFMDemod* m_nfmDemod;
bool m_squelchOpen;
int m_audioSampleRate;
+ bool m_reportedDcsCode;
+ bool m_dcsShowPositive;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
@@ -57,6 +58,8 @@ private:
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
+ void setCtcssFreq(Real ctcssFreq);
+ void setDcsCode(unsigned int dcsCode);
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
@@ -74,6 +77,9 @@ private slots:
void on_squelch_valueChanged(int value);
void on_ctcss_currentIndexChanged(int index);
void on_ctcssOn_toggled(bool checked);
+ void on_dcsOn_toggled(bool checked);
+ void on_dcsPositive_toggled(bool checked);
+ void on_dcsCode_currentIndexChanged(int index);
void on_highPassFilter_toggled(bool checked);
void on_audioMute_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown);
diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.ui b/plugins/channelrx/demodnfm/nfmdemodgui.ui
index 768de5d89..54765df15 100644
--- a/plugins/channelrx/demodnfm/nfmdemodgui.ui
+++ b/plugins/channelrx/demodnfm/nfmdemodgui.ui
@@ -6,8 +6,8 @@
0
0
- 302
- 182
+ 364
+ 200
@@ -18,8 +18,8 @@
- 302
- 0
+ 364
+ 200
@@ -36,13 +36,13 @@
0
0
- 300
- 141
+ 362
+ 191
- 300
+ 362
0
@@ -537,41 +537,39 @@
-
-
-
+
+
+ Activate CTCSS
+
+
+ Qt::RightToLeft
+
CTCSS
-
-
-
-
-
-
- Activate CTCSS
-
-
-
-
-
-
- -
-
-
-
- 16777215
- 16777215
-
-
-
- Set CTCSS Frequency
-
-
-
-
+
+
+
+ 60
+ 16777215
+
+
+
+ Set CTCSS Frequency
+
+
-
+
+
+ 30
+ 0
+
+
CTCSS detected
@@ -583,6 +581,61 @@
+ -
+
+
+ Activate DCS
+
+
+ Qt::RightToLeft
+
+
+ DCS
+
+
+
+ -
+
+
+
+ 60
+ 16777215
+
+
+
+ Set DCS code
+
+
+
+ -
+
+
+ Display DCS code as postive
+
+
+ +
+
+
+
+ -
+
+
+
+ 30
+ 0
+
+
+
+ DCS detected
+
+
+ --
+
+
+ Qt::AlignCenter
+
+
+
-
@@ -596,6 +649,29 @@
+
+
+ -
+
+
+ 6
+
+
+ 6
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
-
@@ -697,12 +773,6 @@
1
-
- LevelMeterSignalDB
- QWidget
-
- 1
-
ButtonSwitch
QToolButton
@@ -714,6 +784,12 @@
1
+
+ LevelMeterSignalDB
+ QWidget
+
+ 1
+
diff --git a/plugins/channelrx/demodnfm/nfmdemodreport.cpp b/plugins/channelrx/demodnfm/nfmdemodreport.cpp
index b72c40730..650c642b3 100644
--- a/plugins/channelrx/demodnfm/nfmdemodreport.cpp
+++ b/plugins/channelrx/demodnfm/nfmdemodreport.cpp
@@ -18,9 +18,10 @@
#include "nfmdemodreport.h"
MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message)
+MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportDCSCode, Message)
NFMDemodReport::NFMDemodReport()
{ }
NFMDemodReport::~NFMDemodReport()
-{ }
\ No newline at end of file
+{ }
diff --git a/plugins/channelrx/demodnfm/nfmdemodreport.h b/plugins/channelrx/demodnfm/nfmdemodreport.h
index 8b5c49c9c..c6ab11d3b 100644
--- a/plugins/channelrx/demodnfm/nfmdemodreport.h
+++ b/plugins/channelrx/demodnfm/nfmdemodreport.h
@@ -47,6 +47,26 @@ public:
{ }
};
+ class MsgReportDCSCode : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ unsigned int getCode() const { return m_code; }
+
+ static MsgReportDCSCode* create(unsigned int code)
+ {
+ return new MsgReportDCSCode(code);
+ }
+
+ private:
+ unsigned int m_code;
+
+ MsgReportDCSCode(unsigned int code) :
+ Message(),
+ m_code(code)
+ { }
+ };
+
public:
NFMDemodReport();
~NFMDemodReport();
diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp
index 6fd1ac579..704da0771 100644
--- a/plugins/channelrx/demodnfm/nfmdemodsettings.cpp
+++ b/plugins/channelrx/demodnfm/nfmdemodsettings.cpp
@@ -60,6 +60,9 @@ void NFMDemodSettings::resetToDefaults()
m_ctcssOn = false;
m_audioMute = false;
m_ctcssIndex = 0;
+ m_dcsOn = false;
+ m_dcsCode = 0023;
+ m_dcsPositive = false;
m_rgbColor = QColor(255, 0, 0).rgb();
m_title = "NFM Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
@@ -101,6 +104,9 @@ QByteArray NFMDemodSettings::serialize() const
s.writeU32(20, m_reverseAPIChannelIndex);
s.writeS32(21, m_streamIndex);
s.writeReal(22, m_fmDeviation);
+ s.writeBool(23, m_dcsOn);
+ s.writeU32(24, m_dcsCode);
+ s.writeBool(25, m_dcsPositive);
return s.final();
}
@@ -160,6 +166,10 @@ bool NFMDemodSettings::deserialize(const QByteArray& data)
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readS32(21, &m_streamIndex, 0);
d.readReal(22, &m_fmDeviation, 5000.0);
+ d.readBool(23, &m_dcsOn, false);
+ d.readU32(24, &utmp, 0023);
+ m_dcsCode = utmp < 511 ? utmp : 511;
+ d.readBool(26, &m_dcsPositive, false);
return true;
}
diff --git a/plugins/channelrx/demodnfm/nfmdemodsettings.h b/plugins/channelrx/demodnfm/nfmdemodsettings.h
index 8ae968391..4517ad423 100644
--- a/plugins/channelrx/demodnfm/nfmdemodsettings.h
+++ b/plugins/channelrx/demodnfm/nfmdemodsettings.h
@@ -41,6 +41,9 @@ struct NFMDemodSettings
bool m_ctcssOn;
bool m_audioMute;
int m_ctcssIndex;
+ bool m_dcsOn;
+ unsigned int m_dcsCode;
+ bool m_dcsPositive;
quint32 m_rgbColor;
QString m_title;
QString m_audioDeviceName;
diff --git a/plugins/channelrx/demodnfm/nfmdemodsink.cpp b/plugins/channelrx/demodnfm/nfmdemodsink.cpp
index 68a63e45a..d6cb8b443 100644
--- a/plugins/channelrx/demodnfm/nfmdemodsink.cpp
+++ b/plugins/channelrx/demodnfm/nfmdemodsink.cpp
@@ -30,6 +30,7 @@
#include "dsp/devicesamplemimo.h"
#include "dsp/misc.h"
#include "dsp/datafifo.h"
+#include "dsp/dcscodes.h"
#include "device/deviceapi.h"
#include "maincore.h"
@@ -49,6 +50,7 @@ NFMDemodSink::NFMDemodSink() :
m_audioFifo(48000),
m_rfFilter(FFT_FILTER_LENGTH),
m_ctcssIndex(0),
+ m_dcsCode(0),
m_sampleCount(0),
m_squelchCount(0),
m_squelchGate(4800),
@@ -68,6 +70,9 @@ NFMDemodSink::NFMDemodSink() :
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
+ m_dcsDetector.setSampleRate(CTCSS_DETECTOR_RATE);
+ m_dcsDetector.setEqWindow(23);
+
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
@@ -171,14 +176,18 @@ void NFMDemodSink::processOneSample(Complex &ci)
m_squelchOpen = m_squelchCount > m_squelchGate;
int ctcssIndex = m_squelchOpen && m_settings.m_ctcssOn ? m_ctcssIndex : 0;
+ unsigned int dcsCode = m_squelchOpen && m_settings.m_dcsOn ? m_dcsCode : 0;
+
if (m_squelchOpen)
{
if (m_settings.m_ctcssOn)
{
int factor = (m_audioSampleRate / CTCSS_DETECTOR_RATE) - 1; // decimate -> 6k
+
if ((m_sampleCount & factor) == factor)
{
Real ctcssSample = m_ctcssLowpass.filter(demod);
+
if (m_ctcssDetector.analyze(&ctcssSample))
{
int maxToneIndex;
@@ -186,8 +195,29 @@ void NFMDemodSink::processOneSample(Complex &ci)
}
}
}
+ else if (m_settings.m_dcsOn)
+ {
+ int factor = (m_audioSampleRate / CTCSS_DETECTOR_RATE) - 1; // decimate -> 6k (same decimation as for CTCSS)
- if (!m_settings.m_audioMute && (!m_settings.m_ctcssOn || m_ctcssIndexSelected == ctcssIndex || m_ctcssIndexSelected == 0))
+ if ((m_sampleCount & factor) == factor)
+ {
+ Real dcsSample = m_ctcssLowpass.filter(demod);
+ unsigned int dcsCodeDetected;
+
+ if (m_dcsDetector.analyze(&dcsSample, dcsCodeDetected))
+ {
+ dcsCode = DCSCodes::m_toCanonicalCode.value(dcsCodeDetected, 0);
+
+ if (dcsCode != 0) {
+ dcsCode = m_settings.m_dcsPositive ? dcsCode : DCSCodes::m_signFlip[dcsCode];
+ }
+ }
+ }
+ }
+
+ if (!m_settings.m_audioMute &&
+ (!m_settings.m_ctcssOn || m_ctcssIndexSelected == ctcssIndex || m_ctcssIndexSelected == 0) &&
+ (!m_settings.m_dcsOn || m_dcsCodeSeleted == dcsCode || m_dcsCodeSeleted == 0))
{
Real audioSample = m_squelchDelayLine.readBack(m_squelchGate);
audioSample = m_settings.m_highPass ? m_bandpass.filter(audioSample) : m_lowpass.filter(audioSample);
@@ -200,14 +230,27 @@ void NFMDemodSink::processOneSample(Complex &ci)
if (ctcssIndex != m_ctcssIndex)
{
auto *guiQueue = getMessageQueueToGUI();
+
if (guiQueue)
{
guiQueue->push(NFMDemodReport::MsgReportCTCSSFreq::create(
ctcssIndex ? m_ctcssDetector.getToneSet()[ctcssIndex - 1] : 0));
}
+
m_ctcssIndex = ctcssIndex;
}
+ if (dcsCode != m_dcsCode)
+ {
+ auto *guiQueue = getMessageQueueToGUI();
+
+ if (guiQueue) {
+ guiQueue->push(NFMDemodReport::MsgReportDCSCode::create(dcsCode));
+ }
+
+ m_dcsCode = dcsCode;
+ }
+
m_audioBuffer[m_audioBufferFill].l = sample;
m_audioBuffer[m_audioBufferFill].r = sample;
++m_audioBufferFill;
@@ -286,6 +329,9 @@ void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
<< " m_squelch: " << settings.m_squelch
<< " m_ctcssIndex: " << settings.m_ctcssIndex
<< " m_ctcssOn: " << settings.m_ctcssOn
+ << " m_dcsOn: " << settings.m_dcsOn
+ << " m_dcsCode: " << settings.m_dcsCode
+ << " m_dcsPositive: " << settings.m_dcsPositive
<< " m_highPass: " << settings.m_highPass
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
@@ -339,6 +385,10 @@ void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
setSelectedCtcssIndex(settings.m_ctcssIndex);
}
+ if ((settings.m_dcsCode != m_settings.m_dcsCode) || force) {
+ setSelectedDcsCode(settings.m_dcsCode);
+ }
+
m_settings = settings;
}
@@ -359,8 +409,8 @@ void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
} else {
m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay
}
- m_afSquelch.setThreshold(m_squelchLevel);
+ m_afSquelch.setThreshold(m_squelchLevel);
m_phaseDiscri.setFMScaling(Real(sampleRate) / (2.0f * m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/2);
diff --git a/plugins/channelrx/demodnfm/nfmdemodsink.h b/plugins/channelrx/demodnfm/nfmdemodsink.h
index 432fb10f9..b9bb8ff4f 100644
--- a/plugins/channelrx/demodnfm/nfmdemodsink.h
+++ b/plugins/channelrx/demodnfm/nfmdemodsink.h
@@ -33,6 +33,7 @@
#include "util/doublebufferfifo.h"
#include "audio/audiofifo.h"
+#include "dcsdetector.h"
#include "nfmdemodsettings.h"
class ChannelAPI;
@@ -52,6 +53,10 @@ public:
m_ctcssIndexSelected = selectedCtcssIndex;
}
+ void setSelectedDcsCode(unsigned int selectedDcsCode) {
+ m_dcsCodeSeleted = selectedDcsCode;
+ }
+
bool getSquelchOpen() const { return m_squelchOpen; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
@@ -120,6 +125,9 @@ private:
CTCSSDetector m_ctcssDetector;
int m_ctcssIndex; // 0 for nothing detected
int m_ctcssIndexSelected;
+ DCSDetector m_dcsDetector;
+ unsigned int m_dcsCode;
+ unsigned int m_dcsCodeSeleted;
int m_sampleCount;
int m_squelchCount;
int m_squelchGate;
diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt
index 00a3e9074..446c89c44 100644
--- a/sdrbase/CMakeLists.txt
+++ b/sdrbase/CMakeLists.txt
@@ -101,6 +101,7 @@ set(sdrbase_SOURCES
dsp/cwkeyer.cpp
dsp/cwkeyersettings.cpp
dsp/datafifo.cpp
+ dsp/dcscodes.cpp
dsp/decimatorsff.cpp
dsp/decimatorsfi.cpp
dsp/decimatorc.cpp
@@ -186,6 +187,7 @@ set(sdrbase_SOURCES
util/db.cpp
util/fixedtraits.cpp
util/fits.cpp
+ util/golay2312.cpp
util/httpdownloadmanager.cpp
util/lfsr.cpp
util/maidenhead.cpp
@@ -262,6 +264,7 @@ set(sdrbase_HEADERS
dsp/cwkeyer.h
dsp/cwkeyersettings.h
dsp/datafifo.h
+ dsp/dcscodes.h
dsp/decimators.h
dsp/decimatorsif.h
dsp/decimatorsff.h
@@ -382,6 +385,7 @@ set(sdrbase_HEADERS
util/doublebufferfifo.h
util/fixedtraits.h
util/fits.h
+ util/golay2312.h
util/httpdownloadmanager.h
util/incrementalarray.h
util/incrementalvector.h
diff --git a/sdrbase/dsp/dcscodes.cpp b/sdrbase/dsp/dcscodes.cpp
new file mode 100644
index 000000000..0e2a0ea71
--- /dev/null
+++ b/sdrbase/dsp/dcscodes.cpp
@@ -0,0 +1,465 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+// //
+// Source: http://onfreq.com/syntorx/dcs.html //
+// //
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "dcscodes.h"
+
+const QMap DCSCodes::m_toCanonicalCode {
+ {0023, 0023},
+ {0340, 0023},
+ {0766, 0023},
+ {0025, 0025},
+ {0026, 0026},
+ {0566, 0026},
+ {0031, 0031},
+ {0374, 0031},
+ {0643, 0031},
+ {0032, 0032},
+ {0036, 0036},
+ {0137, 0036},
+ {0043, 0043},
+ {0355, 0043},
+ {0047, 0047},
+ {0375, 0047},
+ {0707, 0047},
+ {0051, 0051},
+ {0771, 0051},
+ {0520, 0051},
+ {0053, 0053},
+ {0054, 0054},
+ {0405, 0054},
+ {0675, 0054},
+ {0065, 0065},
+ {0301, 0065},
+ {0071, 0071},
+ {0603, 0071},
+ {0717, 0071},
+ {0746, 0071},
+ {0072, 0072},
+ {0470, 0072},
+ {0701, 0072},
+ {0073, 0073},
+ {0640, 0073},
+ {0074, 0074},
+ {0360, 0074},
+ {0721, 0074},
+ {0112, 0112},
+ {0250, 0112},
+ {0505, 0112},
+ {0512, 0112},
+ {0114, 0114},
+ {0327, 0114},
+ {0615, 0114},
+ {0115, 0115},
+ {0534, 0115},
+ {0674, 0115},
+ {0116, 0116},
+ {0060, 0116},
+ {0737, 0116},
+ {0122, 0122},
+ {0535, 0125},
+ {0125, 0125},
+ {0173, 0125},
+ {0131, 0131},
+ {0572, 0131},
+ {0702, 0131},
+ {0132, 0132},
+ {0605, 0132},
+ {0634, 0132},
+ {0714, 0132},
+ {0134, 0134},
+ {0273, 0134},
+ {0143, 0143},
+ {0333, 0143},
+ {0145, 0145},
+ {0525, 0145},
+ {0152, 0152},
+ {0366, 0152},
+ {0415, 0152},
+ {0155, 0155},
+ {0233, 0155},
+ {0660, 0155},
+ {0156, 0156},
+ {0517, 0156},
+ {0741, 0156},
+ {0162, 0162},
+ {0416, 0162},
+ {0553, 0162},
+ {0165, 0165},
+ {0354, 0165},
+ {0172, 0172},
+ {0057, 0172},
+ {0174, 0174},
+ {0142, 0174},
+ {0270, 0174},
+ {0205, 0205},
+ {0135, 0205},
+ {0610, 0205},
+ {0212, 0212},
+ {0253, 0212},
+ {0223, 0223},
+ {0350, 0223},
+ {0475, 0223},
+ {0750, 0223},
+ {0225, 0225},
+ {0536, 0225},
+ {0226, 0226},
+ {0104, 0226},
+ {0557, 0226},
+ {0243, 0243},
+ {0267, 0243},
+ {0342, 0243},
+ {0244, 0244},
+ {0176, 0244},
+ {0417, 0244},
+ {0245, 0245},
+ {0370, 0245},
+ {0246, 0246},
+ {0542, 0246},
+ {0653, 0246},
+ {0554, 0245},
+ {0251, 0251},
+ {0236, 0251},
+ {0704, 0251},
+ {0742, 0251},
+ {0252, 0252},
+ {0661, 0252},
+ {0255, 0255},
+ {0425, 0255},
+ {0261, 0261},
+ {0227, 0261},
+ {0567, 0261},
+ {0263, 0263},
+ {0213, 0263},
+ {0736, 0263},
+ {0265, 0265},
+ {0171, 0265},
+ {0426, 0265},
+ {0266, 0266},
+ {0655, 0266},
+ {0271, 0271},
+ {0427, 0271},
+ {0510, 0271},
+ {0762, 0271},
+ {0274, 0274},
+ {0652, 0274},
+ {0306, 0306},
+ {0147, 0306},
+ {0303, 0306},
+ {0761, 0306},
+ {0311, 0311},
+ {0330, 0311},
+ {0456, 0311},
+ {0561, 0311},
+ {0315, 0315},
+ {0321, 0315},
+ {0673, 0315},
+ {0325, 0325},
+ {0550, 0325},
+ {0626, 0325},
+ {0331, 0331},
+ {0372, 0331},
+ {0507, 0331},
+ {0332, 0332},
+ {0433, 0332},
+ {0552, 0332},
+ {0343, 0343},
+ {0324, 0343},
+ {0570, 0343},
+ {0346, 0346},
+ {0616, 0346},
+ {0635, 0346},
+ {0724, 0346},
+ {0351, 0351},
+ {0353, 0351},
+ {0435, 0351},
+ {0356, 0356},
+ {0521, 0356},
+ {0364, 0364},
+ {0130, 0364},
+ {0641, 0364},
+ {0365, 0365},
+ {0107, 0365},
+ {0371, 0371},
+ {0217, 0371},
+ {0453, 0371},
+ {0530, 0371},
+ {0411, 0411},
+ {0117, 0411},
+ {0756, 0411},
+ {0412, 0412},
+ {0127, 0412},
+ {0441, 0412},
+ {0711, 0412},
+ {0413, 0413},
+ {0133, 0413},
+ {0620, 0413},
+ {0423, 0423},
+ {0234, 0423},
+ {0563, 0423},
+ {0621, 0423},
+ {0713, 0423},
+ {0431, 0431},
+ {0262, 0431},
+ {0316, 0431},
+ {0730, 0431},
+ {0432, 0432},
+ {0432, 0432},
+ {0276, 0432},
+ {0326, 0432},
+ {0445, 0445},
+ {0222, 0445},
+ {0457, 0445},
+ {0575, 0445},
+ {0446, 0446},
+ {0467, 0446},
+ {0511, 0446},
+ {0672, 0446},
+ {0452, 0452},
+ {0524, 0452},
+ {0765, 0452},
+ {0454, 0454},
+ {0545, 0454},
+ {0513, 0454},
+ {0564, 0454},
+ {0455, 0455},
+ {0533, 0455},
+ {0551, 0455},
+ {0462, 0462},
+ {0462, 0462},
+ {0472, 0462},
+ {0623, 0462},
+ {0725, 0462},
+ {0464, 0464},
+ {0237, 0464},
+ {0642, 0464},
+ {0772, 0464},
+ {0465, 0465},
+ {0056, 0465},
+ {0656, 0465},
+ {0466, 0466},
+ {0144, 0466},
+ {0666, 0466},
+ {0503, 0503},
+ {0157, 0503},
+ {0322, 0503},
+ {0506, 0506},
+ {0224, 0506},
+ {0313, 0506},
+ {0574, 0506},
+ {0516, 0516},
+ {0067, 0516},
+ {0720, 0516},
+ {0523, 0523},
+ {0647, 0523},
+ {0726, 0523},
+ {0526, 0526},
+ {0562, 0526},
+ {0645, 0526},
+ {0532, 0532},
+ {0161, 0532},
+ {0345, 0532},
+ {0546, 0546},
+ {0317, 0546},
+ {0614, 0546},
+ {0751, 0546},
+ {0565, 0565},
+ {0307, 0565},
+ {0362, 0565},
+ {0606, 0606},
+ {0153, 0606},
+ {0630, 0606},
+ {0612, 0612},
+ {0254, 0612},
+ {0314, 0612},
+ {0706, 0612},
+ {0624, 0624},
+ {0075, 0624},
+ {0501, 0624},
+ {0627, 0627},
+ {0037, 0627},
+ {0560, 0627},
+ {0631, 0631},
+ {0231, 0631},
+ {0504, 0631},
+ {0636, 0631},
+ {0745, 0631},
+ {0632, 0632},
+ {0123, 0632},
+ {0657, 0632},
+ {0654, 0654},
+ {0163, 0654},
+ {0460, 0654},
+ {0607, 0654},
+ {0662, 0662},
+ {0363, 0662},
+ {0436, 0662},
+ {0443, 0662},
+ {0444, 0662},
+ {0664, 0664},
+ {0344, 0664},
+ {0471, 0664},
+ {0715, 0664},
+ {0703, 0703},
+ {0150, 0703},
+ {0256, 0703},
+ {0712, 0712},
+ {0136, 0712},
+ {0502, 0712},
+ {0723, 0723},
+ {0235, 0723},
+ {0671, 0723},
+ {0611, 0723},
+ {0731, 0731},
+ {0447, 0731},
+ {0473, 0731},
+ {0474, 0731},
+ {0744, 0731},
+ {0732, 0732},
+ {0164, 0732},
+ {0207, 0732},
+ {0734, 0734},
+ {0066, 0734},
+ {0743, 0743},
+ {0312, 0743},
+ {0515, 0743},
+ {0663, 0743},
+ {0754, 0754},
+ {0076, 0754},
+ {0203, 0754},
+};
+
+const QMap DCSCodes::m_signFlip = {
+ {0023, 0047},
+ {0025, 0244},
+ {0026, 0464},
+ {0031, 0627},
+ {0032, 0051},
+ {0043, 0445},
+ {0047, 0023},
+ {0051, 0032},
+ {0053, 0452},
+ {0054, 0413},
+ {0065, 0271},
+ {0071, 0306},
+ {0072, 0245},
+ {0073, 0506},
+ {0074, 0174},
+ {0114, 0712},
+ {0115, 0152},
+ {0116, 0754},
+ {0122, 0225},
+ {0125, 0365},
+ {0131, 0364},
+ {0132, 0546},
+ {0134, 0223},
+ {0143, 0412},
+ {0145, 0274},
+ {0152, 0115},
+ {0155, 0731},
+ {0156, 0265},
+ {0162, 0503},
+ {0165, 0251},
+ {0172, 0036},
+ {0174, 0074},
+ {0205, 0263},
+ {0212, 0356},
+ {0223, 0134},
+ {0225, 0122},
+ {0226, 0411},
+ {0243, 0351},
+ {0244, 0025},
+ {0245, 0072},
+ {0246, 0523},
+ {0251, 0165},
+ {0252, 0462},
+ {0255, 0511},
+ {0261, 0732},
+ {0263, 0205},
+ {0265, 0156},
+ {0266, 0454},
+ {0271, 0065},
+ {0274, 0145},
+ {0306, 0071},
+ {0311, 0664},
+ {0315, 0423},
+ {0325, 0526},
+ {0331, 0465},
+ {0332, 0455},
+ {0343, 0532},
+ {0346, 0612},
+ {0351, 0243},
+ {0356, 0212},
+ {0364, 0131},
+ {0365, 0125},
+ {0371, 0734},
+ {0411, 0226},
+ {0412, 0143},
+ {0413, 0054},
+ {0423, 0315},
+ {0431, 0723},
+ {0432, 0516},
+ {0445, 0043},
+ {0446, 0255},
+ {0452, 0053},
+ {0454, 0655},
+ {0455, 0332},
+ {0462, 0252},
+ {0464, 0026},
+ {0465, 0331},
+ {0466, 0662},
+ {0503, 0162},
+ {0506, 0073},
+ {0516, 0432},
+ {0523, 0246},
+ {0526, 0325},
+ {0532, 0343},
+ {0546, 0132},
+ {0565, 0703},
+ {0606, 0631},
+ {0612, 0346},
+ {0624, 0632},
+ {0627, 0031},
+ {0631, 0606},
+ {0632, 0624},
+ {0654, 0743},
+ {0662, 0466},
+ {0664, 0311},
+ {0703, 0565},
+ {0712, 0114},
+ {0723, 0431},
+ {0731, 0155},
+ {0732, 0261},
+ {0734, 0371},
+ {0743, 0654},
+ {0754, 0116},
+};
+
+
+void DCSCodes::getCanonicalCodes(QList& codes)
+{
+ codes.clear();
+
+ for (auto code : m_toCanonicalCode.keys())
+ {
+ if (code == m_toCanonicalCode.value(code)) {
+ codes.append(code);
+ }
+ }
+}
diff --git a/sdrbase/dsp/dcscodes.h b/sdrbase/dsp/dcscodes.h
new file mode 100644
index 000000000..9f9a5e737
--- /dev/null
+++ b/sdrbase/dsp/dcscodes.h
@@ -0,0 +1,35 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_DSP_DCSCODES_H_
+#define INCLUDE_DSP_DCSCODES_H_
+
+#include
+#include
+
+#include "export.h"
+
+class SDRBASE_API DCSCodes
+{
+public:
+ static void getCanonicalCodes(QList& codes);
+ static const int m_nbCodes;
+ static const QMap m_toCanonicalCode;
+ static const QMap m_signFlip;
+};
+
+#endif // INCLUDE_DSP_DCSCODES_H_
diff --git a/sdrbase/util/golay2312.cpp b/sdrbase/util/golay2312.cpp
new file mode 100644
index 000000000..c053c306f
--- /dev/null
+++ b/sdrbase/util/golay2312.cpp
@@ -0,0 +1,253 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+
+#include "golay2312.h"
+
+const unsigned int Golay2312::m_B[11] = {
+ 0b101001001111,
+ 0b111101101000,
+ 0b011110110100,
+ 0b001111011010,
+ 0b000111101101,
+ 0b101010111001,
+ 0b111100010011,
+ 0b110111000110,
+ 0b011011100011,
+ 0b100100111110,
+ 0b010010011111,
+};
+
+const unsigned int Golay2312::m_I11[11] = {
+ 0b10000000000,
+ 0b01000000000,
+ 0b00100000000,
+ 0b00010000000,
+ 0b00001000000,
+ 0b00000100000,
+ 0b00000010000,
+ 0b00000001000,
+ 0b00000000100,
+ 0b00000000010,
+ 0b00000000001,
+};
+
+const unsigned int Golay2312::m_I12[12] = {
+ 0b100000000000,
+ 0b010000000000,
+ 0b001000000000,
+ 0b000100000000,
+ 0b000010000000,
+ 0b000001000000,
+ 0b000000100000,
+ 0b000000010000,
+ 0b000000001000,
+ 0b000000000100,
+ 0b000000000010,
+ 0b000000000001,
+};
+
+Golay2312::Golay2312()
+{
+ initG();
+ initH();
+ buildCorrMatrix(m_corrPL, m_HPL);
+ buildCorrMatrix(m_corrPF, m_HPF, true);
+}
+
+Golay2312::~Golay2312()
+{
+}
+
+void Golay2312::initG()
+{
+ for (int r = 0; r < 23; r++)
+ {
+ // parity last
+ if (r < 12) {
+ m_GPL[r] = m_I12[r];
+ } else {
+ m_GPL[r] = m_B[r-12];
+ }
+ // parity first
+ if (r < 11) {
+ m_GPF[r] = m_B[r];
+ } else {
+ m_GPF[r] = m_I12[r-11];
+ }
+ }
+}
+
+void Golay2312::initH()
+{
+ for (int r = 0; r < 11; r++)
+ {
+ m_HPL[r] = (m_B[r] << 11) + m_I11[r]; // parity last
+ m_HPF[r] = (m_I11[r] << 12) + m_B[r]; // parity first
+ }
+}
+
+void Golay2312::encodeParityLast(unsigned int msg, unsigned int *tx)
+{
+ *tx = 0;
+
+ for (int r = 0; r < 23; r++) {
+ *tx += (std::bitset<32>(m_GPL[r] & msg).count() % 2) << (22-r);
+ }
+}
+
+void Golay2312::encodeParityFirst(unsigned int msg, unsigned int *tx)
+{
+ *tx = 0;
+
+ for (int r = 0; r < 23; r++) {
+ *tx += (std::bitset<32>(m_GPF[r] & msg).count() % 2) << (22-r);
+ }
+}
+
+bool Golay2312::decodeParityLast(unsigned int *rx)
+{
+ unsigned int s = syn(m_HPL, *rx);
+ return lut(m_corrPL, s, rx);
+}
+
+bool Golay2312::decodeParityFirst(unsigned int *rx)
+{
+ unsigned int s = syn(m_HPF, *rx);
+ return lut(m_corrPF, s, rx);
+}
+
+unsigned int Golay2312::syn(unsigned int *H, unsigned int rx)
+{
+ unsigned int s = 0;
+
+ for (int r = 0; r < 11; r++) {
+ s += (std::bitset<32>(H[r] & rx).count() % 2) << (10-r);
+ }
+
+ return s;
+}
+
+bool Golay2312::lut(unsigned char *corr, unsigned int syndrome, unsigned int *rx)
+{
+ if (syndrome == 0) {
+ return true;
+ }
+
+ int i = 0;
+
+ for (; i < 3; i++)
+ {
+ if (corr[3*syndrome + i] == 0xFF) {
+ break;
+ } else {
+ *rx ^= (1 << corr[3*syndrome + i]); // flip bit
+ }
+ }
+
+ if (i == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+void Golay2312::buildCorrMatrix(unsigned char *corr, unsigned int *H, bool pf)
+{
+ int shiftP = pf ? 12 : 0; // shift in position value for parity bits
+ int shiftW = pf ? 0 : 11; // shift in position value for message word bits
+ std::fill(corr, corr + 3*2048, 0xFF);
+ int syndromeI;
+ unsigned int cw;
+
+ for (int i1 = 0; i1 < 12; i1++)
+ {
+ for (int i2 = i1+1; i2 < 12; i2++)
+ {
+ for (int i3 = i2+1; i3 < 12; i3++)
+ {
+ // 3 bit patterns (in message)
+ cw = (1 << (i1+shiftW)) + (1 << (i2+shiftW)) + (1 << (i3+shiftW));
+ syndromeI = syn(H, cw);
+ corr[3*syndromeI + 0] = i1 + shiftW;
+ corr[3*syndromeI + 1] = i2 + shiftW;
+ corr[3*syndromeI + 2] = i3 + shiftW;
+ }
+
+ // 2 bit patterns (in message)
+ cw = (1 << (i1+shiftW)) + (1 << (i2+shiftW));
+ syndromeI = syn(H, cw);
+ corr[3*syndromeI + 0] = i1 + shiftW;
+ corr[3*syndromeI + 1] = i2 + shiftW;
+
+ // 1 possible bit flip left in the parity part
+ for (int ip = 0; ip < 11; ip++)
+ {
+ int syndromeIP = syndromeI ^ (1 << (10-ip));
+ corr[3*syndromeIP + 0] = i1 + shiftW;
+ corr[3*syndromeIP + 1] = i2 + shiftW;
+ corr[3*syndromeIP + 2] = 10 - ip + shiftP;
+ }
+ }
+
+ // single bit patterns (in message)
+ cw = (1 << (i1+shiftW));
+ syndromeI = syn(H, cw);
+ corr[3*syndromeI + 0] = i1 + shiftW;
+
+ for (int ip1 = 0; ip1 < 11; ip1++) // 1 more bit flip in parity
+ {
+ int syndromeIP1 = syndromeI ^ (1 << (10-ip1));
+ corr[3*syndromeIP1 + 0] = i1 + shiftW;
+ corr[3*syndromeIP1 + 1] = 10 - ip1 + shiftP;
+
+ for (int ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity
+ {
+ int syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2));
+ corr[3*syndromeIP2 + 0] = i1 + shiftW;
+ corr[3*syndromeIP2 + 1] = 10 - ip1 + shiftP;
+ corr[3*syndromeIP2 + 2] = 10 - ip2 + shiftP;
+ }
+ }
+ }
+
+ // no bit patterns (in message) -> all in parity
+
+ for (int ip1 = 0; ip1 < 11; ip1++) // 1 bit flip in parity
+ {
+ int syndromeIP1 = (1 << (10-ip1));
+ corr[3*syndromeIP1 + 0] = 10 - ip1 + shiftP;
+
+ for (int ip2 = ip1+1; ip2 < 11; ip2++) // 1 more bit flip in parity
+ {
+ int syndromeIP2 = syndromeIP1 ^ (1 << (10-ip2));
+ corr[3*syndromeIP2 + 0] = 10 - ip1 + shiftP;
+ corr[3*syndromeIP2 + 1] = 10 - ip2 + shiftP;
+
+ for (int ip3 = ip2+1; ip3 < 11; ip3++) // 1 more bit flip in parity
+ {
+ int syndromeIP3 = syndromeIP2 ^ (1 << (10-ip3));
+ corr[3*syndromeIP3 + 0] = 10 - ip1 + shiftP;
+ corr[3*syndromeIP3 + 1] = 10 - ip2 + shiftP;
+ corr[3*syndromeIP3 + 2] = 10 - ip3 + shiftP;
+ }
+ }
+ }
+}
+
diff --git a/sdrbase/util/golay2312.h b/sdrbase/util/golay2312.h
new file mode 100644
index 000000000..aa8044006
--- /dev/null
+++ b/sdrbase/util/golay2312.h
@@ -0,0 +1,57 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_GOLAY2312_H_
+#define INCLUDE_GOLAY2312_H_
+
+#include "export.h"
+
+class SDRBASE_API Golay2312
+{
+public:
+ Golay2312();
+ ~Golay2312();
+
+ void encodeParityLast(unsigned int msg, unsigned int *tx);
+ void encodeParityFirst(unsigned int msg, unsigned int *tx);
+ bool decodeParityLast(unsigned int *rx);
+ bool decodeParityFirst(unsigned int *rx);
+
+private:
+ inline int bitAt(int i, unsigned int v) {
+ return (v>>i) & 0x01;
+ }
+ void initG();
+ void initH();
+ void buildCorrMatrix(unsigned char *corr, unsigned int *H, bool pf = false);
+ unsigned int syn(unsigned int *H, unsigned int rx);
+ bool lut(unsigned char *corr, unsigned int syndrome, unsigned int *rx);
+
+ unsigned char m_corrPL[2048*3]; //!< up to 3 bit error correction by syndrome index - parity last
+ unsigned char m_corrPF[2048*3]; //!< up to 3 bit error correction by syndrome index - parity first
+ unsigned int m_GPL[23]; //!< Generator matrix of 23x12 bits - parity first (MSB)
+ unsigned int m_GPF[23]; //!< Generator matrix of 23x12 bits - parity last (LSB)
+ unsigned int m_HPL[11]; //!< Parity check matrix of 11x23 bits - parity last (LSB)
+ unsigned int m_HPF[11]; //!< Parity check matrix of 11x23 bits - parity first (MSB)
+ // building arrays
+ static const unsigned int m_B[11]; //!< Coding matrix (11x12 bits)
+ static const unsigned int m_I12[12]; //!< 12x12 identity matrix (12x12 bits)
+ static const unsigned int m_I11[11]; //!< 11x11 identity matrix (11x11 bits)
+
+};
+
+#endif // INCLUDE_GOLAY2312_H_
diff --git a/sdrbench/CMakeLists.txt b/sdrbench/CMakeLists.txt
index 9e79458ab..ffc9c9258 100644
--- a/sdrbench/CMakeLists.txt
+++ b/sdrbench/CMakeLists.txt
@@ -3,6 +3,7 @@ project (sdrbench)
set(sdrbench_SOURCES
mainbench.cpp
parserbench.cpp
+ test_golay2312.cpp
)
set(sdrbench_HEADERS
diff --git a/sdrbench/mainbench.cpp b/sdrbench/mainbench.cpp
index 52988013a..cb31eb8a3 100644
--- a/sdrbench/mainbench.cpp
+++ b/sdrbench/mainbench.cpp
@@ -64,6 +64,8 @@ void MainBench::run()
testDecimateFF();
} else if (m_parser.getTestType() == ParserBench::TestAMBE) {
testAMBE();
+ } else if (m_parser.getTestType() == ParserBench::TestGolay2312) {
+ testGolay2312();
} else {
qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType();
}
@@ -207,6 +209,7 @@ void MainBench::testAMBE()
}
}
+
void MainBench::decimateII(const qint16* buf, int len)
{
SampleVector::iterator it = m_convertBuffer.begin();
diff --git a/sdrbench/mainbench.h b/sdrbench/mainbench.h
index 008c3d224..397ee6319 100644
--- a/sdrbench/mainbench.h
+++ b/sdrbench/mainbench.h
@@ -54,6 +54,7 @@ private:
void testDecimateFI();
void testDecimateFF();
void testAMBE();
+ void testGolay2312();
void decimateII(const qint16 *buf, int len);
void decimateInfII(const qint16 *buf, int len);
void decimateSupII(const qint16 *buf, int len);
diff --git a/sdrbench/parserbench.cpp b/sdrbench/parserbench.cpp
index adbe3b135..7602a6904 100644
--- a/sdrbench/parserbench.cpp
+++ b/sdrbench/parserbench.cpp
@@ -24,7 +24,7 @@
ParserBench::ParserBench() :
m_testOption(QStringList() << "t" << "test",
- "Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe",
+ "Test type: decimateii, decimatefi, decimateff, decimateif, decimateinfii, decimatesupii, ambe, golay2312",
"test",
"decimateii"),
m_nbSamplesOption(QStringList() << "n" << "nb-samples",
@@ -69,7 +69,7 @@ void ParserBench::parse(const QCoreApplication& app)
QString test = m_parser.value(m_testOption);
- QString testStr = "([a-z]+)";
+ QString testStr = "([a-z0-9]+)";
QRegExp ipRegex ("^" + testStr + "$");
QRegExpValidator ipValidator(ipRegex);
@@ -127,6 +127,8 @@ ParserBench::TestType ParserBench::getTestType() const
return TestDecimatorsSupII;
} else if (m_testStr == "ambe") {
return TestAMBE;
+ } else if (m_testStr == "golay2312") {
+ return TestGolay2312;
} else {
return TestDecimatorsII;
}
diff --git a/sdrbench/parserbench.h b/sdrbench/parserbench.h
index 5cdf715ec..9dce1cfdd 100644
--- a/sdrbench/parserbench.h
+++ b/sdrbench/parserbench.h
@@ -35,7 +35,8 @@ public:
TestDecimatorsFF,
TestDecimatorsInfII,
TestDecimatorsSupII,
- TestAMBE
+ TestAMBE,
+ TestGolay2312
} TestType;
ParserBench();
diff --git a/sdrbench/test_golay2312.cpp b/sdrbench/test_golay2312.cpp
new file mode 100644
index 000000000..e55d9bd65
--- /dev/null
+++ b/sdrbench/test_golay2312.cpp
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2021 Edouard Griffiths, F4EXB. //
+// //
+// Swagger server adapter interface //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// (at your option) any later version. //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include "mainbench.h"
+#include "util/golay2312.h"
+
+void MainBench::testGolay2312()
+{
+ qDebug() << "MainBench::testGolay2312: parity first";
+
+ unsigned int msg = 04023; // {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1};
+ unsigned int expectedCodeword = 035430000 + msg;
+ Golay2312 golay2312;
+ bool success = true;
+
+ unsigned int codeword;
+ golay2312.encodeParityFirst(msg, &codeword);
+
+ if (codeword != expectedCodeword)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << "encoder mismatch: got:" << oct << codeword
+ << "expected:" << oct << expectedCodeword;
+ success = false;
+ }
+
+ unsigned int rxCodeword = codeword;
+ bool decoded = golay2312.decodeParityFirst(&rxCodeword);
+
+ if (!decoded)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << " unrecoverable error (no error)";
+ success = false;
+ }
+ else if (rxCodeword != codeword)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << "decoder mismatch (no error): got:" << oct << rxCodeword
+ << "expected:" << oct << codeword;
+ success = false;
+ }
+
+ // flip one bit (4th)
+ rxCodeword = codeword ^ 000020000;
+ decoded = golay2312.decodeParityFirst(&rxCodeword);
+
+ if (!decoded)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << " unrecoverable error (parity[1])";
+ success = false;
+ }
+ else if (rxCodeword != codeword)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << "decoder mismatch (parity[1]): got:" << oct << rxCodeword
+ << "expected:" << oct << codeword;
+ success = false;
+ }
+
+ // flip two bits (1st, 5th)
+ rxCodeword = codeword ^ 000120000;
+ decoded = golay2312.decodeParityFirst(&rxCodeword);
+
+ if (!decoded)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << " unrecoverable error (parity[1,3])";
+ success = false;
+ }
+ else if (rxCodeword != codeword)
+ {
+ qDebug() << "MainBench::testGolay2312:"
+ << "decoder mismatch (parity[1,3]): got:" << oct << rxCodeword
+ << "expected:" << oct << codeword;
+ success = false;
+ }
+
+ // Conclusion
+
+ if (success) {
+ qDebug() << "MainBench::testGolay2312: success";
+ } else {
+ qDebug() << "MainBench::testGolay2312: failed";
+ }
+}
+