1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 10:05:46 -05:00

NFM demod: DCS squelch (1)

This commit is contained in:
f4exb 2021-04-17 18:14:15 +02:00
parent 2562d42385
commit c901ba5c63
24 changed files with 1416 additions and 53 deletions

View File

@ -1,6 +1,7 @@
project(nfm) project(nfm)
set(nfm_SOURCES set(nfm_SOURCES
dcsdetector.cpp
nfmdemod.cpp nfmdemod.cpp
nfmdemodsettings.cpp nfmdemodsettings.cpp
nfmdemodwebapiadapter.cpp nfmdemodwebapiadapter.cpp
@ -11,6 +12,7 @@ set(nfm_SOURCES
) )
set(nfm_HEADERS set(nfm_HEADERS
dcsdetector.h
nfmdemod.h nfmdemod.h
nfmdemodsettings.h nfmdemodsettings.h
nfmdemodwebapiadapter.h nfmdemodwebapiadapter.h

View File

@ -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 <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <QMutexLocker>
#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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_DCSDETECTOR_H_
#define INCLUDE_DSP_DCSDETECTOR_H_
#include <QMutex>
#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_

View File

@ -151,6 +151,9 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force)
<< " m_squelch: " << settings.m_squelch << " m_squelch: " << settings.m_squelch
<< " m_ctcssIndex: " << settings.m_ctcssIndex << " m_ctcssIndex: " << settings.m_ctcssIndex
<< " m_ctcssOn: " << settings.m_ctcssOn << " 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_highPass: " << settings.m_highPass
<< " m_audioMute: " << settings.m_audioMute << " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName << " m_audioDeviceName: " << settings.m_audioDeviceName

View File

@ -12,6 +12,7 @@
#include "gui/crightclickenabler.h" #include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h" #include "gui/audioselectdialog.h"
#include "dsp/dspengine.h" #include "dsp/dspengine.h"
#include "dsp/dcscodes.h"
#include "maincore.h" #include "maincore.h"
#include "nfmdemodreport.h" #include "nfmdemodreport.h"
@ -61,6 +62,12 @@ bool NFMDemodGUI::handleMessage(const Message& message)
setCtcssFreq(report.getFrequency()); setCtcssFreq(report.getFrequency());
return true; 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)) else if (NFMDemod::MsgConfigureNFMDemod::match(message))
{ {
qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgConfigureNFMDemod"); qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgConfigureNFMDemod");
@ -214,6 +221,42 @@ void NFMDemodGUI::on_ctcssOn_toggled(bool checked)
applySettings(); 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) void NFMDemodGUI::on_highPassFilter_toggled(bool checked)
{ {
m_settings.m_highPass = checked; m_settings.m_highPass = checked;
@ -297,6 +340,7 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_doApplySettings(true), m_doApplySettings(true),
m_squelchOpen(false), m_squelchOpen(false),
m_audioSampleRate(-1), m_audioSampleRate(-1),
m_reportedDcsCode(0),
m_tickCount(0) m_tickCount(0)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -328,11 +372,22 @@ NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
ui->ctcss->addItem("--"); ui->ctcss->addItem("--");
for (int i=0; i<ctcss_nbTones; i++) for (int i=0; i<ctcss_nbTones; i++) {
{
ui->ctcss->addItem(QString("%1").arg(ctcss_tones[i])); ui->ctcss->addItem(QString("%1").arg(ctcss_tones[i]));
} }
ui->dcsOn->setChecked(m_settings.m_dcsOn);
ui->dscPositive->setChecked(m_settings.m_dcsPositive);
QList<unsigned int> 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); blockApplySettings(false);
ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); // squelch closed 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); 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(); displayStreamIndex();
blockApplySettings(false); blockApplySettings(false);
@ -466,16 +531,28 @@ void NFMDemodGUI::enterEvent(QEvent*)
void NFMDemodGUI::setCtcssFreq(Real ctcssFreq) void NFMDemodGUI::setCtcssFreq(Real ctcssFreq)
{ {
if (ctcssFreq == 0) if (ctcssFreq == 0) {
{
ui->ctcssText->setText("--"); ui->ctcssText->setText("--");
} } else {
else
{
ui->ctcssText->setText(QString("%1").arg(ctcssFreq)); 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) void NFMDemodGUI::blockApplySettings(bool block)
{ {
m_doApplySettings = !block; m_doApplySettings = !block;

View File

@ -29,7 +29,6 @@ public:
QByteArray serialize() const; QByteArray serialize() const;
bool deserialize(const QByteArray& data); bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setCtcssFreq(Real ctcssFreq);
public slots: public slots:
void channelMarkerChangedByCursor(); void channelMarkerChangedByCursor();
@ -47,6 +46,8 @@ private:
NFMDemod* m_nfmDemod; NFMDemod* m_nfmDemod;
bool m_squelchOpen; bool m_squelchOpen;
int m_audioSampleRate; int m_audioSampleRate;
bool m_reportedDcsCode;
bool m_dcsShowPositive;
uint32_t m_tickCount; uint32_t m_tickCount;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
@ -57,6 +58,8 @@ private:
void applySettings(bool force = false); void applySettings(bool force = false);
void displaySettings(); void displaySettings();
void displayStreamIndex(); void displayStreamIndex();
void setCtcssFreq(Real ctcssFreq);
void setDcsCode(unsigned int dcsCode);
bool handleMessage(const Message& message); bool handleMessage(const Message& message);
void leaveEvent(QEvent*); void leaveEvent(QEvent*);
@ -74,6 +77,9 @@ private slots:
void on_squelch_valueChanged(int value); void on_squelch_valueChanged(int value);
void on_ctcss_currentIndexChanged(int index); void on_ctcss_currentIndexChanged(int index);
void on_ctcssOn_toggled(bool checked); 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_highPassFilter_toggled(bool checked);
void on_audioMute_toggled(bool checked); void on_audioMute_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown); void onWidgetRolled(QWidget* widget, bool rollDown);

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>302</width> <width>364</width>
<height>182</height> <height>200</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -18,8 +18,8 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>302</width> <width>364</width>
<height>0</height> <height>200</height>
</size> </size>
</property> </property>
<property name="font"> <property name="font">
@ -36,13 +36,13 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>300</width> <width>362</width>
<height>141</height> <height>191</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>300</width> <width>362</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -537,41 +537,39 @@
<item> <item>
<layout class="QHBoxLayout" name="ctcssLayout"> <layout class="QHBoxLayout" name="ctcssLayout">
<item> <item>
<widget class="QLabel" name="ctcssLabel"> <widget class="QCheckBox" name="ctcssOn">
<property name="toolTip">
<string>Activate CTCSS</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text"> <property name="text">
<string>CTCSS</string> <string>CTCSS</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="CTCSSblock"> <widget class="QComboBox" name="ctcss">
<item> <property name="maximumSize">
<widget class="QCheckBox" name="ctcssOn"> <size>
<property name="toolTip"> <width>60</width>
<string>Activate CTCSS</string> <height>16777215</height>
</property> </size>
<property name="text"> </property>
<string/> <property name="toolTip">
</property> <string>Set CTCSS Frequency</string>
</widget> </property>
</item> </widget>
<item>
<widget class="QComboBox" name="ctcss">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Set CTCSS Frequency</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QLabel" name="ctcssText"> <widget class="QLabel" name="ctcssText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="toolTip"> <property name="toolTip">
<string>CTCSS detected</string> <string>CTCSS detected</string>
</property> </property>
@ -583,6 +581,61 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="dcsOn">
<property name="toolTip">
<string>Activate DCS</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>DCS</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="dcsCode">
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Set DCS code</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="dscPositive">
<property name="toolTip">
<string>Display DCS code as postive</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="dcsText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>DCS detected</string>
</property>
<property name="text">
<string>--</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
@ -596,6 +649,29 @@
</property> </property>
</spacer> </spacer>
</item> </item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="audioLayout">
<property name="topMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QLabel" name="volumeLabel"> <widget class="QLabel" name="volumeLabel">
<property name="text"> <property name="text">
@ -697,12 +773,6 @@
<header>gui/rollupwidget.h</header> <header>gui/rollupwidget.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>ButtonSwitch</class> <class>ButtonSwitch</class>
<extends>QToolButton</extends> <extends>QToolButton</extends>
@ -714,6 +784,12 @@
<header>gui/valuedialz.h</header> <header>gui/valuedialz.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../sdrgui/resources/res.qrc"/> <include location="../../../sdrgui/resources/res.qrc"/>

View File

@ -18,6 +18,7 @@
#include "nfmdemodreport.h" #include "nfmdemodreport.h"
MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message) MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message)
MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportDCSCode, Message)
NFMDemodReport::NFMDemodReport() NFMDemodReport::NFMDemodReport()
{ } { }

View File

@ -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: public:
NFMDemodReport(); NFMDemodReport();
~NFMDemodReport(); ~NFMDemodReport();

View File

@ -60,6 +60,9 @@ void NFMDemodSettings::resetToDefaults()
m_ctcssOn = false; m_ctcssOn = false;
m_audioMute = false; m_audioMute = false;
m_ctcssIndex = 0; m_ctcssIndex = 0;
m_dcsOn = false;
m_dcsCode = 0023;
m_dcsPositive = false;
m_rgbColor = QColor(255, 0, 0).rgb(); m_rgbColor = QColor(255, 0, 0).rgb();
m_title = "NFM Demodulator"; m_title = "NFM Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
@ -101,6 +104,9 @@ QByteArray NFMDemodSettings::serialize() const
s.writeU32(20, m_reverseAPIChannelIndex); s.writeU32(20, m_reverseAPIChannelIndex);
s.writeS32(21, m_streamIndex); s.writeS32(21, m_streamIndex);
s.writeReal(22, m_fmDeviation); s.writeReal(22, m_fmDeviation);
s.writeBool(23, m_dcsOn);
s.writeU32(24, m_dcsCode);
s.writeBool(25, m_dcsPositive);
return s.final(); return s.final();
} }
@ -160,6 +166,10 @@ bool NFMDemodSettings::deserialize(const QByteArray& data)
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readS32(21, &m_streamIndex, 0); d.readS32(21, &m_streamIndex, 0);
d.readReal(22, &m_fmDeviation, 5000.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; return true;
} }

View File

@ -41,6 +41,9 @@ struct NFMDemodSettings
bool m_ctcssOn; bool m_ctcssOn;
bool m_audioMute; bool m_audioMute;
int m_ctcssIndex; int m_ctcssIndex;
bool m_dcsOn;
unsigned int m_dcsCode;
bool m_dcsPositive;
quint32 m_rgbColor; quint32 m_rgbColor;
QString m_title; QString m_title;
QString m_audioDeviceName; QString m_audioDeviceName;

View File

@ -30,6 +30,7 @@
#include "dsp/devicesamplemimo.h" #include "dsp/devicesamplemimo.h"
#include "dsp/misc.h" #include "dsp/misc.h"
#include "dsp/datafifo.h" #include "dsp/datafifo.h"
#include "dsp/dcscodes.h"
#include "device/deviceapi.h" #include "device/deviceapi.h"
#include "maincore.h" #include "maincore.h"
@ -49,6 +50,7 @@ NFMDemodSink::NFMDemodSink() :
m_audioFifo(48000), m_audioFifo(48000),
m_rfFilter(FFT_FILTER_LENGTH), m_rfFilter(FFT_FILTER_LENGTH),
m_ctcssIndex(0), m_ctcssIndex(0),
m_dcsCode(0),
m_sampleCount(0), m_sampleCount(0),
m_squelchCount(0), m_squelchCount(0),
m_squelchGate(4800), m_squelchGate(4800),
@ -68,6 +70,9 @@ NFMDemodSink::NFMDemodSink() :
m_demodBuffer.resize(1<<12); m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0; m_demodBufferFill = 0;
m_dcsDetector.setSampleRate(CTCSS_DETECTOR_RATE);
m_dcsDetector.setEqWindow(23);
applySettings(m_settings, true); applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
} }
@ -171,14 +176,18 @@ void NFMDemodSink::processOneSample(Complex &ci)
m_squelchOpen = m_squelchCount > m_squelchGate; m_squelchOpen = m_squelchCount > m_squelchGate;
int ctcssIndex = m_squelchOpen && m_settings.m_ctcssOn ? m_ctcssIndex : 0; 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_squelchOpen)
{ {
if (m_settings.m_ctcssOn) if (m_settings.m_ctcssOn)
{ {
int factor = (m_audioSampleRate / CTCSS_DETECTOR_RATE) - 1; // decimate -> 6k int factor = (m_audioSampleRate / CTCSS_DETECTOR_RATE) - 1; // decimate -> 6k
if ((m_sampleCount & factor) == factor) if ((m_sampleCount & factor) == factor)
{ {
Real ctcssSample = m_ctcssLowpass.filter(demod); Real ctcssSample = m_ctcssLowpass.filter(demod);
if (m_ctcssDetector.analyze(&ctcssSample)) if (m_ctcssDetector.analyze(&ctcssSample))
{ {
int maxToneIndex; 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); Real audioSample = m_squelchDelayLine.readBack(m_squelchGate);
audioSample = m_settings.m_highPass ? m_bandpass.filter(audioSample) : m_lowpass.filter(audioSample); 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) if (ctcssIndex != m_ctcssIndex)
{ {
auto *guiQueue = getMessageQueueToGUI(); auto *guiQueue = getMessageQueueToGUI();
if (guiQueue) if (guiQueue)
{ {
guiQueue->push(NFMDemodReport::MsgReportCTCSSFreq::create( guiQueue->push(NFMDemodReport::MsgReportCTCSSFreq::create(
ctcssIndex ? m_ctcssDetector.getToneSet()[ctcssIndex - 1] : 0)); ctcssIndex ? m_ctcssDetector.getToneSet()[ctcssIndex - 1] : 0));
} }
m_ctcssIndex = ctcssIndex; 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].l = sample;
m_audioBuffer[m_audioBufferFill].r = sample; m_audioBuffer[m_audioBufferFill].r = sample;
++m_audioBufferFill; ++m_audioBufferFill;
@ -286,6 +329,9 @@ void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
<< " m_squelch: " << settings.m_squelch << " m_squelch: " << settings.m_squelch
<< " m_ctcssIndex: " << settings.m_ctcssIndex << " m_ctcssIndex: " << settings.m_ctcssIndex
<< " m_ctcssOn: " << settings.m_ctcssOn << " 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_highPass: " << settings.m_highPass
<< " m_audioMute: " << settings.m_audioMute << " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName << " m_audioDeviceName: " << settings.m_audioDeviceName
@ -339,6 +385,10 @@ void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force)
setSelectedCtcssIndex(settings.m_ctcssIndex); setSelectedCtcssIndex(settings.m_ctcssIndex);
} }
if ((settings.m_dcsCode != m_settings.m_dcsCode) || force) {
setSelectedDcsCode(settings.m_dcsCode);
}
m_settings = settings; m_settings = settings;
} }
@ -359,8 +409,8 @@ void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate)
} else { } 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.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_phaseDiscri.setFMScaling(Real(sampleRate) / (2.0f * m_settings.m_fmDeviation));
m_audioFifo.setSize(sampleRate); m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/2); m_squelchDelayLine.resize(sampleRate/2);

View File

@ -33,6 +33,7 @@
#include "util/doublebufferfifo.h" #include "util/doublebufferfifo.h"
#include "audio/audiofifo.h" #include "audio/audiofifo.h"
#include "dcsdetector.h"
#include "nfmdemodsettings.h" #include "nfmdemodsettings.h"
class ChannelAPI; class ChannelAPI;
@ -52,6 +53,10 @@ public:
m_ctcssIndexSelected = selectedCtcssIndex; m_ctcssIndexSelected = selectedCtcssIndex;
} }
void setSelectedDcsCode(unsigned int selectedDcsCode) {
m_dcsCodeSeleted = selectedDcsCode;
}
bool getSquelchOpen() const { return m_squelchOpen; } bool getSquelchOpen() const { return m_squelchOpen; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) void getMagSqLevels(double& avg, double& peak, int& nbSamples)
@ -120,6 +125,9 @@ private:
CTCSSDetector m_ctcssDetector; CTCSSDetector m_ctcssDetector;
int m_ctcssIndex; // 0 for nothing detected int m_ctcssIndex; // 0 for nothing detected
int m_ctcssIndexSelected; int m_ctcssIndexSelected;
DCSDetector m_dcsDetector;
unsigned int m_dcsCode;
unsigned int m_dcsCodeSeleted;
int m_sampleCount; int m_sampleCount;
int m_squelchCount; int m_squelchCount;
int m_squelchGate; int m_squelchGate;

View File

@ -101,6 +101,7 @@ set(sdrbase_SOURCES
dsp/cwkeyer.cpp dsp/cwkeyer.cpp
dsp/cwkeyersettings.cpp dsp/cwkeyersettings.cpp
dsp/datafifo.cpp dsp/datafifo.cpp
dsp/dcscodes.cpp
dsp/decimatorsff.cpp dsp/decimatorsff.cpp
dsp/decimatorsfi.cpp dsp/decimatorsfi.cpp
dsp/decimatorc.cpp dsp/decimatorc.cpp
@ -186,6 +187,7 @@ set(sdrbase_SOURCES
util/db.cpp util/db.cpp
util/fixedtraits.cpp util/fixedtraits.cpp
util/fits.cpp util/fits.cpp
util/golay2312.cpp
util/httpdownloadmanager.cpp util/httpdownloadmanager.cpp
util/lfsr.cpp util/lfsr.cpp
util/maidenhead.cpp util/maidenhead.cpp
@ -262,6 +264,7 @@ set(sdrbase_HEADERS
dsp/cwkeyer.h dsp/cwkeyer.h
dsp/cwkeyersettings.h dsp/cwkeyersettings.h
dsp/datafifo.h dsp/datafifo.h
dsp/dcscodes.h
dsp/decimators.h dsp/decimators.h
dsp/decimatorsif.h dsp/decimatorsif.h
dsp/decimatorsff.h dsp/decimatorsff.h
@ -382,6 +385,7 @@ set(sdrbase_HEADERS
util/doublebufferfifo.h util/doublebufferfifo.h
util/fixedtraits.h util/fixedtraits.h
util/fits.h util/fits.h
util/golay2312.h
util/httpdownloadmanager.h util/httpdownloadmanager.h
util/incrementalarray.h util/incrementalarray.h
util/incrementalvector.h util/incrementalvector.h

465
sdrbase/dsp/dcscodes.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
// //
// Source: http://onfreq.com/syntorx/dcs.html //
// //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "dcscodes.h"
const QMap<unsigned int, unsigned int> 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<unsigned int, unsigned int> 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<unsigned int>& codes)
{
codes.clear();
for (auto code : m_toCanonicalCode.keys())
{
if (code == m_toCanonicalCode.value(code)) {
codes.append(code);
}
}
}

35
sdrbase/dsp/dcscodes.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_DSP_DCSCODES_H_
#define INCLUDE_DSP_DCSCODES_H_
#include <QList>
#include <QMap>
#include "export.h"
class SDRBASE_API DCSCodes
{
public:
static void getCanonicalCodes(QList<unsigned int>& codes);
static const int m_nbCodes;
static const QMap<unsigned int, unsigned int> m_toCanonicalCode;
static const QMap<unsigned int, unsigned int> m_signFlip;
};
#endif // INCLUDE_DSP_DCSCODES_H_

253
sdrbase/util/golay2312.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <bitset>
#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;
}
}
}
}

57
sdrbase/util/golay2312.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#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_

View File

@ -3,6 +3,7 @@ project (sdrbench)
set(sdrbench_SOURCES set(sdrbench_SOURCES
mainbench.cpp mainbench.cpp
parserbench.cpp parserbench.cpp
test_golay2312.cpp
) )
set(sdrbench_HEADERS set(sdrbench_HEADERS

View File

@ -64,6 +64,8 @@ void MainBench::run()
testDecimateFF(); testDecimateFF();
} else if (m_parser.getTestType() == ParserBench::TestAMBE) { } else if (m_parser.getTestType() == ParserBench::TestAMBE) {
testAMBE(); testAMBE();
} else if (m_parser.getTestType() == ParserBench::TestGolay2312) {
testGolay2312();
} else { } else {
qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType(); qDebug() << "MainBench::run: unknown test type: " << m_parser.getTestType();
} }
@ -207,6 +209,7 @@ void MainBench::testAMBE()
} }
} }
void MainBench::decimateII(const qint16* buf, int len) void MainBench::decimateII(const qint16* buf, int len)
{ {
SampleVector::iterator it = m_convertBuffer.begin(); SampleVector::iterator it = m_convertBuffer.begin();

View File

@ -54,6 +54,7 @@ private:
void testDecimateFI(); void testDecimateFI();
void testDecimateFF(); void testDecimateFF();
void testAMBE(); void testAMBE();
void testGolay2312();
void decimateII(const qint16 *buf, int len); void decimateII(const qint16 *buf, int len);
void decimateInfII(const qint16 *buf, int len); void decimateInfII(const qint16 *buf, int len);
void decimateSupII(const qint16 *buf, int len); void decimateSupII(const qint16 *buf, int len);

View File

@ -24,7 +24,7 @@
ParserBench::ParserBench() : ParserBench::ParserBench() :
m_testOption(QStringList() << "t" << "test", 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", "test",
"decimateii"), "decimateii"),
m_nbSamplesOption(QStringList() << "n" << "nb-samples", m_nbSamplesOption(QStringList() << "n" << "nb-samples",
@ -69,7 +69,7 @@ void ParserBench::parse(const QCoreApplication& app)
QString test = m_parser.value(m_testOption); QString test = m_parser.value(m_testOption);
QString testStr = "([a-z]+)"; QString testStr = "([a-z0-9]+)";
QRegExp ipRegex ("^" + testStr + "$"); QRegExp ipRegex ("^" + testStr + "$");
QRegExpValidator ipValidator(ipRegex); QRegExpValidator ipValidator(ipRegex);
@ -127,6 +127,8 @@ ParserBench::TestType ParserBench::getTestType() const
return TestDecimatorsSupII; return TestDecimatorsSupII;
} else if (m_testStr == "ambe") { } else if (m_testStr == "ambe") {
return TestAMBE; return TestAMBE;
} else if (m_testStr == "golay2312") {
return TestGolay2312;
} else { } else {
return TestDecimatorsII; return TestDecimatorsII;
} }

View File

@ -35,7 +35,8 @@ public:
TestDecimatorsFF, TestDecimatorsFF,
TestDecimatorsInfII, TestDecimatorsInfII,
TestDecimatorsSupII, TestDecimatorsSupII,
TestAMBE TestAMBE,
TestGolay2312
} TestType; } TestType;
ParserBench(); ParserBench();

106
sdrbench/test_golay2312.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#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";
}
}