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

Merge branch 'rttymod' of https://github.com/srcejon/sdrangel into rttymod

This commit is contained in:
Jon Beniston 2023-09-04 13:15:15 +01:00
commit 2f5f2672d0
31 changed files with 739 additions and 149 deletions

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ obj-x86_64-linux-gnu/*
/rescuesdriq/vendor/ /rescuesdriq/vendor/
/rescuesdriq/Godeps/ /rescuesdriq/Godeps/
/.vs

View File

@ -1,3 +1,17 @@
sdrangel (7.15.4-1) unstable; urgency=medium
* Fix Mac compilation. PR #1786
* Add support for plugin presets. PR #1789
* Map feature updates. PR #1778
* Fix RTLSDR E4000 gain and bandwidth settings. Add tuner type to GUI. PR #1790
* Add support for RTLSDRBlog V4 with HF upsampler. PR #1790
* Update RTLSDR driver to include RTLSDRBlog V4 support. PR #1790
* Add rotator az/el and offset to table. PR #1791
* Optmize redrawing of charts in Star Tracker. PR #1791
* Initialise PhaseDiscriminators state, to avoid outputting huge values. Fix #1794. PR #1794
-- Edouard Griffiths, F4EXB <f4exb06@gmail.com> Sat, 02 Sep 2023 19:12:12 +0200
sdrangel (7.15.3-1) unstable; urgency=medium sdrangel (7.15.3-1) unstable; urgency=medium
* Rotator Controller: Add additional gamepad calibration and functionality. PR #1761 * Rotator Controller: Add additional gamepad calibration and functionality. PR #1761

View File

@ -16,7 +16,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# configure version # configure version
set(sdrangel_VERSION_MAJOR "7") set(sdrangel_VERSION_MAJOR "7")
set(sdrangel_VERSION_MINOR "15") set(sdrangel_VERSION_MINOR "15")
set(sdrangel_VERSION_PATCH "3") set(sdrangel_VERSION_PATCH "4")
set(sdrangel_VERSION_SUFFIX "") set(sdrangel_VERSION_SUFFIX "")
# SDRAngel cmake options # SDRAngel cmake options

View File

@ -1,56 +1,94 @@
{ {
"version": 3, "version": 3,
"configurePresets": [ "configurePresets": [
{ {
"name": "default", "name": "default",
"binaryDir": "build-default", "binaryDir": "build-default",
"cacheVariables": { "cacheVariables": {
"DEBUG_OUTPUT": "ON", "DEBUG_OUTPUT": "ON",
"AIRSPYHF_DIR": "/opt/install/libairspyhf", "AIRSPYHF_DIR": "/opt/install/libairspyhf",
"AIRSPY_DIR": "/opt/install/libairspy", "AIRSPY_DIR": "/opt/install/libairspy",
"APT_DIR": "/opt/install/aptdec", "APT_DIR": "/opt/install/aptdec",
"BLADERF_DIR": "/opt/install/libbladeRF", "BLADERF_DIR": "/opt/install/libbladeRF",
"CM256CC_DIR": "/opt/install/cm256cc", "CM256CC_DIR": "/opt/install/cm256cc",
"CODEC2_DIR": "/opt/install/codec2", "CODEC2_DIR": "/opt/install/codec2",
"DAB_DIR": "/opt/install/libdab", "DAB_DIR": "/opt/install/libdab",
"DSDCC_DIR": "/opt/install/dsdcc", "DSDCC_DIR": "/opt/install/dsdcc",
"HACKRF_DIR": "/opt/install/libhackrf", "HACKRF_DIR": "/opt/install/libhackrf",
"HAMLIB_DIR": "/opt/build/hamlib-prefix", "HAMLIB_DIR": "/opt/build/hamlib-prefix",
"IIO_DIR": "/opt/install/libiio", "IIO_DIR": "/opt/install/libiio",
"LIBSIGMF_DIR": "/opt/install/libsigmf", "LIBSIGMF_DIR": "/opt/install/libsigmf",
"LIMESUITE_DIR": "/opt/install/LimeSuite", "LIMESUITE_DIR": "/opt/install/LimeSuite",
"MBE_DIR": "/opt/install/mbelib", "MBE_DIR": "/opt/install/mbelib",
"MIRISDR_DIR": "/opt/install/libmirisdr", "MIRISDR_DIR": "/opt/install/libmirisdr",
"PERSEUS_DIR": "/opt/install/libperseus", "PERSEUS_DIR": "/opt/install/libperseus",
"RTLSDR_DIR": "/opt/install/librtlsdr", "RTLSDR_DIR": "/opt/install/librtlsdr",
"SERIALDV_DIR": "/opt/install/serialdv", "SERIALDV_DIR": "/opt/install/serialdv",
"SGP4_DIR": "/opt/install/sgp4", "SGP4_DIR": "/opt/install/sgp4",
"SOAPYSDR_DIR": "/opt/install/SoapySDR", "SOAPYSDR_DIR": "/opt/install/SoapySDR",
"UHD_DIR": "/opt/install/uhd", "UHD_DIR": "/opt/install/uhd",
"XTRX_DIR": "/opt/install/xtrx-images", "XTRX_DIR": "/opt/install/xtrx-images",
"CMAKE_INSTALL_PREFIX": "/opt/install/sdrangel" "CMAKE_INSTALL_PREFIX": "/opt/install/sdrangel"
},
"warnings": {
"dev": false
}
}, },
{ "warnings": {
"name": "default-qt6", "dev": false
"inherits": "default", },
"binaryDir": "build-qt6", "vendor": {
"cacheVariables": { "microsoft.com/VisualStudioSettings/CMake/1.0": {
"ENABLE_QT6": "ON" "hostOS": [ "Linux" ]
} }
} }
},
{
// Don't inherit from "default", as we don't want UHD_DIR etc set
"name": "default-windows",
"binaryDir": "c:/users/jon/source/repos/sdrangel/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"DEBUG_OUTPUT": "ON",
"RX_SAMPLE_24BIT": "ON",
"DARCH_OPT": "SSE4_1",
"HIDE_CONSOLE": "OFF",
"ENABLE_AIRSPY": "ON",
"ENABLE_BLADERF": "OFF",
"ENABLE_HACKRF": "OFF",
"ENABLE_IIO": "OFF",
"ENABLE_MIRISDR": "OFF",
"ENABLE_PERSEUS": "OFF",
"ENABLE_XTRX": "OFF",
"BUILD_SERVER": "OFF",
"CMAKE_PREFIX_PATH": "C:/Qt/5.15.2/msvc2019_64;C:/Applications/boost_1_81_0"
},
"warnings": {
"dev": false
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
"hostOS": [ "Windows" ]
}
}
},
{
"name": "default-qt6",
"inherits": "default",
"binaryDir": "build-qt6",
"cacheVariables": {
"ENABLE_QT6": "ON"
}
}
], ],
"buildPresets": [ "buildPresets": [
{ {
"name": "default", "name": "default",
"configurePreset": "default" "configurePreset": "default"
}, },
{ {
"name": "default-qt6", "name": "default-windows",
"configurePreset": "default-qt6" "configurePreset": "default-windows"
} },
] {
"name": "default-qt6",
"configurePreset": "default-qt6"
}
]
} }

14
debian/changelog vendored
View File

@ -1,3 +1,17 @@
sdrangel (7.15.4-1) unstable; urgency=medium
* Fix Mac compilation. PR #1786
* Add support for plugin presets. PR #1789
* Map feature updates. PR #1778
* Fix RTLSDR E4000 gain and bandwidth settings. Add tuner type to GUI. PR #1790
* Add support for RTLSDRBlog V4 with HF upsampler. PR #1790
* Update RTLSDR driver to include RTLSDRBlog V4 support. PR #1790
* Add rotator az/el and offset to table. PR #1791
* Optmize redrawing of charts in Star Tracker. PR #1791
* Initialise PhaseDiscriminators state, to avoid outputting huge values. Fix #1794. PR #1794
-- Edouard Griffiths, F4EXB <f4exb06@gmail.com> Sat, 02 Sep 2023 19:12:12 +0200
sdrangel (7.15.3-1) unstable; urgency=medium sdrangel (7.15.3-1) unstable; urgency=medium
* Rotator Controller: Add additional gamepad calibration and functionality. PR #1761 * Rotator Controller: Add additional gamepad calibration and functionality. PR #1761

View File

@ -227,6 +227,8 @@ void DSCDemodSink::receiveBit(bool bit)
m_dscDecoder.init(m_phasingPatterns[i].m_offset); m_dscDecoder.init(m_phasingPatterns[i].m_offset);
m_gotSOP = true; m_gotSOP = true;
m_bitCount = 0; m_bitCount = 0;
m_rssiMagSqSum = 0.0;
m_rssiMagSqCount = 0;
break; break;
} }
} }

View File

@ -329,6 +329,8 @@ void NavtexDemodSink::receiveBit(bool bit)
m_gotSOP = true; m_gotSOP = true;
m_bitCount = 0; m_bitCount = 0;
m_sitorBDecoder.init(); m_sitorBDecoder.init();
m_rssiMagSqSum = 0.0;
m_rssiMagSqCount = 0;
} }
else else
{ {

View File

@ -79,7 +79,12 @@ Specifies whether bits are transmitted least-significant-bit first (LSB) or most
<h3>14: Mark/Space Frequency</h3> <h3>14: Mark/Space Frequency</h3>
When unchecked, the mark frequency is the higher frequency, when checked the space frequency is higher. When unchecked, the mark frequency is the higher RF frequency, when checked the space frequency is higher.
This should be unchecked when transmitter is using LSB AFSK and checked for USB AFSK and DWD
[1](https://www.dwd.de/EN/specialusers/shipping/broadcast_en/brodcast_rtty_1_052014.pdf?__blob=publicationFile&v=1)
[2](https://www.dwd.de/EN/specialusers/shipping/broadcast_en/broadcast_rtty_2_052014.pdf?__blob=publicationFile&v=1)
shipping weather broadcasts.
<h3>15: Suppress CR LF</h3> <h3>15: Suppress CR LF</h3>
@ -101,3 +106,4 @@ Click to specify the name of the .txt file which received characters are logged
The received text area shows characters as they are received. The received text area shows characters as they are received.
Holding the cursor over an acronym may show a tooltip with the decoded acronym.

View File

@ -34,6 +34,7 @@
#include "plugin/pluginapi.h" #include "plugin/pluginapi.h"
#include "util/simpleserializer.h" #include "util/simpleserializer.h"
#include "util/db.h" #include "util/db.h"
#include "util/rtty.h"
#include "gui/basicchannelsettingsdialog.h" #include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h" #include "gui/devicestreamselectiondialog.h"
#include "dsp/dspengine.h" #include "dsp/dspengine.h"
@ -482,6 +483,8 @@ RttyDemodGUI::RttyDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
ui->text->addAcronyms(Rtty::m_acronyms);
ui->scopeContainer->setVisible(false); ui->scopeContainer->setVisible(false);
// Hide developer only settings // Hide developer only settings

View File

@ -916,7 +916,7 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QPlainTextEdit" name="text"> <widget class="AcronymView" name="text">
<property name="toolTip"> <property name="toolTip">
<string>Received text</string> <string>Received text</string>
</property> </property>
@ -1293,6 +1293,12 @@
<header>gui/glscopegui.h</header> <header>gui/glscopegui.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>AcronymView</class>
<extends>QTextEdit</extends>
<header>gui/acronymview.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>deltaFrequency</tabstop> <tabstop>deltaFrequency</tabstop>

View File

@ -154,8 +154,8 @@ void RttyDemodSink::processOneSample(Complex &ci)
m_expIdx = (m_expIdx + 1) % m_expLength; m_expIdx = (m_expIdx + 1) % m_expLength;
//Complex exp = m_exp[m_sampleIdx]; //Complex exp = m_exp[m_sampleIdx];
//qDebug() << "IQ " << real(ci) << imag(ci); //qDebug() << "IQ " << real(ci) << imag(ci);
Complex corr1 = ci * exp; Complex corr1 = ci * std::conj(exp); // Conj is high/mark freq (as for matched filter, we need to time reverse and take conjugate)
Complex corr2 = ci * std::conj(exp); Complex corr2 = ci * exp; // Low/space freq
// Filter // Filter
Real abs1, abs2; Real abs1, abs2;
@ -238,6 +238,8 @@ void RttyDemodSink::processOneSample(Complex &ci)
m_clockCount = 0; m_clockCount = 0;
m_clock = false; m_clock = false;
m_cycleCount = 0; m_cycleCount = 0;
m_rssiMagSqSum = 0.0;
m_rssiMagSqCount = 0;
} }
} }
else else

View File

@ -349,7 +349,7 @@ void PacketModSource::applySettings(const PacketModSettings& settings, bool forc
<< " symbolSpan: " << settings.m_symbolSpan << " symbolSpan: " << settings.m_symbolSpan
<< " channelSampleRate:" << m_channelSampleRate << " channelSampleRate:" << m_channelSampleRate
<< " baud:" << settings.m_baud; << " baud:" << settings.m_baud;
m_pulseShape.create(settings.m_beta, m_settings.m_symbolSpan, m_channelSampleRate/settings.m_baud); m_pulseShape.create(settings.m_beta, settings.m_symbolSpan, m_channelSampleRate/settings.m_baud);
} }
if ((settings.m_polynomial != m_settings.m_polynomial) || force) if ((settings.m_polynomial != m_settings.m_polynomial) || force)
m_scrambler.setPolynomial(settings.m_polynomial); m_scrambler.setPolynomial(settings.m_polynomial);

View File

@ -2,7 +2,10 @@
<h2>Introduction</h2> <h2>Introduction</h2>
This plugin can be used to transmit RTTY encoded text. This plugin can be used to modulate RTTY (Radioteletype) encoded text.
RTTY uses BFSK (Binary Frequency Shift Keying), where transmission of data alternates between two frequencies,
the mark frequency and the space frequency. The RTTY Modulator should be centered in between these frequencies.
The baud rate, frequency shift (difference between mark and space frequencies), filter bandwidth and baudot character set are configurable.
<h2>Interface</h2> <h2>Interface</h2>
@ -64,7 +67,14 @@ UDP port number to receive text to be transmitted on.
<h3>13: Baudot Character Set</h3> <h3>13: Baudot Character Set</h3>
Specifies the Baudot character set used to encode the text to transmit. Specifies the Baudot character set used to encode the text to transmit. The following character sets are supported:
* ITA 2
* UK
* European
* US
* Russian
* Murray
<h3>14: Bit Ordering</h3> <h3>14: Bit Ordering</h3>
@ -90,11 +100,26 @@ Press to clear the transmitted text.
Enter text to transmit. Pressing return will transmit the text and clear this field. Press the arrow to display and select a list of pre-defined text or previously transmitted text to enter in to the field. Enter text to transmit. Pressing return will transmit the text and clear this field. Press the arrow to display and select a list of pre-defined text or previously transmitted text to enter in to the field.
The list of pre-defined text can be customised via the Transmit Settings dialog (20).
<h3>20: TX</h3> <h3>20: TX</h3>
Press to transmits the current text. The text field will not be cleared. Press to transmit the current text. The text field will not be cleared.
Right click to open a dialog to adjust additional transmitter settings. Right click to open a dialog to adjust additional Transmit Settings, including the list of pre-defined text.
Predefined text supports the following variable substitutions:
* ${callsign} - Gets replaced with the station name from Preferences > My Position
* ${location} = Gets replaced with the Maidenhead locator for the position specified under Preferences > My Position
The substitutions are applied when the Transmit Settings dialog is closed.
<h3>21: Transmitted Text</h3>
The trasnmitted text area shows characters as they are transmitted.
Holding the cursor over an acronym may show a tooltip with the decoded acronym.
<h2>API</h2> <h2>API</h2>

View File

@ -31,6 +31,7 @@
#include "plugin/pluginapi.h" #include "plugin/pluginapi.h"
#include "util/simpleserializer.h" #include "util/simpleserializer.h"
#include "util/db.h" #include "util/db.h"
#include "util/rtty.h"
#include "util/maidenhead.h" #include "util/maidenhead.h"
#include "gui/glspectrum.h" #include "gui/glspectrum.h"
#include "gui/crightclickenabler.h" #include "gui/crightclickenabler.h"
@ -476,6 +477,8 @@ RttyModGUI::RttyModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandS
m_settings.setChannelMarker(&m_channelMarker); m_settings.setChannelMarker(&m_channelMarker);
m_settings.setRollupState(&m_rollupState); m_settings.setRollupState(&m_rollupState);
ui->transmittedText->addAcronyms(Rtty::m_acronyms);
ui->spectrumContainer->setVisible(false); ui->spectrumContainer->setVisible(false);
displaySettings(); displaySettings();

View File

@ -751,7 +751,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacerSettings">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
@ -854,11 +854,7 @@
<number>3</number> <number>3</number>
</property> </property>
<item> <item>
<widget class="QPlainTextEdit" name="transmittedText"> <widget class="AcronymView" name="transmittedText"/>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -960,6 +956,12 @@
<header>gui/levelmeter.h</header> <header>gui/levelmeter.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>AcronymView</class>
<extends>QTextEdit</extends>
<header>gui/acronymview.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>deltaFrequency</tabstop> <tabstop>deltaFrequency</tabstop>

View File

@ -57,7 +57,7 @@ void RttyModSettings::resetToDefaults()
"UR 599 QTH IS ${location}", "UR 599 QTH IS ${location}",
"TU DE ${callsign} CQ" "TU DE ${callsign} CQ"
}); });
m_rgbColor = QColor(0, 105, 2).rgb(); m_rgbColor = QColor(180, 205, 130).rgb();
m_title = "RTTY Modulator"; m_title = "RTTY Modulator";
m_streamIndex = 0; m_streamIndex = 0;
m_useReverseAPI = false; m_useReverseAPI = false;
@ -66,7 +66,7 @@ void RttyModSettings::resetToDefaults()
m_reverseAPIDeviceIndex = 0; m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0; m_reverseAPIChannelIndex = 0;
m_pulseShaping = false; m_pulseShaping = false;
m_beta = 0.5f; m_beta = 1.0f;
m_symbolSpan = 6; m_symbolSpan = 6;
m_udpEnabled = false; m_udpEnabled = false;
m_udpAddress = "127.0.0.1"; m_udpAddress = "127.0.0.1";
@ -200,7 +200,7 @@ bool RttyModSettings::deserialize(const QByteArray& data)
d.readU32(39, &utmp, 0); d.readU32(39, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readBool(46, &m_pulseShaping, false); d.readBool(46, &m_pulseShaping, false);
d.readReal(47, &m_beta, 0.5f); d.readReal(47, &m_beta, 1.0f);
d.readS32(48, &m_symbolSpan, 6); d.readS32(48, &m_symbolSpan, 6);
d.readBool(51, &m_udpEnabled); d.readBool(51, &m_udpEnabled);
d.readString(52, &m_udpAddress, "127.0.0.1"); d.readString(52, &m_udpAddress, "127.0.0.1");

View File

@ -43,7 +43,7 @@ RttyModSource::RttyModSource() :
{ {
m_bits.append(0); m_bits.append(0);
m_lowpass.create(301, m_channelSampleRate, 400.0 / 2.0); m_lowpass.create(301, m_channelSampleRate, 400.0 / 2.0);
m_pulseShape.create(0.5, 6, m_channelSampleRate/45.45); m_pulseShape.create(0.5, 6, m_channelSampleRate / 45.45, true);
m_demodBuffer.resize(1<<12); m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0; m_demodBufferFill = 0;
@ -121,7 +121,7 @@ void RttyModSource::sampleToSpectrum(Complex sample)
void RttyModSource::modulateSample() void RttyModSource::modulateSample()
{ {
Real audioMod; Real mod;
if (m_sampleIdx == 0) if (m_sampleIdx == 0)
{ {
@ -154,18 +154,18 @@ void RttyModSource::modulateSample()
if (m_settings.m_pulseShaping) if (m_settings.m_pulseShaping)
{ {
if (m_sampleIdx == 1) { if (m_sampleIdx == 1) {
audioMod = m_pulseShape.filter(m_bit ? 1.0f : -1.0f); mod = m_pulseShape.filter(m_bit ? 1.0f : -1.0f);
} else { } else {
audioMod = m_pulseShape.filter(0.0f); mod = m_pulseShape.filter(0.0f);
} }
} }
else else
{ {
audioMod = m_bit ? 1.0f : -1.0f; mod = m_bit ? 1.0f : -1.0f;
} }
// FM // FM
m_fmPhase += m_phaseSensitivity * audioMod * (m_settings.m_spaceHigh ? -1.0f : 1.0f); m_fmPhase += m_phaseSensitivity * mod * (m_settings.m_spaceHigh ? -1.0f : 1.0f);
// Keep phase in range -pi,pi // Keep phase in range -pi,pi
if (m_fmPhase > M_PI) { if (m_fmPhase > M_PI) {
m_fmPhase -= 2.0f * M_PI; m_fmPhase -= 2.0f * M_PI;
@ -194,7 +194,8 @@ void RttyModSource::modulateSample()
Real s = std::real(m_modSample); Real s = std::real(m_modSample);
calculateLevel(s); calculateLevel(s);
m_demodBuffer[m_demodBufferFill] = audioMod * std::numeric_limits<int16_t>::max(); // Send to demod analyser
m_demodBuffer[m_demodBufferFill] = mod * std::numeric_limits<int16_t>::max();
++m_demodBufferFill; ++m_demodBufferFill;
if (m_demodBufferFill >= m_demodBuffer.size()) if (m_demodBufferFill >= m_demodBuffer.size())
@ -258,7 +259,7 @@ void RttyModSource::applySettings(const RttyModSettings& settings, bool force)
<< " symbolSpan: " << settings.m_symbolSpan << " symbolSpan: " << settings.m_symbolSpan
<< " channelSampleRate:" << m_channelSampleRate << " channelSampleRate:" << m_channelSampleRate
<< " baud:" << settings.m_baud; << " baud:" << settings.m_baud;
m_pulseShape.create(settings.m_beta, m_settings.m_symbolSpan, m_channelSampleRate/settings.m_baud); m_pulseShape.create(settings.m_beta, settings.m_symbolSpan, m_channelSampleRate/settings.m_baud, true);
} }
if ((settings.m_characterSet != m_settings.m_characterSet) || force) { if ((settings.m_characterSet != m_settings.m_characterSet) || force) {
@ -302,7 +303,7 @@ void RttyModSource::applyChannelSettings(int channelSampleRate, int channelFrequ
<< " symbolSpan: " << m_settings.m_symbolSpan << " symbolSpan: " << m_settings.m_symbolSpan
<< " channelSampleRate:" << m_channelSampleRate << " channelSampleRate:" << m_channelSampleRate
<< " baud:" << m_settings.m_baud; << " baud:" << m_settings.m_baud;
m_pulseShape.create(m_settings.m_beta, m_settings.m_symbolSpan, channelSampleRate/m_settings.m_baud); m_pulseShape.create(m_settings.m_beta, m_settings.m_symbolSpan, channelSampleRate/m_settings.m_baud, true);
} }
if ((m_channelSampleRate != channelSampleRate) || force) if ((m_channelSampleRate != channelSampleRate) || force)

View File

@ -38,6 +38,7 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = {
QStringLiteral("PacketDemod"), QStringLiteral("PacketDemod"),
QStringLiteral("PacketMod"), QStringLiteral("PacketMod"),
QStringLiteral("RadiosondeDemod"), QStringLiteral("RadiosondeDemod"),
QStringLiteral("RTTYMod"),
QStringLiteral("SSBDemod"), QStringLiteral("SSBDemod"),
QStringLiteral("SSBMod"), QStringLiteral("SSBMod"),
QStringLiteral("WFMDemod"), QStringLiteral("WFMDemod"),
@ -60,6 +61,7 @@ const QStringList DemodAnalyzerSettings::m_channelURIs = {
QStringLiteral("sdrangel.channel.packetdemod"), QStringLiteral("sdrangel.channel.packetdemod"),
QStringLiteral("sdrangel.channeltx.modpacket"), QStringLiteral("sdrangel.channeltx.modpacket"),
QStringLiteral("sdrangel.channel.radiosondedemod"), QStringLiteral("sdrangel.channel.radiosondedemod"),
QStringLiteral("sdrangel.channeltx.modrtty"),
QStringLiteral("sdrangel.channel.ssbdemod"), QStringLiteral("sdrangel.channel.ssbdemod"),
QStringLiteral("sdrangel.channeltx.modssb"), QStringLiteral("sdrangel.channeltx.modssb"),
QStringLiteral("sdrangel.channel.wfmdemod"), QStringLiteral("sdrangel.channel.wfmdemod"),

View File

@ -30,7 +30,7 @@
const PluginDescriptor GS232ControllerPlugin::m_pluginDescriptor = { const PluginDescriptor GS232ControllerPlugin::m_pluginDescriptor = {
GS232Controller::m_featureId, GS232Controller::m_featureId,
QStringLiteral("Rotator Controller"), QStringLiteral("Rotator Controller"),
QStringLiteral("7.15.3"), QStringLiteral("7.15.4"),
QStringLiteral("(c) Jon Beniston, M7RCE"), QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"), QStringLiteral("https://github.com/f4exb/sdrangel"),
true, true,

View File

@ -30,7 +30,7 @@
const PluginDescriptor MapPlugin::m_pluginDescriptor = { const PluginDescriptor MapPlugin::m_pluginDescriptor = {
Map::m_featureId, Map::m_featureId,
QStringLiteral("Map"), QStringLiteral("Map"),
QStringLiteral("7.15.3"), QStringLiteral("7.15.4"),
QStringLiteral("(c) Jon Beniston, M7RCE"), QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"), QStringLiteral("https://github.com/f4exb/sdrangel"),
true, true,

View File

@ -30,7 +30,7 @@
const PluginDescriptor StarTrackerPlugin::m_pluginDescriptor = { const PluginDescriptor StarTrackerPlugin::m_pluginDescriptor = {
StarTracker::m_featureId, StarTracker::m_featureId,
QStringLiteral("Star Tracker"), QStringLiteral("Star Tracker"),
QStringLiteral("7.14.1"), QStringLiteral("7.15.4"),
QStringLiteral("(c) Jon Beniston, M7RCE"), QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"), QStringLiteral("https://github.com/f4exb/sdrangel"),
true, true,

View File

@ -18,7 +18,7 @@
const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = { const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = {
QStringLiteral("RTLSDR"), QStringLiteral("RTLSDR"),
QStringLiteral("RTL-SDR Input"), QStringLiteral("RTL-SDR Input"),
QStringLiteral("7.8.3"), QStringLiteral("7.15.4"),
QStringLiteral("(c) Edouard Griffiths, F4EXB"), QStringLiteral("(c) Edouard Griffiths, F4EXB"),
QStringLiteral("https://github.com/f4exb/sdrangel"), QStringLiteral("https://github.com/f4exb/sdrangel"),
true, true,

View File

@ -259,6 +259,7 @@ set(sdrbase_SOURCES
util/simpleserializer.cpp util/simpleserializer.cpp
util/serialutil.cpp util/serialutil.cpp
#util/spinlock.cpp #util/spinlock.cpp
util/rtty.cpp
util/uid.cpp util/uid.cpp
util/units.cpp util/units.cpp
util/timeutil.cpp util/timeutil.cpp
@ -491,6 +492,7 @@ set(sdrbase_HEADERS
util/profiler.h util/profiler.h
util/radiosonde.h util/radiosonde.h
util/rtpsink.h util/rtpsink.h
util/rtty.h
util/syncmessenger.h util/syncmessenger.h
util/samplesourceserializer.h util/samplesourceserializer.h
util/simpleserializer.h util/simpleserializer.h

View File

@ -89,18 +89,38 @@ public:
else else
{ {
// Calculate maximum output of filter, assuming upsampled bipolar input E.g. [1 0 0 -1 0 0..] // Calculate maximum output of filter, assuming upsampled bipolar input E.g. [1 0 0 -1 0 0..]
// This doesn't necessarily include the centre tap, so we try each offset // This doesn't necessarily include the centre tap, as ISI there should be zero,
double maxGain = 0.0; // it's often at the midpoint between two symbols. However, depending on beta,
for (i = 0; i < samplesPerSymbol; i++) // the input that produces the worst case can vary, so we currently try them all
double maxGain = 0;
for (int input = 0; input < (1 << symbolSpan); input++)
{ {
double g = 0.0; double maxV = 0;
for (j = 0; j < (int)m_taps.size() - 1; j += samplesPerSymbol) for(int i = 0; i < nTaps; i++) {
g += std::fabs(2.0 * m_taps[j]); m_samples[i] = 0;
if ((i & 1) == 0) }
g += std::fabs(m_taps[j]); for (int i = 0; i < symbolSpan; i++)
if (g > maxGain) {
maxGain = g; Type sym = (input >> i) & 1 ? 1 : -1;
for (int j = 0; j < samplesPerSymbol; j++)
{
Type out;
if (j == 1) {
out = filter(sym);
} else {
out = filter(0);
}
double outAbs = abs(out);
if (outAbs > maxV) {
maxV = outAbs;
}
}
}
if (maxV > maxGain) {
maxGain = maxV;
}
} }
// Scale up so maximum out is 1 // Scale up so maximum out is 1
for(i = 0; i < (int)m_taps.size(); i++) for(i = 0; i < (int)m_taps.size(); i++)
m_taps[i] /= maxGain; m_taps[i] /= maxGain;

View File

@ -81,8 +81,8 @@ const QStringList Baudot::m_usFigure = {
const QStringList Baudot::m_russianLetter = { const QStringList Baudot::m_russianLetter = {
"\0", "Е", "\n", "А", " ", "С", "И", "У", "\0", "Е", "\n", "А", " ", "С", "И", "У",
"\r", "Д", "П", "Й", "Н", "Ф", "Ц", "К", "\r", "Д", "Р", "Й", "Ч", "Ф", "Ц", "К",
"Т", "З", "Л", "В", "Х", "Ы", "P", "Я", "Т", "З", "Л", "В", "Х", "Ы", "П", "Я",
"О", "Б", "Г", "<", "М", "Ь", "Ж", ">" "О", "Б", "Г", "<", "М", "Ь", "Ж", ">"
}; };
@ -206,36 +206,37 @@ void BaudotEncoder::setCharacterSet(Baudot::CharacterSet characterSet)
switch (m_characterSet) switch (m_characterSet)
{ {
case Baudot::ITA2: case Baudot::ITA2:
m_letters = Baudot::m_ita2Letter; m_chars[LETTERS] = Baudot::m_ita2Letter;
m_figures = Baudot::m_ita2Figure; m_chars[FIGURES] = Baudot::m_ita2Figure;
break; break;
case Baudot::UK: case Baudot::UK:
m_letters = Baudot::m_ukLetter; m_chars[LETTERS] = Baudot::m_ukLetter;
m_figures = Baudot::m_ukFigure; m_chars[FIGURES] = Baudot::m_ukFigure;
break; break;
case Baudot::EUROPEAN: case Baudot::EUROPEAN:
m_letters = Baudot::m_europeanLetter; m_chars[LETTERS] = Baudot::m_europeanLetter;
m_figures = Baudot::m_europeanFigure; m_chars[FIGURES] = Baudot::m_europeanFigure;
break; break;
case Baudot::US: case Baudot::US:
m_letters = Baudot::m_usLetter; m_chars[LETTERS] = Baudot::m_usLetter;
m_figures = Baudot::m_usFigure; m_chars[FIGURES] = Baudot::m_usFigure;
break; break;
case Baudot::RUSSIAN: case Baudot::RUSSIAN:
m_letters = Baudot::m_russianLetter; m_chars[LETTERS] = Baudot::m_ita2Letter;
m_figures = Baudot::m_russianFigure; m_chars[FIGURES] = Baudot::m_russianFigure;
break; break;
case Baudot::MURRAY: case Baudot::MURRAY:
m_letters = Baudot::m_murrayLetter; m_chars[LETTERS] = Baudot::m_murrayLetter;
m_figures = Baudot::m_murrayFigure; m_chars[FIGURES] = Baudot::m_murrayFigure;
break; break;
default: default:
qDebug() << "BaudotEncoder::BaudotEncoder: Unsupported character set " << m_characterSet; qDebug() << "BaudotEncoder::BaudotEncoder: Unsupported character set " << m_characterSet;
m_letters = Baudot::m_ita2Letter; m_chars[LETTERS] = Baudot::m_ita2Letter;
m_figures = Baudot::m_ita2Figure; m_chars[FIGURES] = Baudot::m_ita2Figure;
m_characterSet = Baudot::ITA2; m_characterSet = Baudot::ITA2;
break; break;
} }
m_chars[(int)CYRILLIC] = Baudot::m_russianLetter;
} }
void BaudotEncoder::setUnshiftOnSpace(bool unshiftOnSpace) void BaudotEncoder::setUnshiftOnSpace(bool unshiftOnSpace)
@ -262,14 +263,11 @@ void BaudotEncoder::setStopBits(int stopBits)
void BaudotEncoder::init() void BaudotEncoder::init()
{ {
m_figure = false; m_page = LETTERS;
} }
bool BaudotEncoder::encode(QChar c, unsigned &bits, unsigned int &bitCount) bool BaudotEncoder::encode(QChar c, unsigned &bits, unsigned int &bitCount)
{ {
unsigned int code;
const unsigned int codeLen = 5;
bits = 0; bits = 0;
bitCount = 0; bitCount = 0;
@ -277,50 +275,63 @@ bool BaudotEncoder::encode(QChar c, unsigned &bits, unsigned int &bitCount)
c = c.toUpper(); c = c.toUpper();
QString s(c); QString s(c);
// We could create reverse look-up tables to speed this up, but it's only 200 baud... if (s == '>')
if (m_letters.contains(s))
{ {
if (m_figure) addCode(bits, bitCount, m_chars[m_page].indexOf(s));
{ m_page = LETTERS;
// Switch to letters
addStartBits(bits, bitCount);
code = reverseBits(m_letters.indexOf(">"), codeLen);
addBits(bits, bitCount, code, codeLen);
addStopBits(bits, bitCount);
m_figure = false;
}
addStartBits(bits, bitCount);
code = reverseBits(m_letters.indexOf(s), codeLen);
addBits(bits, bitCount, code, codeLen);
addStopBits(bits, bitCount);
return true; return true;
} }
else if (m_figures.contains(s)) else if (s == '<')
{ {
if (!m_figure) addCode(bits, bitCount, m_chars[m_page].indexOf(s));
{ m_page = FIGURES;
// Switch to figures return true;
addStartBits(bits, bitCount); }
code = reverseBits(m_letters.indexOf("<"), codeLen); else if ((m_characterSet == Baudot::RUSSIAN) && (s == '\0'))
addBits(bits, bitCount, code, codeLen); {
addStopBits(bits, bitCount); addCode(bits, bitCount, m_chars[m_page].indexOf(s));
m_figure = true; m_page = CYRILLIC;
} return true;
addStartBits(bits, bitCount); }
code = reverseBits(m_figures.indexOf(s), codeLen);
addBits(bits, bitCount, code, codeLen); // We could create reverse look-up tables to speed this up, but it's only 200 baud...
addStopBits(bits, bitCount);
if ((s == " ") && m_unshiftOnSpace) { // Is character in current page? If so, use that, as it avoids switching
m_figure = false; if (m_chars[m_page].contains(s))
} {
addCode(bits, bitCount, m_chars[m_page].indexOf(s));
return true;
} }
else else
{ {
qDebug() << "BaudotEncoder::encode: Can't encode" << c; // Look for character in other pages
return false; const QString switchPage[] = { ">", "<", "\0" };
for (int page = m_page == LETTERS ? 1 : 0; page < (m_characterSet == Baudot::RUSSIAN) ? 3 : 2; page++)
{
if (m_chars[page].contains(s))
{
// Switch to page
addCode(bits, bitCount, m_chars[m_page].indexOf(switchPage[page]));
m_page = (BaudotEncoder::Page)page;
addCode(bits, bitCount, m_chars[m_page].indexOf(s));
return true;
}
}
} }
return true; return false;
}
void BaudotEncoder::addCode(unsigned& bits, unsigned int& bitCount, unsigned int code) const
{
const unsigned int codeLen = 5;
addStartBits(bits, bitCount);
code = reverseBits(code, codeLen);
addBits(bits, bitCount, code, codeLen);
addStopBits(bits, bitCount);
} }
void BaudotEncoder::addStartBits(unsigned& bits, unsigned int& bitCount) const void BaudotEncoder::addStartBits(unsigned& bits, unsigned int& bitCount) const

View File

@ -88,6 +88,7 @@ public:
private: private:
void addCode(unsigned& bits, unsigned int& bitCount, unsigned int code) const;
void addStartBits(unsigned int& bits, unsigned int& bitCount) const; void addStartBits(unsigned int& bits, unsigned int& bitCount) const;
void addStopBits(unsigned int& bits, unsigned int& bitCount) const; void addStopBits(unsigned int& bits, unsigned int& bitCount) const;
void addBits(unsigned int& bits, unsigned int& bitCount, int data, int count) const; void addBits(unsigned int& bits, unsigned int& bitCount, int data, int count) const;
@ -96,9 +97,12 @@ private:
Baudot::CharacterSet m_characterSet; Baudot::CharacterSet m_characterSet;
bool m_unshiftOnSpace; bool m_unshiftOnSpace;
QStringList m_letters; QStringList m_chars[3];
QStringList m_figures; enum Page {
bool m_figure; LETTERS,
FIGURES,
CYRILLIC
} m_page;
bool m_msbFirst; bool m_msbFirst;
int m_startBits; int m_startBits;
int m_stopBits; int m_stopBits;

290
sdrbase/util/rtty.cpp Normal file
View File

@ -0,0 +1,290 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// 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 "util/rtty.h"
// From http://www.ct2fzi.net/abreviations/abreviations.html
const QHash<QString, QString> Rtty::m_acronyms = {
{"AA", "After All"},
{"AB", "All Before"},
{"ABT", "About"},
{"ACK", "Acknowledgement"},
{"ADEE", "Addressee"},
{"ADR", "Address"},
{"AF", "Audio Frequency"},
{"AGN", "Again"},
{"AM", "Amplitude Modulation"},
{"AMU", "Antenna Matching Unit"},
{"ANS", "Answer"},
{"ANT", "Antenna"},
{"ARQ", "Automatic Repeat Request"},
{"ATU", "Antenna Tuning Unit"},
{"B4", "Before"},
{"BCN", "Beacon"},
{"BCNU", "Be Seeing You"},
{"BD", "Bad"},
{"BK", "Break"},
{"BN", "Been"},
{"BTH", "Both"},
{"BTR", "Better"},
{"BTW", "By The Way"},
{"BTU", "Back To You"},
{"C", "Correct"},
{"CBA", "Callbook Address"},
{"CFM", "Confirm"},
{"CK", "Check"},
{"CKT", "Circuit"},
{"CL", "Closing Down"},
{"CLBK", "Callbook"},
{"CLD", "Called"},
{"CLG", "Calling"},
{"CMG", "Coming"},
{"CNT", "Can't"},
{"COMP", "Computer"},
{"CONDX", "Conditions"},
{"COZ", "Because"},
{"CPI", "Copy"},
{"CQ", "General Call"},
{"CRD", "Card"},
{"CS", "Callsign"},
{"CTCSS", "Continuous Tone Coded Squelch System"},
{"CU", "See You"},
{"CUAGN", "See You Again"},
{"CUD", "Could"},
{"CUL", "See You Later"},
{"CUM", "Come"},
{"CUZ", "Because"},
{"CW", "Continuous Wave / Morse"},
{"DA", "Day"},
{"DE", "From"},
{"DF", "Direction Finding"},
{"DIFF", "Difference"},
{"DLD", "Delivered"},
{"DLVD", "Delivered"},
{"DN", "Down"},
{"DR", "Dear"},
{"DSB", "Double Side Band"},
{"DSP", "Digital Signal Processing"},
{"DSW", "Goodbye (Russian)"},
{"DWN", "Down"},
{"DX", "Distance"},
{"EL", "Element"},
{"EME", "Earth-Moon-Earth"},
{"ENUF", "Enough"},
{"ES", "And"},
{"EU", "Europe"},
{"EVE", "Evening"},
{"FB", "Fine Business"},
{"FER", "For"},
{"FIO", "For Information Only"},
{"FM", "Frequency Modulation"},
{"FQ", "Frequency"},
{"FREQ", "Frequency"},
{"FSD", "Full Scale Deflection"},
{"FSK", "Frequency Shift Keying"},
{"FWD", "Forward"},
{"FWIW", "For What It's Worth"},
{"FYI", "For Your Information"},
{"GA", "Good Afternoon"},
{"GB", "Good Bye"},
{"GD", "Good Day"},
{"GE", "Good Evening"},
{"GESS", "Guess"},
{"GG", "Going"},
{"GLD", "Glad"},
{"GM", "Good Morning"},
{"GMT", "Greenwich Mean Time"},
{"GN", "Good Night"},
{"GND", "Ground"},
{"GP", "Ground Plane"},
{"GPS", "Global Positioning System"},
{"GS", "Green Stamp"},
{"GUD", "Good"},
{"GV", "Give"},
{"GVG", "Giving"},
{"HAGD", "Have A Good Day"},
{"HAGWE", "Have A Good Weekend"},
{"HF", "High Frequency"},
{"HI", "High"},
{"HPE", "Hope"},
{"HQ", "Headquarters"},
{"HR", "Here / Hour"},
{"HRD", "Heard"},
{"HV", "Have"},
{"HVG", "Having"},
{"HVY", "Heavy"},
{"HW", "How"},
{"IMHO", "In My Humble Opinion"},
{"IMI", "Say again"},
{"K", "Over"},
{"KN", "Over"},
{"LF", "Low Frequency"},
{"LNG", "Long"},
{"LP", "Long Path"},
{"LSB", "Lower Sideband"},
{"LSN", "Listen"},
{"LTR", "Later"},
{"LV", "Leave"},
{"LVG", "Leaving"},
{"LW", "Long Wire"},
{"MGR", "Manager"},
{"MI", "My"},
{"MNI", "Many"},
{"MOM", "Moment"},
{"MS", "Meteor Scatter"},
{"MSG", "Message"},
{"N", "No"},
{"NCS", "Net Control Station"},
{"ND", "Nothing Doing"},
{"NM", "No More"},
{"NR", "Near / Number"},
{"NW", "Now"},
{"OB", "Old Boy"},
{"OC", "Old Chap"},
{"OM", "Old Man"},
{"OP", "Operator"},
{"OPR", "Operator"},
{"OT", "Old Timer"},
{"OW", "Old Woman"},
{"PA", "Power Amplifier"},
{"PBL", "Preamble"},
{"PKG", "Package"},
{"POV", "Point Of View"},
{"PSE", "Please"},
{"PSK", "Phase Shift Keying"},
{"PT", "Point"},
{"PTT", "Push To Talk"},
{"PWR", "Power"},
{"PX", "Prefix"},
{"QRA", "Address"},
{"QRG", "Frequency"},
{"QRK", "Readability"},
{"QRL", "Busy"},
{"QRM", "Interference"},
{"QRN", "Noise"},
{"QRO", "High Power"},
{"QRP", "Low Power"},
{"QRQ", "Send Faster"},
{"QRS", "Send Slower"},
{"QRSS", "Send Very Slowly"},
{"QRT", "Stop Sending"},
{"QRU", "Nothing Further To Say"},
{"QRV", "Ready"},
{"QRX", "Wait"},
{"QRZ", "Who Is Calling Me"},
{"QSA", "Signal Strength"},
{"QSB", "Fading"},
{"QSK", "Break-in"},
{"QSL", "All Received OK"},
{"QSLL", "I Will Send A QSL Card"},
{"QSO", "Contact"},
{"QSP", "Relay A Message"},
{"QSX", "Listening On Frequency"},
{"QSY", "Change Frequency"},
{"QTH", "Location"},
{"R", "Received OK"},
{"RC", "Ragchew"},
{"RCD", "Recieved"},
{"RCVR", "Receiver"},
{"RE", "Regarding"},
{"REF", "Reference"},
{"RF", "Radio Frequency"},
{"RFI", "Radio Frequency Interference"},
{"RPT", "Repeat / Report"},
{"RST", "Signal Report"},
{"RTTY", "Radio Teletype"},
{"RX", "Receive"},
{"SA", "Say"},
{"SDR", "Software Defined Radio"},
{"SEZ", "Says"},
{"SGD", "Signed"},
{"SHUD", "Should"},
{"SIG", "Signal"},
{"SK", "End Of Work"},
{"SKED", "Schedule"},
{"SN", "Soon"},
{"SP", "Short Path"},
{"SRI", "Sorry"},
{"SSB", "Single Sideband"},
{"STN", "Station"},
{"SUM", "Some"},
{"SVC", "Service"},
{"SWR", "Standing Wave Ratio"},
{"TFC", "Traffic"},
{"TIA", "Thanks In Advance"},
{"TKS", "Thanks"},
{"TMW", "Tomorrow"},
{"TNC", "Terminal Node Controller"},
{"TNX", "Thanks"},
{"TR", "Transmit"},
{"T/R", "Transmit/Receive"},
{"TRBL", "Trouble"},
{"TRF", "Tuned Radio Frequency"},
{"TRIX", "Tricks"},
{"TRX", "Transceiver"},
{"TT", "That"},
{"TTS", "That Is"},
{"TU", "Thank You"},
{"TVI", "Television Interference"},
{"TX", "Transmit"},
{"TXT", "Text"},
{"U", "You"},
{"UHF", "Ultra High Frequency"},
{"UNLIS", "Unlicensed"},
{"UR", "Your"},
{"URS", "Yours"},
{"UTC", "Coordinated Universal Time"},
{"V", "Volts"},
{"VHF", "Very High Frequency"},
{"VE", "Understood"},
{"VERT", "Vertical"},
{"VFB", "Very Fine Business"},
{"VFO", "Variable Frequency Oscillator"},
{"VLF", "Very Low Frequency"},
{"VOX", "Voice Operated"},
{"VSB", "Vestigial Sideband"},
{"VSWR", "Voltage Standing Wave Ratio"},
{"VY", "Very"},
{"W", "Watts"},
{"WA", "Word After"},
{"WAT", "What"},
{"WATSA", "What Say"},
{"WB", "Word Before"},
{"WD", "Word"},
{"WDS", "Words"},
{"WID", "With"},
{"WKD", "Worked"},
{"WKG", "Working"},
{"WL", "Will"},
{"WPM", "Words Per Minute"},
{"WRD", "Word"},
{"WRK", "Work"},
{"WUD", "Would"},
{"WX", "Weather"},
{"XCVR", "Transceiver"},
{"XMTR", "Transmitter"},
{"XTAL", "Crystal"},
{"YF", "Wife"},
{"YL", "Young Lady"},
{"YR", "Year"},
{"Z", "Zulu Time"},
{"30", "I Have Nothing More to Send"},
{"33", "Fondest Regards"},
{"55", "Best Success"},
{"73", "Best Wishes"},
{"88", "Love And Kisses"},
};

31
sdrbase/util/rtty.h Normal file
View File

@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// 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_UTIL_RTTY_H
#define INCLUDE_UTIL_RTTY_H
#include <QHash>
#include "export.h"
class SDRBASE_API Rtty
{
public:
static const QHash<QString, QString> m_acronyms;
};
#endif /* INCLUDE_UTIL_RTTY_H */

View File

@ -7,6 +7,7 @@ set(CMAKE_AUTOUIC OFF)
set(sdrgui_SOURCES set(sdrgui_SOURCES
mainwindow.cpp mainwindow.cpp
gui/aboutdialog.cpp gui/aboutdialog.cpp
gui/acronymview.cpp
gui/addpresetdialog.cpp gui/addpresetdialog.cpp
gui/audiodialog.cpp gui/audiodialog.cpp
gui/audioselectdialog.cpp gui/audioselectdialog.cpp
@ -127,6 +128,7 @@ set(sdrgui_HEADERS
gui/aboutdialog.h gui/aboutdialog.h
gui/accessiblevaluedial.h gui/accessiblevaluedial.h
gui/accessiblevaluedialz.h gui/accessiblevaluedialz.h
gui/acronymview.h
gui/addpresetdialog.h gui/addpresetdialog.h
gui/audiodialog.h gui/audiodialog.h
gui/audioselectdialog.h gui/audioselectdialog.h

View File

@ -0,0 +1,68 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// 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 <QEvent>
#include <QHelpEvent>
#include <QToolTip>
#include <QDebug>
#include "acronymview.h"
AcronymView::AcronymView(QWidget* parent) :
QPlainTextEdit(parent)
{
setMouseTracking(true);
setReadOnly(true);
}
bool AcronymView::event(QEvent* event)
{
if (event->type() == QEvent::ToolTip)
{
QHelpEvent* helpEvent = static_cast<QHelpEvent*>(event);
QTextCursor cursor = cursorForPosition(helpEvent->pos());
cursor.select(QTextCursor::WordUnderCursor);
QString text = cursor.selectedText();
// Remove trailing digits from METAR
while (text.size() > 0 && text.right(1)[0].isDigit()) {
text = text.left(text.size() - 1);
}
if (!text.isEmpty() && m_acronym.contains(text))
{
QToolTip::showText(helpEvent->globalPos(), QString("%1 - %2").arg(text).arg(m_acronym.value(text)));
}
else
{
if (!text.isEmpty()) {
qDebug() << "AcronymView::event: No tooltip for " << text;
}
QToolTip::hideText();
}
return true;
}
return QPlainTextEdit::event(event);
}
void AcronymView::addAcronym(const QString& acronym, const QString& explanation)
{
m_acronym.insert(acronym, explanation);
}
void AcronymView::addAcronyms(const QHash<QString, QString>& acronyms)
{
m_acronym.insert(acronyms);
}

41
sdrgui/gui/acronymview.h Normal file
View File

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Jon Beniston, M7RCE //
// //
// 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_GUI_ACRONYMVIEW_H
#define INCLUDE_GUI_ACRONYMVIEW_H
#include <QHash>
#include <QPlainTextEdit>
#include "export.h"
// Displays text like a QPlainTextEdit, but adds tooltips for acronyms in the text
class SDRGUI_API AcronymView : public QPlainTextEdit {
Q_OBJECT
QHash<QString, QString> m_acronym;
public:
AcronymView(QWidget* parent = nullptr);
bool event(QEvent* event);
void addAcronym(const QString& acronym, const QString& explanation);
void addAcronyms(const QHash<QString, QString>& acronyms);
};
#endif // INCLUDE_GUI_ACRONYMVIEW_H