1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-13 20:01:46 -05:00

NFM mod: implemented Digital Code Squelch (DCS)

This commit is contained in:
f4exb 2021-04-12 12:03:33 +02:00
parent 50940351b7
commit d0c50549af
14 changed files with 284 additions and 1 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

View File

@ -3,6 +3,7 @@ project(modnfm)
set(modnfm_SOURCES set(modnfm_SOURCES
nfmmod.cpp nfmmod.cpp
nfmmodbaseband.cpp nfmmodbaseband.cpp
nfmmoddcs.cpp
nfmmodsource.cpp nfmmodsource.cpp
nfmmodplugin.cpp nfmmodplugin.cpp
nfmmodsettings.cpp nfmmodsettings.cpp
@ -12,6 +13,7 @@ set(modnfm_SOURCES
set(modnfm_HEADERS set(modnfm_HEADERS
nfmmod.h nfmmod.h
nfmmodbaseband.h nfmmodbaseband.h
nfmmoddcs.h
nfmmodsource.h nfmmodsource.h
nfmmodplugin.h nfmmodplugin.h
nfmmodsettings.h nfmmodsettings.h

View File

@ -260,6 +260,9 @@ void NFMMod::applySettings(const NFMModSettings& settings, bool force)
<< " m_toneFrequency: " << settings.m_toneFrequency << " m_toneFrequency: " << settings.m_toneFrequency
<< " 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_channelMute: " << settings.m_channelMute << " m_channelMute: " << settings.m_channelMute
<< " m_playLoop: " << settings.m_playLoop << " m_playLoop: " << settings.m_playLoop
<< " m_modAFInput " << settings.m_modAFInput << " m_modAFInput " << settings.m_modAFInput

View File

@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// 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: https://mmi-comm.tripod.com/dcs.html //
///////////////////////////////////////////////////////////////////////////////////
#include <bitset>
#include "nfmmoddcs.h"
const float NFMModDCS::m_codeRate = 134.3; //!< bits per second
NFMModDCS::NFMModDCS()
{
reset();
}
NFMModDCS::~NFMModDCS()
{
}
void NFMModDCS::reset()
{
m_step = 0;
}
void NFMModDCS::setDCS(int code)
{
unsigned int dcsCode = code < 0 ? 0 : code > 511 ? 511 : code; // trim invalid values
// Parity bits calculation:
unsigned int p[11];
p[0] = std::bitset<32>(dcsCode & 0b10011111).count() % 2; // P0 = C0 + C1 + C2 + C3 + C4 + C7 (modulo two addition)
p[1] = (std::bitset<32>(dcsCode & 0b100111110).count()+1) % 2; // P1 = NOT ( C1 + C2 + C3 + C4 + C5 + C8 )
p[2] = std::bitset<32>(dcsCode & 0b11100011).count() % 2; // P2 = C0 + C1 + C5 + C6 + C7
p[3] = (std::bitset<32>(dcsCode & 0b111000110).count()+1) % 2; // P3 = NOT ( C1 + C2 + C6 + C7 + C8 )
p[4] = (std::bitset<32>(dcsCode & 0b100010011).count()+1) % 2; // P4 = NOT ( C0 + C1 + C4 + C8 )
p[5] = (std::bitset<32>(dcsCode & 0b10111001).count()+1) % 2; // P5 = NOT ( C0 + C3 + C4 + C5 + C7 )
p[6] = std::bitset<32>(dcsCode & 0b111101101).count() % 2; // P6 = C0 + C2 + C3 + C5 + C6 + C7 + C8
p[7] = std::bitset<32>(dcsCode & 0b111011010).count() % 2; // P7 = C1 + C3 + C4 + C6 + C7 + C8
p[8] = std::bitset<32>(dcsCode & 0b110110100).count() % 2; // P8 = C2 + C4 + C5 + C7 + C8
p[9] = (std::bitset<32>(dcsCode & 0b101101000).count()+1) % 2; // P9 = NOT ( C3 + C5 + C6 + C8 )
p[10] = (std::bitset<32>(dcsCode & 0b1001111).count()+1) % 2; // P10 = NOT ( C0 + C1 + C2 + C3 + C6 )
int dcsIndex = 0;
// code:
for (int i = 0; i < 9; i++, dcsIndex++) {
m_dcsWord[dcsIndex] = (dcsCode >> i) & 1;
}
// filler (0x04)
m_dcsWord[dcsIndex++] = 0;
m_dcsWord[dcsIndex++] = 0;
m_dcsWord[dcsIndex++] = 1;
// parity
for (int i = 0; i < 11; i++, dcsIndex++) {
m_dcsWord[dcsIndex] = p[i];
}
m_step = 0;
}
void NFMModDCS::setPositive(bool positive)
{
m_positive = positive;
m_step = 0;
}
void NFMModDCS::setSampleRate(int sampleRate)
{
m_bitPerStep = m_codeRate / sampleRate;
m_step = 0;
}
int NFMModDCS::next()
{
int dcsIndex = (int) m_step;
int carrier = (m_dcsWord[dcsIndex] == 1) ? (m_positive ? 1 : -1) : (m_positive ? -1 : 1);
if (m_step + m_bitPerStep < 23) {
m_step += m_bitPerStep;
} else {
m_step = m_step + m_bitPerStep - 23;
}
return carrier;
}

View File

@ -0,0 +1,43 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 PLUGINS_CHANNELTX_MODNFM_NFMMODDCS_H_
#define PLUGINS_CHANNELTX_MODNFM_NFMMODDCS_H_
class NFMModDCS
{
public:
NFMModDCS();
~NFMModDCS();
void reset();
void setDCS(int code);
void setPositive(bool positive);
void setSampleRate(int sampleRate);
int next(); //!< +1/-1 sample
private:
int m_shift; //!< current frequency shift: -1 or 1
int m_dcsWord[23]; //!< current DCS word in transmit order including parity and filler 11 + 3 + 9
float m_step;
bool m_positive;
float m_bitPerStep;
static const float m_codeRate;
};
#endif // PLUGINS_CHANNELTX_MODNFM_NFMMODDCS_H_

View File

@ -20,6 +20,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QTime> #include <QTime>
#include <QDebug> #include <QDebug>
#include <QRegExp>
#include "device/deviceuiset.h" #include "device/deviceuiset.h"
#include "plugin/pluginapi.h" #include "plugin/pluginapi.h"
@ -299,10 +300,35 @@ void NFMModGUI::on_ctcss_currentIndexChanged(int index)
void NFMModGUI::on_ctcssOn_toggled(bool checked) void NFMModGUI::on_ctcssOn_toggled(bool checked)
{ {
ui->dcsOn->setEnabled(!checked);
m_settings.m_ctcssOn = checked; m_settings.m_ctcssOn = checked;
applySettings(); applySettings();
} }
void NFMModGUI::on_dcsOn_toggled(bool checked)
{
ui->ctcssOn->setEnabled(!checked);
m_settings.m_dcsOn = checked;
applySettings();
}
void NFMModGUI::on_dcsCode_editingFinished()
{
bool ok;
int dcsCode = ui->dcsCode->text().toInt(&ok, 8);
if (ok)
{
m_settings.m_dcsCode = dcsCode;
applySettings();
}
}
void NFMModGUI::on_dcsPositive_toggled(bool checked)
{
m_settings.m_dcsPositive = checked;
applySettings();
}
void NFMModGUI::configureFileName() void NFMModGUI::configureFileName()
{ {
@ -375,7 +401,8 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
m_audioSampleRate(-1), m_audioSampleRate(-1),
m_feedbackAudioSampleRate(-1), m_feedbackAudioSampleRate(-1),
m_tickCount(0), m_tickCount(0),
m_enableNavTime(false) m_enableNavTime(false),
m_dcsCodeValidator(QRegExp("[0-7]{1,3}"))
{ {
ui->setupUi(this); ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true); setAttribute(Qt::WA_DeleteOnClose, true);
@ -431,6 +458,7 @@ NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
ui->ctcss->addItem(QString("%1").arg((double) NFMModSettings::getCTCSSFreq(i), 0, 'f', 1)); ui->ctcss->addItem(QString("%1").arg((double) NFMModSettings::getCTCSSFreq(i), 0, 'f', 1));
} }
ui->dcsCode->setValidator(&m_dcsCodeValidator);
ui->cwKeyerGUI->setCWKeyer(m_nfmMod->getCWKeyer()); ui->cwKeyerGUI->setCWKeyer(m_nfmMod->getCWKeyer());
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
@ -500,6 +528,11 @@ void NFMModGUI::displaySettings()
ui->ctcssOn->setChecked(m_settings.m_ctcssOn); ui->ctcssOn->setChecked(m_settings.m_ctcssOn);
ui->ctcss->setCurrentIndex(m_settings.m_ctcssIndex); ui->ctcss->setCurrentIndex(m_settings.m_ctcssIndex);
ui->dcsOn->setChecked(m_settings.m_dcsOn);
ui->dcsOn->setEnabled(!m_settings.m_ctcssOn);
ui->dcsCode->setText(tr("%1").arg(m_settings.m_dcsCode, 3, 8, QLatin1Char('0')));
ui->dcsPositive->setChecked(m_settings.m_dcsPositive);
ui->channelMute->setChecked(m_settings.m_channelMute); ui->channelMute->setChecked(m_settings.m_channelMute);
ui->playLoop->setChecked(m_settings.m_playLoop); ui->playLoop->setChecked(m_settings.m_playLoop);

View File

@ -18,6 +18,8 @@
#ifndef PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_ #ifndef PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_
#define PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_ #define PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_
#include <QRegExpValidator>
#include "channel/channelgui.h" #include "channel/channelgui.h"
#include "dsp/channelmarker.h" #include "dsp/channelmarker.h"
#include "util/movingaverage.h" #include "util/movingaverage.h"
@ -70,6 +72,7 @@ private:
bool m_enableNavTime; bool m_enableNavTime;
NFMModSettings::NFMModInputAF m_modAFInput; NFMModSettings::NFMModInputAF m_modAFInput;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
QRegExpValidator m_dcsCodeValidator;
explicit NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0); explicit NFMModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0);
virtual ~NFMModGUI(); virtual ~NFMModGUI();
@ -107,6 +110,9 @@ private slots:
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_dcsCode_editingFinished();
void on_dcsPositive_toggled(bool checked);
void on_feedbackEnable_toggled(bool checked); void on_feedbackEnable_toggled(bool checked);
void on_feedbackVolume_valueChanged(int value); void on_feedbackVolume_valueChanged(int value);

View File

@ -673,6 +673,67 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="dcsLabel">
<property name="text">
<string>DCS</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dcsOn">
<property name="toolTip">
<string>Activate DCS</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="dcsCode">
<property name="minimumSize">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip">
<string>DCS code (octal)</string>
</property>
<property name="text">
<string>023</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dcsPositive">
<property name="toolTip">
<string>DCS code is positive (1 corresponds to positive frequency shift)</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_4"> <spacer name="horizontalSpacer_4">
<property name="orientation"> <property name="orientation">

View File

@ -62,6 +62,9 @@ void NFMModSettings::resetToDefaults()
m_playLoop = false; m_playLoop = false;
m_ctcssOn = false; m_ctcssOn = 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 Modulator"; m_title = "NFM Modulator";
m_modAFInput = NFMModInputAF::NFMModInputNone; m_modAFInput = NFMModInputAF::NFMModInputNone;
@ -113,6 +116,9 @@ QByteArray NFMModSettings::serialize() const
s.writeReal(21, m_feedbackVolumeFactor); s.writeReal(21, m_feedbackVolumeFactor);
s.writeBool(22, m_feedbackAudioEnable); s.writeBool(22, m_feedbackAudioEnable);
s.writeS32(23, m_streamIndex); s.writeS32(23, m_streamIndex);
s.writeBool(24, m_dcsOn);
s.writeS32(25, m_dcsCode);
s.writeBool(26, m_dcsPositive);
return s.final(); return s.final();
} }
@ -186,6 +192,10 @@ bool NFMModSettings::deserialize(const QByteArray& data)
d.readReal(21, &m_feedbackVolumeFactor, 1.0); d.readReal(21, &m_feedbackVolumeFactor, 1.0);
d.readBool(22, &m_feedbackAudioEnable, false); d.readBool(22, &m_feedbackAudioEnable, false);
d.readS32(23, &m_streamIndex, 0); d.readS32(23, &m_streamIndex, 0);
d.readBool(24, &m_dcsOn, false);
d.readS32(25, &tmp, 0023);
m_dcsCode = tmp < 0 ? 0 : tmp > 511 ? 511 : tmp;
d.readBool(26, &m_dcsPositive, false);
return true; return true;
} }

View File

@ -51,6 +51,9 @@ struct NFMModSettings
bool m_playLoop; bool m_playLoop;
bool m_ctcssOn; bool m_ctcssOn;
int m_ctcssIndex; int m_ctcssIndex;
bool m_dcsOn;
int m_dcsCode;
bool m_dcsPositive;
quint32 m_rgbColor; quint32 m_rgbColor;
QString m_title; QString m_title;
NFMModInputAF m_modAFInput; NFMModInputAF m_modAFInput;

View File

@ -152,6 +152,8 @@ void NFMModSource::modulateSample()
if (m_settings.m_ctcssOn) { if (m_settings.m_ctcssOn) {
t1 = (0.85f * m_bandpass.filter(t) + 0.15f * 0.625f * m_ctcssNco.next()) * 1.2f; t1 = (0.85f * m_bandpass.filter(t) + 0.15f * 0.625f * m_ctcssNco.next()) * 1.2f;
} else if (m_settings.m_dcsOn) {
t1 = (0.9f * m_bandpass.filter(t) + 0.1f * 0.625f * m_dcsMod.next()) * 1.2f;
} else { } else {
t1 = m_bandpass.filter(t) * 1.2f; t1 = m_bandpass.filter(t) * 1.2f;
} }
@ -344,6 +346,7 @@ void NFMModSource::applyAudioSampleRate(int sampleRate)
m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth);
m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate); m_toneNco.setFreq(m_settings.m_toneFrequency, sampleRate);
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate); m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(m_settings.m_ctcssIndex), sampleRate);
m_dcsMod.setSampleRate(sampleRate);
m_cwKeyer.setSampleRate(sampleRate); m_cwKeyer.setSampleRate(sampleRate);
m_cwKeyer.reset(); m_cwKeyer.reset();
m_preemphasisFilter.configure(m_preemphasis*sampleRate); m_preemphasisFilter.configure(m_preemphasis*sampleRate);
@ -400,6 +403,14 @@ void NFMModSource::applySettings(const NFMModSettings& settings, bool force)
m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate); m_ctcssNco.setFreq(NFMModSettings::getCTCSSFreq(settings.m_ctcssIndex), m_audioSampleRate);
} }
if ((settings.m_dcsCode != m_settings.m_dcsCode) || force) {
m_dcsMod.setDCS(settings.m_dcsCode);
}
if ((settings.m_dcsPositive != m_settings.m_dcsPositive) || force) {
m_dcsMod.setPositive(settings.m_dcsPositive);
}
if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
{ {
if (settings.m_modAFInput == NFMModSettings::NFMModInputAudio) { if (settings.m_modAFInput == NFMModSettings::NFMModInputAudio) {

View File

@ -36,6 +36,7 @@
#include "audio/audiofifo.h" #include "audio/audiofifo.h"
#include "nfmmodsettings.h" #include "nfmmodsettings.h"
#include "nfmmoddcs.h"
class ChannelAPI; class ChannelAPI;
@ -78,6 +79,7 @@ private:
NCO m_carrierNco; NCO m_carrierNco;
NCOF m_toneNco; NCOF m_toneNco;
NCOF m_ctcssNco; NCOF m_ctcssNco;
NFMModDCS m_dcsMod;
float m_modPhasor; //!< baseband modulator phasor float m_modPhasor; //!< baseband modulator phasor
Complex m_modSample; Complex m_modSample;

View File

@ -248,3 +248,15 @@ This is the audio file play length in time units
<h3>23: Play file position slider</h3> <h3>23: Play file position slider</h3>
This slider can be used to randomly set the current position in the file when file play is in pause state (button 18.3) This slider can be used to randomly set the current position in the file when file play is in pause state (button 18.3)
<h3>24: DCS switch</h3>
Checkbox to switch on the DCS (Digital Code Squelch) sub-audio modulation.
<h3>25: DCS code</h3>
This is the DCS octal code (0..511 in decimal 0..777 in octal)
<h3>26: DCS positive modulation</h3>
When checked the "1" bits are represented with positive shift frequency and "0" with negative shift. When unchecked (negative modulation) this is the opposite.