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 @@
gui/rollupwidget.h
1 - - LevelMeterSignalDB - QWidget -
gui/levelmeter.h
- 1 -
ButtonSwitch QToolButton @@ -714,6 +784,12 @@
gui/valuedialz.h
1
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 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"; + } +} +