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

VOR Demod: Improve filtering for voice over ident. For #1590

This commit is contained in:
Jon Beniston 2023-03-06 15:06:05 +00:00
parent 1b2162d88f
commit 9a0b12300a
12 changed files with 135 additions and 23 deletions

View File

@ -8,13 +8,13 @@ VORs transmit two 30Hz signals, one AM at the VOR center frequency and one FM on
VORs also transmit a Morse code ident signal at a 1020Hz offset. This is a 2 or 3 character identifier used to identify the VOR, as multiple VORs can be transmitted on the same frequency. For example, the VOR at London Heathrow transmits .-.. --- -. for LON. The Morse code ident is typically transmitted at 10 seconds intervals at between 7 and 10 wpm. VORs that are under maintenance may transmit TST.
Some VORs also transmit an AM voice identification or information signal between 300-3kHz.
Some VORs also transmit an AM voice identification or information signal (ATIS) between 300-3kHz.
Note that for aircraft, there is typically a direct line-of-sight to the VOR. This is unlikely to be the case when using an SDR on the ground. To get good results, ideally you want to be on a nice high hill or close to the VOR.
<h2>Using it for localization</h2>
Several instances of this plugin can be created to monitor multiple VORs and collate information in the VOR Localizer feature plugin. The VOR Localizer can also perform a round robin on multiple VORs with just one VOR demodulator. Please refer to [VOR Localizer](../../feature/vorlocalizer/readme.md) for more information about this feature plugin
Several instances of this plugin can be created to monitor multiple VORs and collate information in the VOR Localizer feature plugin. The VOR Localizer can also perform a round robin on multiple VORs with just one VOR demodulator. Please refer to [VOR Localizer](../../feature/vorlocalizer/readme.md) for more information about this feature plugin.
<h2>Interface</h2>
@ -46,6 +46,10 @@ If you right click on it it will open a dialog to select the audio output device
This is the Morse code ident threshold, expressed as a linear signal to noise (SNR) ratio. This is effectively the signal level required for the Morse demodulator to detect a dot or dash. Setting this to low values will allow the Morse demodulator to detect weak signals, but it also increases the likelihood that noise will incorrectly be interpreted as a signal, resulting in invalid idents being reported.
<h3>Ident bandpass filter</h3>
Click to toggle a narrow bandpass filter around the Morse ident signal. This can be used to filter out the voice signal from the audio output, to make the Morse ident signal clearer.
<h3>6: Squelch threshold</h3>
This is the squelch threshold in dB. The average total power received in the signal bandwidth before demodulation is compared to this value and the squelch input is open above this value. It can be varied continuously in 0.1 dB steps from 0.0 to -100.0 dB using the dial button.
@ -54,17 +58,17 @@ This is the squelch threshold in dB. The average total power received in the sig
This is the volume of the audio signal from 0.0 (mute) to 10.0 (maximum). It can be varied continuously in 0.1 steps using the dial button.
<h3>8: Radial dircetion</h3>
<h3>8: Radial direction</h3>
Demodulated radial direction in degrees (unadjusted for magnetic declination). If there is a low confidence the value is correct (due to a weak signal), it will be displayed in red.
<h3>9: Reference signal power in dB</h3>
Magnitude of the received 30Hz FM reference signal in dB
Magnitude of the received 30Hz FM reference signal in dB.
<h3>10. Variable signal power in dB</h3>
Mangitude of the received 30Hz AM variable signal in dB
Mangitude of the received 30Hz AM variable signal in dB.
<h3>11. VOR identifier code (decoded)</h3>

View File

@ -262,6 +262,7 @@ void VORDemod::applySettings(const VORDemodSettings& settings, bool force)
<< " m_navId: " << settings.m_navId
<< " m_volume: " << settings.m_volume
<< " m_squelch: " << settings.m_squelch
<< " m_identBandpassEnable: " << settings.m_identBandpassEnable
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_streamIndex: " << settings.m_streamIndex
@ -296,7 +297,9 @@ void VORDemod::applySettings(const VORDemodSettings& settings, bool force)
if ((m_settings.m_audioMute != settings.m_audioMute) || force) {
reverseAPIKeys.append("audioMute");
}
if ((m_settings.m_identBandpassEnable != settings.m_identBandpassEnable) || force) {
reverseAPIKeys.append("identBandpassEnable");
}
if ((m_settings.m_volume != settings.m_volume) || force) {
reverseAPIKeys.append("volume");
}
@ -431,6 +434,9 @@ void VORDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("squelch")) {
settings.m_squelch = response.getVorDemodSettings()->getSquelch();
}
if (channelSettingsKeys.contains("identBandpassEnable")) {
settings.m_identBandpassEnable = response.getVorDemodSettings()->getIdentBandpassEnable();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getVorDemodSettings()->getTitle();
}
@ -487,6 +493,7 @@ void VORDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getVorDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
response.getVorDemodSettings()->setRgbColor(settings.m_rgbColor);
response.getVorDemodSettings()->setSquelch(settings.m_squelch);
response.getVorDemodSettings()->setIdentBandpassEnable(settings.m_identBandpassEnable);
response.getVorDemodSettings()->setVolume(settings.m_volume);
if (response.getVorDemodSettings()->getTitle()) {
@ -675,6 +682,9 @@ void VORDemod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("squelch") || force) {
swgVORDemodSettings->setSquelch(settings.m_squelch);
}
if (channelSettingsKeys.contains("identBandpassEnable") || force) {
swgVORDemodSettings->setIdentBandpassEnable(settings.m_identBandpassEnable);
}
if (channelSettingsKeys.contains("title") || force) {
swgVORDemodSettings->setTitle(new QString(settings.m_title));
}

View File

@ -225,6 +225,12 @@ void VORDemodGUI::on_audioMute_toggled(bool checked)
applySettings();
}
void VORDemodGUI::on_identBandpassEnable_toggled(bool checked)
{
m_settings.m_identBandpassEnable = checked;
applySettings();
}
void VORDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
@ -385,6 +391,7 @@ void VORDemodGUI::displaySettings()
ui->squelchText->setText(QString("%1 dB").arg(m_settings.m_squelch));
ui->audioMute->setChecked(m_settings.m_audioMute);
ui->identBandpassEnable->setChecked(m_settings.m_identBandpassEnable);
updateIndexLabel();
@ -462,6 +469,7 @@ void VORDemodGUI::makeUIConnections()
QObject::connect(ui->volume, &QDial::valueChanged, this, &VORDemodGUI::on_volume_valueChanged);
QObject::connect(ui->squelch, &QDial::valueChanged, this, &VORDemodGUI::on_squelch_valueChanged);
QObject::connect(ui->audioMute, &QToolButton::toggled, this, &VORDemodGUI::on_audioMute_toggled);
QObject::connect(ui->identBandpassEnable, &QToolButton::toggled, this, &VORDemodGUI::on_identBandpassEnable_toggled);
}
void VORDemodGUI::updateAbsoluteCenterFrequency()

View File

@ -99,6 +99,7 @@ private slots:
void on_volume_valueChanged(int value);
void on_squelch_valueChanged(int value);
void on_audioMute_toggled(bool checked);
void on_identBandpassEnable_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VORDemodGUI</class>
<widget class="RollupContents" name="VORDemodSCGUI">
<widget class="RollupContents" name="VORDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
@ -325,6 +325,28 @@
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="identBandpassEnable">
<property name="toolTip">
<string>Toggle Morse identifier bandpass filter to filter voice</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/filter_bandpass.png</normaloff>
<normalon>:/filter_bandpass.png</normalon>:/filter_bandpass.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
@ -619,6 +641,11 @@
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>

View File

@ -37,6 +37,7 @@ void VORDemodSettings::resetToDefaults()
m_squelch = -60.0;
m_volume = 2.0;
m_audioMute = false;
m_identBandpassEnable = false;
m_rgbColor = QColor(255, 255, 102).rgb();
m_title = "VOR Demodulator";
m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
@ -49,7 +50,7 @@ void VORDemodSettings::resetToDefaults()
m_workspaceIndex = 0;
m_hidden = false;
m_identThreshold = 2.0;
m_identThreshold = 4.0;
m_refThresholdDB = -45.0;
m_varThresholdDB = -90.0;
}
@ -65,6 +66,7 @@ QByteArray VORDemodSettings::serialize() const
if (m_channelMarker) {
s.writeBlob(6, m_channelMarker->serialize());
}
s.writeBool(8, m_identBandpassEnable);
s.writeU32(7, m_rgbColor);
s.writeString(9, m_title);
@ -118,6 +120,7 @@ bool VORDemodSettings::deserialize(const QByteArray& data)
d.readBlob(6, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readBool(8, &m_identBandpassEnable, false);
d.readU32(7, &m_rgbColor, QColor(255, 255, 102).rgb());
d.readString(9, &m_title, "VOR Demodulator");
@ -136,7 +139,7 @@ bool VORDemodSettings::deserialize(const QByteArray& data)
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(18, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readReal(20, &m_identThreshold, 2.0);
d.readReal(20, &m_identThreshold, 4.0);
d.readReal(21, &m_refThresholdDB, -45.0);
d.readReal(22, &m_varThresholdDB, -90.0);

View File

@ -31,6 +31,7 @@ struct VORDemodSettings
Real m_squelch;
Real m_volume;
bool m_audioMute;
bool m_identBandpassEnable;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;

View File

@ -250,8 +250,9 @@ void VORDemodSCSink::processOneSample(Complex &ci)
m_refGoertzel.filter(phi);
// Ident demod
// Filter to remove voice
Complex c1 = m_bandpassIdent.filter(magc);
// Remove ident sub-carrier offset
Complex c1 = magc;
c1 *= m_ncoIdent.nextIQ();
// Filter other signals
Complex c2 = std::abs(m_lowpassIdent.filter(c1));
@ -264,7 +265,7 @@ void VORDemodSCSink::processOneSample(Complex &ci)
if (mav > m_identMaxs[m_binCnt])
m_identMaxs[m_binCnt] = mav;
m_binSampleCnt++;
if (m_binSampleCnt >= m_samplesPerDot10wpm/2)
if (m_binSampleCnt >= m_samplesPerDot10wpm/4)
{
// Calc minimum of maximums
m_identNoise = 1.0f;
@ -285,11 +286,12 @@ void VORDemodSCSink::processOneSample(Complex &ci)
// CW demod
int bit = (mav / m_identNoise) >= m_settings.m_identThreshold;
//m_stream << mav << "," << m_identNoise << "," << bit << "," << (mav / m_identNoise) << "\n";
if ((m_prevBit == 0) && (bit == 1))
{
if (m_bitTime > 7*m_samplesPerDot10wpm)
{
if (m_ident != "")
if (m_ident.trimmed().size() > 2) // Filter out noise that may appear as one or two characters
{
qDebug() << "VORDemodSCSink::processOneSample:" << m_ident << " " << Morse::toString(m_ident);
@ -298,9 +300,8 @@ void VORDemodSCSink::processOneSample(Complex &ci)
VORDemodReport::MsgReportIdent *msg = VORDemodReport::MsgReportIdent::create(m_ident);
getMessageQueueToChannel()->push(msg);
}
m_ident = "";
}
m_ident = "";
}
else if (m_bitTime > 2.5*m_samplesPerDot10wpm)
{
@ -330,8 +331,8 @@ void VORDemodSCSink::processOneSample(Complex &ci)
if (m_bitTime > 10*m_samplesPerDot7wpm)
{
m_ident = m_ident.simplified();
if (m_ident != "")
{
if (m_ident.trimmed().size() > 2) // Filter out noise that may appear as one or two characters
{
qDebug() << "VORDemodSCSink::processOneSample:" << m_ident << " " << Morse::toString(m_ident);
if (getMessageQueueToChannel())
@ -340,8 +341,8 @@ void VORDemodSCSink::processOneSample(Complex &ci)
getMessageQueueToChannel()->push(msg);
}
m_ident = "";
}
m_ident = "";
m_bitTime = 0;
}
}
@ -371,6 +372,14 @@ void VORDemodSCSink::applyChannelSettings(int channelSampleRate, int channelFreq
m_ncoIdent.setFreq(-1020, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE); // +-50Hz source offset allowed
m_ncoRef.setFreq(-9960, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE);
m_bandpassIdent.create(1001, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE, 970.0f, 1070.0f); // Ident at 1020
//m_bandpassIdent.printTaps("bpf");
m_highpassIdent.create(1001, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE, 900.0f);
//m_highpassIdent.printTaps("hpf");
//m_file.setFileName("morse.txt");
//m_file.open(QIODevice::WriteOnly);
//m_stream.setDevice(&m_file);
m_lowpassIdent.create(301, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE, 100.0f);
m_lowpassRef.create(301, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE, 600.0f); // Max deviation is 480Hz
m_movingAverageIdent.resize(m_samplesPerDot10wpm/5); // Needs to be short enough for noise floor calculation
@ -395,6 +404,7 @@ void VORDemodSCSink::applySettings(const VORDemodSettings& settings, bool force)
<< " m_squelch: " << settings.m_squelch
<< " m_audioMute: " << settings.m_audioMute
<< " m_audioDeviceName: " << settings.m_audioDeviceName
<< " m_identBandpassEnable: " << settings.m_identBandpassEnable
<< " force: " << force;
if ((m_settings.m_squelch != settings.m_squelch) || force) {
@ -416,6 +426,16 @@ void VORDemodSCSink::applySettings(const VORDemodSettings& settings, bool force)
m_varGoertzel.reset();
}
if ((m_settings.m_identBandpassEnable != settings.m_identBandpassEnable) || force)
{
if (settings.m_identBandpassEnable) {
m_bandpass.create(1001, m_audioSampleRate, 970.0f, 1070.0f);
} else {
m_bandpass.create(301, m_audioSampleRate, 300.0f, 3000.0f);
}
//m_bandpass.printTaps("audio_bpf");
}
m_settings = settings;
}
@ -433,7 +453,12 @@ void VORDemodSCSink::applyAudioSampleRate(int sampleRate)
m_audioInterpolator.create(16, VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE, 3000.0f);
m_audioInterpolatorDistanceRemain = 0;
m_audioInterpolatorDistance = (Real) VORDemodSettings::VORDEMOD_CHANNEL_SAMPLE_RATE / (Real) sampleRate;
m_bandpass.create(301, sampleRate, 300.0f, 3000.0f);
if (m_settings.m_identBandpassEnable) {
m_bandpass.create(1001, sampleRate, 970.0f, 1070.0f);
} else {
m_bandpass.create(301, sampleRate, 300.0f, 3000.0f);
}
//m_bandpass.printTaps("audio_bpf");
m_audioFifo.setSize(sampleRate);
m_squelchDelayLine.resize(sampleRate/5);

View File

@ -29,11 +29,11 @@
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "util/messagequeue.h"
#include <QFile>
#include <QTextStream>
#include "vordemodsettings.h"
#include <vector>
class VORDemodSCSink : public ChannelSampleSink {
public:
VORDemodSCSink();
@ -119,12 +119,12 @@ private:
NCO m_ncoIdent;
NCO m_ncoRef;
Lowpass<Complex> m_lowpassRef;
Bandpass<Complex> m_bandpassIdent;
Lowpass<Complex> m_lowpassIdent;
Highpass<Real> m_highpassIdent;
Complex m_refPrev;
MovingAverageUtilVar<Real, double> m_movingAverageIdent;
static const int m_identBins = 10;
Real m_identMins[m_identBins];
Real m_identMin;
static const int m_identBins = 20;
Real m_identMaxs[m_identBins];
Real m_identNoise;
int m_binSampleCnt;
@ -136,6 +136,8 @@ private:
QString m_ident;
Goertzel m_varGoertzel;
Goertzel m_refGoertzel;
//QFile m_file;
//QTextStream m_stream;
void processOneSample(Complex &ci);
void processOneAudioSample(Complex &ci);

View File

@ -16,6 +16,8 @@ VORDemodSettings:
format: float
audioMute:
type: integer
identBandpassEnable:
type: integer
rgbColor:
type: integer
title:

View File

@ -38,6 +38,8 @@ SWGVORDemodSettings::SWGVORDemodSettings() {
m_volume_isSet = false;
audio_mute = 0;
m_audio_mute_isSet = false;
ident_bandpass_enable = 0;
m_ident_bandpass_enable_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
@ -80,6 +82,8 @@ SWGVORDemodSettings::init() {
m_volume_isSet = false;
audio_mute = 0;
m_audio_mute_isSet = false;
ident_bandpass_enable = 0;
m_ident_bandpass_enable_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
@ -114,6 +118,7 @@ SWGVORDemodSettings::cleanup() {
if(title != nullptr) {
delete title;
}
@ -158,6 +163,8 @@ SWGVORDemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&audio_mute, pJson["audioMute"], "qint32", "");
::SWGSDRangel::setValue(&ident_bandpass_enable, pJson["identBandpassEnable"], "qint32", "");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
@ -213,6 +220,9 @@ SWGVORDemodSettings::asJsonObject() {
if(m_audio_mute_isSet){
obj->insert("audioMute", QJsonValue(audio_mute));
}
if(m_ident_bandpass_enable_isSet){
obj->insert("identBandpassEnable", QJsonValue(ident_bandpass_enable));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
@ -303,6 +313,16 @@ SWGVORDemodSettings::setAudioMute(qint32 audio_mute) {
this->m_audio_mute_isSet = true;
}
qint32
SWGVORDemodSettings::getIdentBandpassEnable() {
return ident_bandpass_enable;
}
void
SWGVORDemodSettings::setIdentBandpassEnable(qint32 ident_bandpass_enable) {
this->ident_bandpass_enable = ident_bandpass_enable;
this->m_ident_bandpass_enable_isSet = true;
}
qint32
SWGVORDemodSettings::getRgbColor() {
return rgb_color;
@ -443,6 +463,9 @@ SWGVORDemodSettings::isSet(){
if(m_audio_mute_isSet){
isObjectUpdated = true; break;
}
if(m_ident_bandpass_enable_isSet){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}

View File

@ -59,6 +59,9 @@ public:
qint32 getAudioMute();
void setAudioMute(qint32 audio_mute);
qint32 getIdentBandpassEnable();
void setIdentBandpassEnable(qint32 ident_bandpass_enable);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
@ -114,6 +117,9 @@ private:
qint32 audio_mute;
bool m_audio_mute_isSet;
qint32 ident_bandpass_enable;
bool m_ident_bandpass_enable_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;