Merge branch 'release-2.1.0' into develop

This commit is contained in:
Bill Somerville 2019-07-02 00:18:22 +01:00
commit 1968597783
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
168 changed files with 17489 additions and 3446 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
.gitattributes export-ignore
/samples export-ignore
/lib/fsk4hf export-ignore
/lib/fsk4hf export-ignore
/robots export-ignore

View File

@ -0,0 +1,397 @@
#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
#include <memory>
#include <locale.h>
#include <QCoreApplication>
#include <QTextStream>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QStringList>
#include <QFileInfo>
#include <QAudioFormat>
#include <QAudioDeviceInfo>
#include <QAudioInput>
#include <QAudioOutput>
#include <QTimer>
#include <QDateTime>
#include <QDebug>
#include "revision_utils.hpp"
#include "Audio/BWFFile.hpp"
namespace
{
QTextStream qtout {stdout};
}
class Record final
: public QObject
{
Q_OBJECT;
public:
Record (int start, int duration, QAudioDeviceInfo const& source_device, BWFFile * output, int notify_interval, int buffer_size)
: source_ {source_device, output->format ()}
, notify_interval_ {notify_interval}
, output_ {output}
, duration_ {duration}
{
if (buffer_size) source_.setBufferSize (output_->format ().bytesForFrames (buffer_size));
if (notify_interval_)
{
source_.setNotifyInterval (notify_interval);
connect (&source_, &QAudioInput::notify, this, &Record::notify);
}
if (start == -1)
{
start_recording ();
}
else
{
auto now = QDateTime::currentDateTimeUtc ();
auto time = now.time ();
auto then = now;
then.setTime (QTime {time.hour (), time.minute (), start});
auto delta_ms = (now.msecsTo (then) + (60 * 1000)) % (60 * 1000);
QTimer::singleShot (int (delta_ms), Qt::PreciseTimer, this, &Record::start_recording);
}
}
Q_SIGNAL void done ();
private:
Q_SLOT void start_recording ()
{
qtout << "started recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
source_.start (output_);
if (!notify_interval_) QTimer::singleShot (duration_ * 1000, Qt::PreciseTimer, this, &Record::stop_recording);
qtout << QString {"buffer size used is: %1"}.arg (source_.bufferSize ()) << endl;
}
Q_SLOT void notify ()
{
auto length = source_.elapsedUSecs ();
qtout << QString {"%1 μs recorded\r"}.arg (length) << flush;
if (length >= duration_ * 1000 * 1000) stop_recording ();
}
Q_SLOT void stop_recording ()
{
auto length = source_.elapsedUSecs ();
source_.stop ();
qtout << QString {"%1 μs recorded "}.arg (length) << '(' << source_.format ().framesForBytes (output_->size ()) << " frames recorded)\n";
qtout << "stopped recording at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
Q_EMIT done ();
}
QAudioInput source_;
int notify_interval_;
BWFFile * output_;
int duration_;
};
class Playback final
: public QObject
{
Q_OBJECT;
public:
Playback (int start, BWFFile * input, QAudioDeviceInfo const& sink_device, int notify_interval, int buffer_size, QString const& category)
: input_ {input}
, sink_ {sink_device, input->format ()}
, notify_interval_ {notify_interval}
{
if (buffer_size) sink_.setBufferSize (input_->format ().bytesForFrames (buffer_size));
if (category.size ()) sink_.setCategory (category);
if (notify_interval_)
{
sink_.setNotifyInterval (notify_interval);
connect (&sink_, &QAudioOutput::notify, this, &Playback::notify);
}
connect (&sink_, &QAudioOutput::stateChanged, this, &Playback::sink_state_changed);
if (start == -1)
{
start_playback ();
}
else
{
auto now = QDateTime::currentDateTimeUtc ();
auto time = now.time ();
auto then = now;
then.setTime (QTime {time.hour (), time.minute (), start});
auto delta_ms = (now.msecsTo (then) + (60 * 1000)) % (60 * 1000);
QTimer::singleShot (int (delta_ms), Qt::PreciseTimer, this, &Playback::start_playback);
}
}
Q_SIGNAL void done ();
private:
Q_SLOT void start_playback ()
{
qtout << "started playback at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
sink_.start (input_);
qtout << QString {"buffer size used is: %1 (%2 frames)"}.arg (sink_.bufferSize ()).arg (sink_.format ().framesForBytes (sink_.bufferSize ())) << endl;
}
Q_SLOT void notify ()
{
auto length = sink_.elapsedUSecs ();
qtout << QString {"%1 μs rendered\r"}.arg (length) << flush;
}
Q_SLOT void sink_state_changed (QAudio::State state)
{
switch (state)
{
case QAudio::ActiveState:
qtout << "\naudio output state changed to active\n";
break;
case QAudio::SuspendedState:
qtout << "\naudio output state changed to suspended\n";
break;
case QAudio::StoppedState:
qtout << "\naudio output state changed to stopped\n";
break;
case QAudio::IdleState:
stop_playback ();
qtout << "\naudio output state changed to idle\n";
break;
#if QT_VERSION >= QT_VERSION_CHECK (5, 10, 0)
case QAudio::InterruptedState:
qtout << "\naudio output state changed to interrupted\n";
break;
#endif
}
}
Q_SLOT void stop_playback ()
{
auto length = sink_.elapsedUSecs ();
sink_.stop ();
qtout << QString {"%1 μs rendered "}.arg (length) << '(' << sink_.format ().framesForBytes (input_->size ()) << " frames rendered)\n";
qtout << "stopped playback at " << QDateTime::currentDateTimeUtc ().toString ("hh:mm:ss.zzz UTC") << endl;
Q_EMIT done ();
}
BWFFile * input_;
QAudioOutput sink_;
int notify_interval_;
};
#include "record_time_signal.moc"
int main(int argc, char *argv[])
{
QCoreApplication app {argc, argv};
try
{
::setlocale (LC_NUMERIC, "C"); // ensure number forms are in
// consistent format, do this
// after instantiating
// QApplication so that Qt has
// correct l18n
// Override programs executable basename as application name.
app.setApplicationName ("WSJT-X Record Time Signal");
app.setApplicationVersion (version ());
QCommandLineParser parser;
parser.setApplicationDescription (
"\nTool to determine and experiment with QAudioInput latencies\n\n"
"\tUse the -I option to list available recording device numbers\n"
);
auto help_option = parser.addHelpOption ();
auto version_option = parser.addVersionOption ();
parser.addOptions ({
{{"I", "list-audio-inputs"},
app.translate ("main", "List the available audio input devices")},
{{"O", "list-audio-outputs"},
app.translate ("main", "List the available audio output devices")},
{{"s", "start-time"},
app.translate ("main", "Record from <start-time> seconds, default start immediately"),
app.translate ("main", "start-time")},
{{"d", "duration"},
app.translate ("main", "Recording <duration> seconds"),
app.translate ("main", "duration")},
{{"o", "output"},
app.translate ("main", "Save output as <output-file>"),
app.translate ("main", "output-file")},
{{"i", "input"},
app.translate ("main", "Playback <input-file>"),
app.translate ("main", "input-file")},
{{"f", "force"},
app.translate ("main", "Overwrite existing file")},
{{"r", "sample-rate"},
app.translate ("main", "Record at <sample-rate>, default 48000 Hz"),
app.translate ("main", "sample-rate")},
{{"c", "num-channels"},
app.translate ("main", "Record <num> channels, default 2"),
app.translate ("main", "num")},
{{"R", "recording-device-number"},
app.translate ("main", "Record from <device-number>"),
app.translate ("main", "device-number")},
{{"P", "playback-device-number"},
app.translate ("main", "Playback to <device-number>"),
app.translate ("main", "device-number")},
{{"C", "category"},
app.translate ("main", "Playback <category-name>"),
app.translate ("main", "category-name")},
{{"n", "notify-interval"},
app.translate ("main", "use notify signals every <interval> milliseconds, zero to use a timer"),
app.translate ("main", "interval")},
{{"b", "buffer-size"},
app.translate ("main", "audio buffer size <frames>"),
app.translate ("main", "frames")},
});
parser.process (app);
auto input_devices = QAudioDeviceInfo::availableDevices (QAudio::AudioInput);
if (parser.isSet ("I"))
{
int n {0};
for (auto const& device : input_devices)
{
qtout << ++n << " - [" << device.deviceName () << ']' << endl;
}
return 0;
}
auto output_devices = QAudioDeviceInfo::availableDevices (QAudio::AudioOutput);
if (parser.isSet ("O"))
{
int n {0};
for (auto const& device : output_devices)
{
qtout << ++n << " - [" << device.deviceName () << ']' << endl;
}
return 0;
}
bool ok;
int start {-1};
if (parser.isSet ("s"))
{
start = parser.value ("s").toInt (&ok);
if (!ok) throw std::invalid_argument {"start time not a number"};
if (0 > start || start > 59) throw std::invalid_argument {"0 > start > 59"};
}
int sample_rate {48000};
if (parser.isSet ("r"))
{
sample_rate = parser.value ("r").toInt (&ok);
if (!ok) throw std::invalid_argument {"sample rate not a number"};
}
int num_channels {2};
if (parser.isSet ("c"))
{
num_channels = parser.value ("c").toInt (&ok);
if (!ok) throw std::invalid_argument {"channel count not a number"};
}
int notify_interval {0};
if (parser.isSet ("n"))
{
notify_interval = parser.value ("n").toInt (&ok);
if (!ok) throw std::invalid_argument {"notify interval not a number"};
}
int buffer_size {0};
if (parser.isSet ("b"))
{
buffer_size = parser.value ("b").toInt (&ok);
if (!ok) throw std::invalid_argument {"buffer size not a number"};
}
int input_device {0};
if (parser.isSet ("R"))
{
input_device = parser.value ("R").toInt (&ok);
if (!ok || 0 >= input_device || input_device > input_devices.size ())
{
throw std::invalid_argument {"invalid recording device"};
}
}
int output_device {0};
if (parser.isSet ("P"))
{
output_device = parser.value ("P").toInt (&ok);
if (!ok || 0 >= output_device || output_device > output_devices.size ())
{
throw std::invalid_argument {"invalid playback device"};
}
}
if (!(parser.isSet ("o") || parser.isSet ("i"))) throw std::invalid_argument {"file required"};
if (parser.isSet ("o") && parser.isSet ("i")) throw std::invalid_argument {"specify either input or output"};
QAudioFormat audio_format;
if (parser.isSet ("o")) // Record
{
int duration = parser.value ("d").toInt (&ok);
if (!ok) throw std::invalid_argument {"duration not a number"};
QFileInfo ofi {parser.value ("o")};
if (!ofi.suffix ().size () && ofi.fileName ()[ofi.fileName ().size () - 1] != QChar {'.'})
{
ofi.setFile (ofi.filePath () + ".wav");
}
if (!parser.isSet ("f") && ofi.isFile ())
{
throw std::invalid_argument {"set the `-force' option to overwrite an existing output file"};
}
audio_format.setSampleRate (sample_rate);
audio_format.setChannelCount (num_channels);
audio_format.setSampleSize (16);
audio_format.setSampleType (QAudioFormat::SignedInt);
audio_format.setCodec ("audio/pcm");
auto source = input_device ? input_devices[input_device - 1] : QAudioDeviceInfo::defaultInputDevice ();
if (!source.isFormatSupported (audio_format))
{
qtout << "warning, requested format not supported, using nearest" << endl;
audio_format = source.nearestFormat (audio_format);
}
BWFFile output_file {audio_format, ofi.filePath ()};
if (!output_file.open (BWFFile::WriteOnly)) throw std::invalid_argument {QString {"cannot open output file \"%1\""}.arg (ofi.filePath ()).toStdString ()};
// run the application
Record record {start, duration, source, &output_file, notify_interval, buffer_size};
QObject::connect (&record, &Record::done, &app, &QCoreApplication::quit);
return app.exec();
}
else // Playback
{
QFileInfo ifi {parser.value ("i")};
if (!ifi.isFile () && !ifi.suffix ().size () && ifi.fileName ()[ifi.fileName ().size () - 1] != QChar {'.'})
{
ifi.setFile (ifi.filePath () + ".wav");
}
BWFFile input_file {audio_format, ifi.filePath ()};
if (!input_file.open (BWFFile::ReadOnly)) throw std::invalid_argument {QString {"cannot open input file \"%1\""}.arg (ifi.filePath ()).toStdString ()};
auto sink = output_device ? output_devices[output_device - 1] : QAudioDeviceInfo::defaultOutputDevice ();
if (!sink.isFormatSupported (input_file.format ()))
{
throw std::invalid_argument {"audio output device does not support input file audio format"};
}
// run the application
Playback play {start, &input_file, sink, notify_interval, buffer_size, parser.value ("category")};
QObject::connect (&play, &Playback::done, &app, &QCoreApplication::quit);
return app.exec();
}
}
catch (std::exception const& e)
{
std::cerr << "Error: " << e.what () << '\n';
}
catch (...)
{
std::cerr << "Unexpected fatal error\n";
throw; // hoping the runtime might tell us more about the exception
}
return -1;
}

View File

@ -19,9 +19,12 @@ find_path (__hamlib_pc_path NAMES hamlib.pc
PATH_SUFFIXES lib/pkgconfig lib64/pkgconfig
)
if (__hamlib_pc_path)
set (ENV{PKG_CONFIG_PATH} "${__hamlib_pc_path}" "$ENV{PKG_CONFIG_PATH}")
unset (__hamlib_pc_path CACHE)
set (__pc_path $ENV{PKG_CONFIG_PATH})
list (APPEND __pc_path "${__hamlib_pc_path}")
set (ENV{PKG_CONFIG_PATH} "${__pc_path}")
unset (__pc_path CACHE)
endif ()
unset (__hamlib_pc_path CACHE)
# Use pkg-config to get hints about paths, libs and, flags
unset (__pkg_config_checked_hamlib CACHE)

View File

@ -39,6 +39,10 @@ if (POLICY CMP0063)
cmake_policy (SET CMP0063 NEW) # honour visibility properties for all library types
endif (POLICY CMP0063)
if (POLICY CMP0071)
cmake_policy (SET CMP0071 NEW) # run automoc and autouic on generated sources
endif (POLICY CMP0071)
include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake)
message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
@ -233,6 +237,7 @@ set (wsjt_qt_CXXSRCS
models/FrequencyList.cpp
models/StationList.cpp
widgets/FrequencyLineEdit.cpp
widgets/FrequencyDeltaLineEdit.cpp
item_delegates/CandidateKeyFilter.cpp
item_delegates/ForeignKeyDelegate.cpp
validators/LiveFrequencyValidator.cpp
@ -275,9 +280,12 @@ set (wsjt_qt_CXXSRCS
widgets/CabrilloLogWindow.cpp
item_delegates/CallsignDelegate.cpp
item_delegates/MaidenheadLocatorDelegate.cpp
item_delegates/FrequencyDelegate.cpp
item_delegates/FrequencyDeltaDelegate.cpp
models/CabrilloLog.cpp
logbook/AD1CCty.cpp
logbook/WorkedBefore.cpp
logbook/Multiplier.cpp
)
set (wsjt_qtmm_CXXSRCS
@ -383,9 +391,11 @@ set (wsjt_FSRCS
lib/astro0.f90
lib/avecho.f90
lib/averms.f90
lib/ft4/averaged_mf.f90
lib/azdist.f90
lib/badmsg.f90
lib/ft8/baseline.f90
lib/ft4/ft4_baseline.f90
lib/bpdecode40.f90
lib/bpdecode128_90.f90
lib/ft8/bpdecode174_91.f90
@ -518,7 +528,6 @@ set (wsjt_FSRCS
lib/msk144sim.f90
lib/mskrtd.f90
lib/nuttal_window.f90
lib/ft4/ft4b.f90
lib/ft4/ft4sim.f90
lib/ft4/ft4sim_mult.f90
lib/ft4/ft4_downsample.f90
@ -555,6 +564,7 @@ set (wsjt_FSRCS
lib/stdmsg.f90
lib/subtract65.f90
lib/ft8/subtractft8.f90
lib/ft4/subtractft4.f90
lib/sun.f90
lib/symspec.f90
lib/symspec2.f90
@ -563,7 +573,7 @@ set (wsjt_FSRCS
lib/sync64.f90
lib/sync65.f90
lib/ft4/getcandidates4.f90
lib/ft4/syncft4.f90
lib/ft4/get_ft4_bitmetrics.f90
lib/ft8/sync8.f90
lib/ft8/sync8d.f90
lib/ft4/sync4d.f90
@ -846,9 +856,6 @@ if (Boost_NO_SYSTEM_PATHS)
set (BOOST_ROOT ${PROJECT_SOURCE_DIR}/boost)
endif ()
find_package (Boost 1.63 REQUIRED)
if (Boost_FOUND)
include_directories (${Boost_INCLUDE_DIRS})
endif ()
#
# OpenMP
@ -879,10 +886,7 @@ message (STATUS "hamlib_LIBRARY_DIRS: ${hamlib_LIBRARY_DIRS}")
#
# Widgets finds its own dependencies.
find_package (Qt5Widgets 5 REQUIRED)
find_package (Qt5Multimedia 5 REQUIRED)
find_package (Qt5PrintSupport 5 REQUIRED)
find_package (Qt5Sql 5 REQUIRED)
find_package (Qt5 REQUIRED Widgets Multimedia PrintSupport Sql LinguistTools)
if (WIN32)
add_definitions (-DQT_NEEDS_QTMAIN)
@ -1088,11 +1092,41 @@ add_custom_target (ctags COMMAND ${CTAGS} -o ${CMAKE_SOURCE_DIR}/tags -R ${sourc
add_custom_target (etags COMMAND ${ETAGS} -o ${CMAKE_SOURCE_DIR}/TAGS -R ${sources})
# Qt i18n
set (LANGUAGES
en_GB
pt_PT
)
foreach (lang_ ${LANGUAGES})
file (TO_NATIVE_PATH translations/wsjtx_${lang_}.ts ts_)
list (APPEND TS_FILES ${ts_})
endforeach ()
if (UPDATE_TRANSLATIONS)
message (STATUS "UPDATE_TRANSLATIONS option is set.")
qt5_create_translation (
QM_FILES ${wsjt_qt_UISRCS} ${wsjtx_UISRCS} ${wsjt_qt_CXXSRCS} ${wsjtx_CXXSRCS}
${TS_FILES}
)
else ()
qt5_add_translation (QM_FILES ${TS_FILES})
endif ()
add_custom_target (translations DEPENDS ${QM_FILES})
set_property (DIRECTORY PROPERTY CLEAN_NO_CUSTOM TRUE)
# do this after i18n to stop lupdate walking the boost tree which it
# chokes on
if (Boost_FOUND)
include_directories (${Boost_INCLUDE_DIRS})
endif ()
# embedded resources
function (add_resources resources path)
foreach (resource_file_ ${ARGN})
get_filename_component (name_ ${resource_file_} NAME)
file (TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${resource_file_} source_)
if (IS_ABSOLUTE "${resource_file_}")
file (TO_NATIVE_PATH ${resource_file_} source_)
else ()
file (TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${resource_file_} source_)
endif ()
file (TO_NATIVE_PATH ${path}/${name_} dest_)
set (resources_ "${resources_}\n <file alias=\"${dest_}\">${source_}</file>")
set (${resources} ${${resources}}${resources_} PARENT_SCOPE)
@ -1101,6 +1135,7 @@ endfunction (add_resources resources path)
add_resources (wsjtx_RESOURCES "" ${TOP_LEVEL_RESOURCES})
add_resources (wsjtx_RESOURCES /Palettes ${PALETTE_FILES})
add_resources (wsjtx_RESOURCES /Translations ${QM_FILES})
configure_file (wsjtx.qrc.in wsjtx.qrc @ONLY)
@ -1121,7 +1156,6 @@ if (WIN32)
wrap_ax_server (GENAXSRCS ${AXSERVERSRCS})
endif (WIN32)
#
# targets
#
@ -1240,6 +1274,9 @@ target_link_libraries (jt49sim wsjt_fort wsjt_cxx)
add_executable (allsim lib/allsim.f90 wsjtx.rc)
target_link_libraries (allsim wsjt_fort wsjt_cxx)
add_executable (rtty_spec lib/rtty_spec.f90 wsjtx.rc)
target_link_libraries (rtty_spec wsjt_fort wsjt_cxx)
add_executable (jt65code lib/jt65code.f90 wsjtx.rc)
target_link_libraries (jt65code wsjt_fort wsjt_cxx)
@ -1282,11 +1319,14 @@ target_link_libraries (msk144sim wsjt_fort wsjt_cxx)
add_executable (ft4sim lib/ft4/ft4sim.f90 wsjtx.rc)
target_link_libraries (ft4sim wsjt_fort wsjt_cxx)
add_executable (averaged_mf lib/ft4/averaged_mf.f90 wsjtx.rc)
target_link_libraries (averaged_mf wsjt_fort wsjt_cxx)
add_executable (ft4sim_mult lib/ft4/ft4sim_mult.f90 wsjtx.rc)
target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
add_executable (ft4d lib/ft4/ft4d.f90 wsjtx.rc)
target_link_libraries (ft4d wsjt_fort wsjt_cxx)
add_executable (record_time_signal Audio/tools/record_time_signal.cpp)
target_link_libraries (record_time_signal wsjt_cxx wsjt_qtmm wsjt_qt)
endif(WSJT_BUILD_UTILS)

View File

@ -167,8 +167,11 @@
#include "MetaDataRegistry.hpp"
#include "SettingsGroup.hpp"
#include "widgets/FrequencyLineEdit.hpp"
#include "widgets/FrequencyDeltaLineEdit.hpp"
#include "item_delegates/CandidateKeyFilter.hpp"
#include "item_delegates/ForeignKeyDelegate.hpp"
#include "item_delegates/FrequencyDelegate.hpp"
#include "item_delegates/FrequencyDeltaDelegate.hpp"
#include "TransceiverFactory.hpp"
#include "Transceiver.hpp"
#include "models/Bands.hpp"
@ -248,6 +251,8 @@ namespace
class FrequencyDialog final
: public QDialog
{
Q_OBJECT
public:
using Item = FrequencyList_v2::Item;
@ -294,6 +299,8 @@ private:
class StationDialog final
: public QDialog
{
Q_OBJECT
public:
explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr)
: QDialog {parent}
@ -564,6 +571,7 @@ private:
DecodeHighlightingModel decode_highlighing_model_;
DecodeHighlightingModel next_decode_highlighing_model_;
bool highlight_by_mode_;
bool include_WAE_entities_;
int LotW_days_since_upload_;
TransceiverFactory::ParameterPack rig_params_;
@ -609,6 +617,7 @@ private:
bool miles_;
bool quick_call_;
bool disable_TX_on_73_;
bool force_call_1st_;
bool alternate_bindings_;
int watchdog_;
bool TX_messages_;
@ -705,6 +714,7 @@ bool Configuration::clear_DX () const {return m_->clear_DX_;}
bool Configuration::miles () const {return m_->miles_;}
bool Configuration::quick_call () const {return m_->quick_call_;}
bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;}
bool Configuration::force_call_1st() const {return m_->force_call_1st_;}
bool Configuration::alternate_bindings() const {return m_->alternate_bindings_;}
int Configuration::watchdog () const {return m_->watchdog_;}
bool Configuration::TX_messages () const {return m_->TX_messages_;}
@ -716,6 +726,7 @@ bool Configuration::x2ToneSpacing() const {return m_->x2ToneSpacing_;}
bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;}
bool Configuration::split_mode () const {return m_->split_mode ();}
QString Configuration::opCall() const {return m_->opCall_;}
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
@ -742,6 +753,7 @@ bool Configuration::pwrBandTuneMemory () const {return m_->pwrBandTuneMemory_;}
LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;}
DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;}
bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;}
bool Configuration::include_WAE_entities () const {return m_->include_WAE_entities_;}
void Configuration::set_calibration (CalibrationParams params)
{
@ -947,6 +959,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, station_insert_action_ {tr ("&Insert ..."), nullptr}
, station_dialog_ {new StationDialog {&next_stations_, &bands_, this}}
, highlight_by_mode_ {false}
, include_WAE_entities_ {false}
, LotW_days_since_upload_ {0}
, last_port_type_ {TransceiverFactory::Capabilities::none}
, rig_is_dummy_ {false}
@ -1116,9 +1129,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true);
// delegates
auto frequencies_item_delegate = new QStyledItemDelegate {this};
frequencies_item_delegate->setItemEditorFactory (item_editor_factory ());
ui_->frequencies_table_view->setItemDelegate (frequencies_item_delegate);
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this});
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {&regions_, 0, this});
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::mode_column, new ForeignKeyDelegate {&modes_, 0, this});
@ -1157,9 +1168,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder);
// stations delegates
auto stations_item_delegate = new QStyledItemDelegate {this};
stations_item_delegate->setItemEditorFactory (item_editor_factory ());
ui_->stations_table_view->setItemDelegate (stations_item_delegate);
ui_->stations_table_view->setItemDelegateForColumn (StationList::offset_column, new FrequencyDeltaDelegate {this});
ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this});
// stations actions
@ -1242,6 +1251,7 @@ void Configuration::impl::initialize_models ()
ui_->miles_check_box->setChecked (miles_);
ui_->quick_call_check_box->setChecked (quick_call_);
ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_);
ui_->force_call_1st_check_box->setChecked (force_call_1st_);
ui_->alternate_bindings_check_box->setChecked (alternate_bindings_);
ui_->tx_watchdog_spin_box->setValue (watchdog_);
ui_->TX_messages_check_box->setChecked (TX_messages_);
@ -1315,6 +1325,7 @@ void Configuration::impl::initialize_models ()
next_decode_highlighing_model_.items (decode_highlighing_model_.items ());
ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_);
ui_->include_WAE_check_box->setChecked (include_WAE_entities_);
ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_);
set_rig_invariants ();
@ -1463,6 +1474,7 @@ void Configuration::impl::read_settings ()
if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items ();
decode_highlighing_model_.items (highlight_items);
highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool ();
include_WAE_entities_ = settings_->value("IncludeWAEEntities", false).toBool ();
LotW_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt ();
lotw_users_.set_age_constraint (LotW_days_since_upload_);
@ -1496,6 +1508,7 @@ void Configuration::impl::read_settings ()
miles_ = settings_->value ("Miles", false).toBool ();
quick_call_ = settings_->value ("QuickCall", false).toBool ();
disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool ();
force_call_1st_ = settings_->value ("ForceCallFirst", false).toBool ();
alternate_bindings_ = settings_->value ("AlternateBindings", false).toBool ();
watchdog_ = settings_->value ("TxWatchdog", 6).toInt ();
TX_messages_ = settings_->value ("Tx2QSO", true).toBool ();
@ -1575,6 +1588,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ()));
settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ()));
settings_->setValue ("HighlightByMode", highlight_by_mode_);
settings_->setValue ("IncludeWAEEntities", include_WAE_entities_);
settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_);
settings_->setValue ("toRTTY", log_as_RTTY_);
settings_->setValue ("dBtoComments", report_in_comments_);
@ -1598,6 +1612,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("Miles", miles_);
settings_->setValue ("QuickCall", quick_call_);
settings_->setValue ("73TxDisable", disable_TX_on_73_);
settings_->setValue ("ForceCallFirst", force_call_1st_);
settings_->setValue ("AlternateBindings", alternate_bindings_);
settings_->setValue ("TxWatchdog", watchdog_);
settings_->setValue ("Tx2QSO", TX_messages_);
@ -2042,6 +2057,7 @@ void Configuration::impl::accept ()
miles_ = ui_->miles_check_box->isChecked ();
quick_call_ = ui_->quick_call_check_box->isChecked ();
disable_TX_on_73_ = ui_->disable_TX_on_73_check_box->isChecked ();
force_call_1st_ = ui_->force_call_1st_check_box->isChecked ();
alternate_bindings_ = ui_->alternate_bindings_check_box->isChecked ();
watchdog_ = ui_->tx_watchdog_spin_box->value ();
TX_messages_ = ui_->TX_messages_check_box->isChecked ();
@ -2076,7 +2092,12 @@ void Configuration::impl::accept ()
Q_EMIT self_->udp_server_port_changed (new_port);
}
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
{
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
Q_EMIT self_->accept_udp_requests_changed (accept_udp_requests_);
}
n1mm_server_name_ = ui_->n1mm_server_name_line_edit->text ();
n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value ();
broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked ();
@ -2109,6 +2130,7 @@ void Configuration::impl::accept ()
Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_);
}
highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked ();
include_WAE_entities_ = ui_->include_WAE_check_box->isChecked ();
LotW_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value ();
lotw_users_.set_age_constraint (LotW_days_since_upload_);

View File

@ -127,6 +127,7 @@ public:
bool miles () const;
bool quick_call () const;
bool disable_TX_on_73 () const;
bool force_call_1st() const;
bool alternate_bindings() const;
int watchdog () const;
bool TX_messages () const;
@ -147,6 +148,7 @@ public:
bool EMEonly() const;
bool post_decodes () const;
QString opCall() const;
void opCall (QString const&);
QString udp_server_name () const;
port_type udp_server_port () const;
QString n1mm_server_name () const;
@ -175,6 +177,7 @@ public:
LotWUsers const& lotw_users () const;
DecodeHighlightingModel const& decode_highlighting () const;
bool highlight_by_mode () const;
bool include_WAE_entities () const;
enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, FOX, HOUND};
SpecialOperatingActivity special_op_id () const;
@ -268,6 +271,7 @@ public:
//
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
// signal updates to decode highlighting
Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const;

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>546</width>
<height>536</height>
<height>553</height>
</rect>
</property>
<property name="windowTitle">
@ -301,7 +301,14 @@
<string>Behavior</string>
</property>
<layout class="QGridLayout" name="gridLayout_8">
<item row="4" column="1">
<item row="3" column="1">
<widget class="QCheckBox" name="decode_at_52s_check_box">
<property name="text">
<string>Decode after EME delay</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<spacer name="horizontalSpacer_7">
@ -347,10 +354,27 @@
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="decode_at_52s_check_box">
<item row="0" column="1">
<widget class="QCheckBox" name="enable_VHF_features_check_box">
<property name="text">
<string>Decode after EME delay</string>
<string>Enable VHF/UHF/Microwave features</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="single_decode_check_box">
<property name="text">
<string>Single decode</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="tx_QSY_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Some rigs are not able to process CAT commands while transmitting. This means that if you are operating in split mode you may have to uncheck this option.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Allow Tx frequency changes while transmitting</string>
</property>
</widget>
</item>
@ -367,31 +391,35 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="single_decode_check_box">
<property name="text">
<string>Single decode</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="enable_VHF_features_check_box">
<property name="text">
<string>Enable VHF/UHF/Microwave features</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="tx_QSY_check_box">
<item row="1" column="0">
<widget class="QCheckBox" name="monitor_last_used_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Some rigs are not able to process CAT commands while transmitting. This means that if you are operating in split mode you may have to uncheck this option.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this if you wish to automatically return to the last monitored frequency when monitor is enabled, leave it unchecked if you wish to have the current rig frequency maintained.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Allow Tx frequency changes while transmitting</string>
<string>Monitor returns to last used frequency</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="5" column="0">
<widget class="QCheckBox" name="alternate_bindings_check_box">
<property name="text">
<string>Alternate F1-F6 bindings</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="disable_TX_on_73_check_box">
<property name="toolTip">
<string>Turns off automatic transmissions after sending a 73 or any other free
text message.</string>
</property>
<property name="text">
<string>Di&amp;sable Tx after sending 73</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QCheckBox" name="CW_id_after_73_check_box">
@ -444,34 +472,6 @@ quiet period when decoding is done.</string>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="monitor_last_used_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this if you wish to automatically return to the last monitored frequency when monitor is enabled, leave it unchecked if you wish to have the current rig frequency maintained.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Monitor returns to last used frequency</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="alternate_bindings_check_box">
<property name="text">
<string>Alternate F1-F5 bindings</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="disable_TX_on_73_check_box">
<property name="toolTip">
<string>Turns off automatic transmissions after sending a 73 or any other free
text message.</string>
</property>
<property name="text">
<string>Di&amp;sable Tx after sending 73</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="quick_call_check_box">
<property name="toolTip">
@ -482,6 +482,13 @@ text message.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="force_call_1st_check_box">
<property name="text">
<string>Calling CQ forces Call 1st</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -1912,7 +1919,7 @@ for assessing propagation and system performance.</string>
<item>
<widget class="QGroupBox" name="n1mm_group_box">
<property name="title">
<string>N1MM Logger+ Broadcasts</string>
<string>Secondary UDP Server (deprecated)</string>
</property>
<layout class="QFormLayout" name="formLayout_15">
<item row="0" column="0" colspan="2">
@ -1928,7 +1935,7 @@ for assessing propagation and system performance.</string>
<item row="1" column="0">
<widget class="QLabel" name="n1mm_server_name_label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;N1MM Server name or IP address:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Server name or IP address:</string>
</property>
<property name="buddy">
<cstring>n1mm_server_name_line_edit</cstring>
@ -1945,7 +1952,7 @@ for assessing propagation and system performance.</string>
<item row="2" column="0">
<widget class="QLabel" name="n1mm_server_port_label">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;N1MM Server port number:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Server port number:</string>
</property>
<property name="buddy">
<cstring>n1mm_server_port_spin_box</cstring>
@ -2287,6 +2294,23 @@ Right click for insert and delete options.</string>
</item>
</layout>
</item>
<item>
<layout class="QFormLayout" name="formLayout_20">
<item row="0" column="0">
<widget class="QCheckBox" name="include_WAE_check_box"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="includeExtraWAEEntitiesLabel">
<property name="text">
<string>Include extra WAE entities</string>
</property>
<property name="buddy">
<cstring>include_WAE_check_box</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -2299,35 +2323,6 @@ Right click for insert and delete options.</string>
<string>Logbook of the World User Validation</string>
</property>
<layout class="QFormLayout" name="formLayout_18">
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Age of last upload less than:</string>
</property>
<property name="buddy">
<cstring>LotW_days_since_upload_spin_box</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="suffix">
<string> days</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>365</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
@ -2362,6 +2357,35 @@ Right click for insert and delete options.</string>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Age of last upload less than:</string>
</property>
<property name="buddy">
<cstring>LotW_days_since_upload_spin_box</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="suffix">
<string> days</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>365</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -2469,7 +2493,7 @@ Right click for insert and delete options.</string>
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="gbSpecialOpActivity">
<property name="title">
<string>Special operating activity: Generation of FT8 and MSK144 messages</string>
<string>Special operating activity: Generation of FT4, FT8, and MSK144 messages</string>
</property>
<property name="checkable">
<bool>true</bool>
@ -2556,7 +2580,7 @@ Right click for insert and delete options.</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &amp;quot;DX&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>ARRL RTTY Roundup</string>
<string>RTTY Roundup messages</string>
</property>
<attribute name="buttonGroup">
<string notr="true">special_op_activity_button_group</string>
@ -3079,12 +3103,12 @@ Right click for insert and delete options.</string>
</connections>
<buttongroups>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
</buttongroups>
</ui>

View File

@ -127,7 +127,7 @@ int DXLabSuiteCommanderTransceiver::do_start ()
throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
}
poll ();
do_poll ();
return resolution;
}
@ -247,7 +247,7 @@ void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
update_mode (m);
}
void DXLabSuiteCommanderTransceiver::poll ()
void DXLabSuiteCommanderTransceiver::do_poll ()
{
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
bool quiet {false};

View File

@ -39,7 +39,7 @@ protected:
void do_mode (MODE) override;
void do_ptt (bool on) override;
void poll () override;
void do_poll () override;
private:
MODE get_mode (bool no_debug = false);

View File

@ -38,5 +38,7 @@
<string>True</string>
<key>NSRequiresAquaSystemAppearance</key>
<true/>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to receive signals.</string>
</dict>
</plist>

View File

@ -2,6 +2,7 @@
#include <QDateTime>
#include <QtAlgorithms>
#include <QDebug>
#include <math.h>
#include "commons.h"
#include "moc_Detector.cpp"
@ -10,7 +11,7 @@ extern "C" {
void fil4_(qint16*, qint32*, qint16*, qint32*);
}
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds,
Detector::Detector (unsigned frameRate, double periodLengthInSeconds,
unsigned downSampleFactor, QObject * parent)
: AudioDevice (parent)
, m_frameRate (frameRate)
@ -54,12 +55,14 @@ void Detector::clear ()
qint64 Detector::writeData (char const * data, qint64 maxSize)
{
int ns=secondInPeriod();
if(ns < m_ns) { // When ns has wrapped around to zero, restart the buffers
static unsigned mstr0=999999;
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
if(mstr < mstr0) { //When mstr has wrapped around to 0, restart the buffer
dec_data.params.kin = 0;
m_bufferPos = 0;
}
m_ns=ns;
mstr0=mstr;
// no torn frames
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ())));
@ -72,7 +75,7 @@ qint64 Detector::writeData (char const * data, qint64 maxSize)
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
<< " frames of data on the floor!"
<< dec_data.params.kin << ns;
<< dec_data.params.kin << mstr;
}
for (unsigned remaining = framesAccepted; remaining; ) {
@ -120,13 +123,3 @@ qint64 Detector::writeData (char const * data, qint64 maxSize)
return maxSize; // we drop any data past the end of the buffer on
// the floor until the next period starts
}
unsigned Detector::secondInPeriod () const
{
// we take the time of the data as the following assuming no latency
// delivering it to us (not true but close enough for us)
qint64 now (QDateTime::currentMSecsSinceEpoch ());
unsigned secondInToday ((now % 86400000LL) / 1000);
return secondInToday % m_period;
}

View File

@ -22,9 +22,10 @@ public:
//
// the samplesPerFFT argument is the number after down sampling
//
Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned downSampleFactor = 4u, QObject * parent = 0);
Detector (unsigned frameRate, double periodLengthInSeconds, unsigned downSampleFactor = 4u,
QObject * parent = 0);
void setTRPeriod(unsigned p) {m_period=p;}
void setTRPeriod(double p) {m_period=p;}
bool reset () override;
Q_SIGNAL void framesWritten (qint64) const;
@ -40,10 +41,9 @@ protected:
private:
void clear (); // discard buffer contents
unsigned secondInPeriod () const;
unsigned m_frameRate;
unsigned m_period;
double m_period;
unsigned m_downSampleFactor;
qint32 m_samplesPerFFT; // after any down sampling
qint32 m_ns;

View File

@ -1,5 +1,7 @@
#include "EmulateSplitTransceiver.hpp"
#include "moc_EmulateSplitTransceiver.cpp"
EmulateSplitTransceiver::EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped, QObject * parent)
: Transceiver {parent}
, wrapped_ {std::move (wrapped)}

View File

@ -27,6 +27,8 @@
class EmulateSplitTransceiver final
: public Transceiver
{
Q_OBJECT
public:
// takes ownership of wrapped Transceiver
explicit EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped,

View File

@ -19,6 +19,8 @@ namespace
int constexpr yaesu_delay {250};
}
#include "moc_HRDTransceiver.cpp"
void HRDTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
{
(*registry)[HRD_transceiver_name] = TransceiverFactory::Capabilities (id, TransceiverFactory::Capabilities::network, true, true /* maybe */);
@ -885,7 +887,7 @@ bool HRDTransceiver::is_button_checked (int button_index, bool no_debug)
return "1" == reply;
}
void HRDTransceiver::poll ()
void HRDTransceiver::do_poll ()
{
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
bool quiet {false};

View File

@ -27,6 +27,8 @@ class QByteArray;
class HRDTransceiver final
: public PollingTransceiver
{
Q_OBJECT
public:
static void register_transceivers (TransceiverFactory::Transceivers *, int id);
@ -48,7 +50,7 @@ protected:
void do_ptt (bool on) override;
// Implement the PollingTransceiver interface.
void poll () override;
void do_poll () override;
private:
QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false);

View File

@ -632,7 +632,7 @@ int HamlibTransceiver::do_start ()
resolution = -1; // best guess
}
poll ();
do_poll ();
TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution);
return resolution;
@ -898,7 +898,7 @@ void HamlibTransceiver::do_mode (MODE mode)
update_mode (mode);
}
void HamlibTransceiver::poll ()
void HamlibTransceiver::do_poll ()
{
#if !WSJT_TRACE_CAT_POLLS
#if defined (NDEBUG)

View File

@ -21,9 +21,9 @@ extern "C"
class HamlibTransceiver final
: public PollingTransceiver
{
Q_OBJECT; // for translation context
Q_OBJECT // for translation context
public:
public:
static void register_transceivers (TransceiverFactory::Transceivers *);
static void unregister_transceivers ();
@ -40,7 +40,7 @@ class HamlibTransceiver final
void do_mode (MODE) override;
void do_ptt (bool) override;
void poll () override;
void do_poll () override;
void error_check (int ret_code, QString const& doing) const;
void set_conf (char const * item, char const * value);

64
INSTALL
View File

@ -30,13 +30,13 @@ Mac".
Qt v5, preferably v5.5 or later is required to build WSJT-X.
Qt v5 multimedia support and serial port is necessary as well as the
core Qt v5 components, normally installing the Qt multimedia
development package and Qt serialport development package are
sufficient to pull in all the required Qt components and dependants as
a single transaction. On some systems the Qt multimedia plugin
component is separate in the distribution repository an it may also
need installing.
Qt v5 multimedia support, serial port, and Linguist is necessary as
well as the core Qt v5 components, normally installing the Qt
multimedia development, Qt serialport development packages, and the Qt
Linguist packages are sufficient to pull in all the required Qt
components and dependants as a single transaction. On some systems
the Qt multimedia plugin component is separate in the distribution
repository an it may also need installing.
The single precision FFTW v3 library libfftw3f is required along with
the libfftw library development package. Normally installing the
@ -256,47 +256,6 @@ The above commands will build hamlib and install it into
~/hamlib-prefix. If `make install-strip` fails, try `make install`.
Qt
--
NOTE: As of Qt v5.4 building Qt from source on Mac OS X is no longer
necessary since the Qt team have switched to using the modern libc++
Standard C++ Library for all distributable run time
components. Instead you may simply download a binary installer for OS
X 64-bit. The binary installer is here:
http://www.qt.io/download
The binary Qt distributions prior to Qt v5.4 from
http://www.qt.io/download unfortunately are built to use the libstdc++
C++ support library, WSJT-X uses a less geriatric C++ dialect which
uses the libc++ C++ support library. This means that you need to
build Qt from sources. This is not difficult but does take some time.
Download the Qt source tarball from
http://www.qt.io/download-open-source/, the link is about half way
down the page, you want the full sources tar ball shown as a 'tar.gz'
link.
Unpack the sources and cd into the top level directory then type:
$ ./configure -prefix ~/local/qt-macx-clang -opensource \
-confirm-license -platform macx-clang -silent -nomake tests \
-nomake examples -sdk macosx10.10 -skip qtwebkit \
-skip qtwebkit-examples -skip qtquick1 -skip qtconnectivity \
-skip qtlocation -skip qtsensors -skip qtscript \
-skip qtwebsockets -skip qtwebengine -skip qtwebchannel \
-skip qtwayland -skip qtquickcontrols -skip qtdeclarative \
-skip qtxmlpatterns -skip qtenginio
$ make -j4
$ make install
If you are building on 10.8 or don't have the 10.10 Mac SDK (Xcode 6)
available, you can substitute '-sdk macosx10.9' above.
The build above will take a few hours to complete.
CMake
-----
Although CMake is available via MacPorts I prefer to use the binary
@ -328,6 +287,13 @@ $ sudo chgrp wheel /usr/local/bin
and then retry the install command.
Qt
--
Download the latest on-line installer package from the Qt web site and
isntall the latest Qt stable version development package.
WSJT-X
------
First fetch the source from the repository:
@ -411,4 +377,4 @@ $ cmake --build . --target install
73
Bill
G4WJS.
G4WJS.

View File

@ -3,6 +3,7 @@
#include <stdexcept>
#include <vector>
#include <algorithm>
#include <limits>
#include <QUdpSocket>
#include <QHostInfo>
@ -35,6 +36,7 @@ public:
impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self)
: self_ {self}
, enabled_ {false}
, id_ {id}
, version_ {version}
, revision_ {revision}
@ -79,6 +81,7 @@ public:
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
bool enabled_;
QString id_;
QString version_;
QString revision_;
@ -160,6 +163,12 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
schema_ = in.schema ();
}
if (!enabled_)
{
TRACE_UDP ("message processing disabled for id:" << in.id ());
return;
}
//
// message format is described in NetworkMessage.hpp
//
@ -200,6 +209,15 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
}
break;
case NetworkMessage::Close:
TRACE_UDP ("Close");
if (check_status (in) != Fail)
{
last_message_.clear ();
Q_EMIT self_->close ();
}
break;
case NetworkMessage::Replay:
TRACE_UDP ("Replay");
if (check_status (in) != Fail)
@ -261,6 +279,42 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
}
break;
case NetworkMessage::SwitchConfiguration:
{
QByteArray configuration_name;
in >> configuration_name;
TRACE_UDP ("Switch Configuration name:" << configuration_name);
if (check_status (in) != Fail)
{
Q_EMIT self_->switch_configuration (QString::fromUtf8 (configuration_name));
}
}
break;
case NetworkMessage::Configure:
{
QByteArray mode;
quint32 frequency_tolerance;
QByteArray submode;
bool fast_mode {false};
quint32 tr_period {std::numeric_limits<quint32>::max ()};
quint32 rx_df {std::numeric_limits<quint32>::max ()};
QByteArray dx_call;
QByteArray dx_grid;
bool generate_messages {false};
in >> mode >> frequency_tolerance >> submode >> fast_mode >> tr_period >> rx_df
>> dx_call >> dx_grid >> generate_messages;
TRACE_UDP ("Configure mode:" << mode << "frequency tolerance:" << frequency_tolerance << "submode:" << submode << "fast mode:" << fast_mode << "T/R period:" << tr_period << "rx df:" << rx_df << "dx call:" << dx_call << "dx grid:" << dx_grid << "generate messages:" << generate_messages);
if (check_status (in) != Fail)
{
Q_EMIT self_->configure (QString::fromUtf8 (mode), frequency_tolerance
, QString::fromUtf8 (submode), fast_mode, tr_period, rx_df
, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
, generate_messages);
}
}
break;
default:
// Ignore
//
@ -438,13 +492,20 @@ void MessageClient::add_blocked_destination (QHostAddress const& a)
}
}
void MessageClient::enable (bool flag)
{
m_->enabled_ = flag;
}
void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode
, bool tx_enabled, bool transmitting, bool decoding
, qint32 rx_df, qint32 tx_df, QString const& de_call
, quint32 rx_df, quint32 tx_df, QString const& de_call
, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode
, bool fast_mode, quint8 special_op_mode)
, bool fast_mode, quint8 special_op_mode
, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
{
@ -453,8 +514,8 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
<< tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
<< de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
<< fast_mode << special_op_mode;
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode);
<< fast_mode << special_op_mode << frequency_tolerance << tr_period << configuration_name.toUtf8 ();
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode << "frequency tolerance:" << frequency_tolerance << "T/R period:" << tr_period << "configuration name:" << configuration_name);
m_->send_message (out, message);
}
}
@ -527,7 +588,7 @@ void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
QByteArray ADIF {"\n<adif_ver:5>3.0.7\n<programid:6>WSJT-X\n<EOH>\n" + ADIF_record + " <EOR>"};
QByteArray ADIF {"\n<adif_ver:5>3.1.0\n<programid:6>WSJT-X\n<EOH>\n" + ADIF_record + " <EOR>"};
out << ADIF;
TRACE_UDP ("ADIF:" << ADIF);
m_->send_message (out, message);

View File

@ -47,12 +47,16 @@ public:
// change the server port messages are sent to
Q_SLOT void set_server_port (port_type server_port = 0u);
// enable incoming messages
Q_SLOT void enable (bool);
// outgoing messages
Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report
, QString const& tx_mode, bool tx_enabled, bool transmitting, bool decoding
, qint32 rx_df, qint32 tx_df, QString const& de_call, QString const& de_grid
, quint32 rx_df, quint32 tx_df, QString const& de_call, QString const& de_grid
, QString const& dx_grid, bool watchdog_timeout, QString const& sub_mode
, bool fast_mode, quint8 special_op_mode);
, bool fast_mode, quint8 special_op_mode, quint32 frequency_tolerance
, quint32 tr_period, QString const& configuration_name);
Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence
, bool off_air);
@ -89,6 +93,10 @@ public:
Q_SIGNAL void reply (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message_text, bool low_confidence, quint8 modifiers);
// this signal is emitted if the server has requested this client to
// close down gracefully
Q_SIGNAL void close ();
// this signal is emitted if the server has requested a replay of
// all decodes
Q_SIGNAL void replay ();
@ -105,6 +113,16 @@ public:
// callsign request for the specified call
Q_SIGNAL void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only);
// this signal is emitted if the server has requested a
// configuration switch
Q_SIGNAL void switch_configuration (QString const& configuration_name);
// this signal is emitted if the server has requested a
// configuration change
Q_SIGNAL void configure (QString const& mode, quint32 frequency_tolerance, QString const& submode
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
, QString const& dx_grid, bool generate_messages);
// this signal is emitted when network errors occur or if a host
// lookup fails
Q_SIGNAL void error (QString const&) const;

View File

@ -1,6 +1,7 @@
#include "MessageServer.hpp"
#include <stdexcept>
#include <limits>
#include <QNetworkInterface>
#include <QUdpSocket>
@ -16,6 +17,11 @@
#include "moc_MessageServer.cpp"
namespace
{
auto quint32_max = std::numeric_limits<quint32>::max ();
}
class MessageServer::impl
: public QUdpSocket
{
@ -238,8 +244,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
bool tx_enabled {false};
bool transmitting {false};
bool decoding {false};
qint32 rx_df {-1};
qint32 tx_df {-1};
quint32 rx_df {quint32_max};
quint32 tx_df {quint32_max};
QByteArray de_call;
QByteArray de_grid;
QByteArray dx_grid;
@ -247,9 +253,12 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
QByteArray sub_mode;
bool fast_mode {false};
quint8 special_op_mode {0};
quint32 frequency_tolerance {quint32_max};
quint32 tr_period {quint32_max};
QByteArray configuration_name;
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
>> fast_mode >> special_op_mode;
>> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name;
if (check_status (in) != Fail)
{
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
@ -258,7 +267,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
, QString::fromUtf8 (dx_grid), watchdog_timeout
, QString::fromUtf8 (sub_mode), fast_mode
, special_op_mode);
, special_op_mode, frequency_tolerance, tr_period
, QString::fromUtf8 (configuration_name));
}
}
break;
@ -493,6 +503,17 @@ void MessageServer::replay (QString const& id)
}
}
void MessageServer::close (QString const& id)
{
auto iter = m_->clients_.find (id);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_};
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}
void MessageServer::halt_tx (QString const& id, bool auto_only)
{
auto iter = m_->clients_.find (id);
@ -541,3 +562,30 @@ void MessageServer::highlight_callsign (QString const& id, QString const& callsi
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
{
auto iter = m_->clients_.find (id);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
out << configuration_name.toUtf8 ();
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages)
{
auto iter = m_->clients_.find (id);
if (iter != std::end (m_->clients_))
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_};
out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
<< dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
}
}

View File

@ -52,6 +52,9 @@ public:
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
// ask the client with identification 'id' to close down gracefully
Q_SLOT void close (QString const& id);
// ask the client with identification 'id' to replay all decodes
Q_SLOT void replay (QString const& id);
@ -72,15 +75,25 @@ public:
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false);
// ask the client with identification 'id' to switch to
// configuration 'configuration_name'
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
// ask the client with identification 'id' to change configuration
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
// the following signals are emitted when a client broadcasts the
// matching message
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode);
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name);
Q_SIGNAL void client_closed (QString const& id);
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode, QString const& message

View File

@ -14,16 +14,35 @@
#include "WFPalette.hpp"
#include "models/IARURegions.hpp"
#include "models/DecodeHighlightingModel.hpp"
#include "widgets/FrequencyLineEdit.hpp"
#include "widgets/DateTimeEdit.hpp"
QItemEditorFactory * item_editor_factory ()
namespace
{
static QItemEditorFactory * our_item_editor_factory = new QItemEditorFactory;
return our_item_editor_factory;
class ItemEditorFactory final
: public QItemEditorFactory
{
public:
ItemEditorFactory ()
: default_factory_ {QItemEditorFactory::defaultFactory ()}
{
}
QWidget * createEditor (int user_type, QWidget * parent) const override
{
auto editor = QItemEditorFactory::createEditor (user_type, parent);
return editor ? editor : default_factory_->createEditor (user_type, parent);
}
private:
QItemEditorFactory const * default_factory_;
};
}
void register_types ()
{
auto item_editor_factory = new ItemEditorFactory;
QItemEditorFactory::setDefaultFactory (item_editor_factory);
// types in Radio.hpp are registered in their own translation unit
// as they are needed in the wsjtx_udp shared library too
@ -31,9 +50,7 @@ void register_types ()
// used as signal/slot connection arguments since the new Qt 5.5
// Q_ENUM macro only seems to register the unqualified name
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::Frequency> (), new QStandardItemEditorCreator<FrequencyLineEdit> ());
//auto frequency_delta_type_id = qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::FrequencyDelta> (), new QStandardItemEditorCreator<FrequencyDeltaLineEdit> ());
item_editor_factory->registerEditor (qMetaTypeId<QDateTime> (), new QStandardItemEditorCreator<DateTimeEdit> ());
// Frequency list model
qRegisterMetaTypeStreamOperators<FrequencyList_v2::Item> ("Item_v2");

View File

@ -1,9 +1,6 @@
#ifndef META_DATA_REGISTRY_HPP__
#define META_DATA_REGISTRY_HPP__
class QItemEditorFactory;
QItemEditorFactory * item_editor_factory ();
void register_types ();
#endif

View File

@ -25,15 +25,15 @@ double constexpr Modulator::m_twoPi;
// unsigned m_nspd=1.2*48000.0/wpm;
// m_nspd=3072; //18.75 WPM
Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
QObject * parent)
: AudioDevice {parent}
, m_quickClose {false}
, m_phi {0.0}
, m_toneSpacing {0.0}
, m_fSpread {0.0}
, m_frameRate {frameRate}
, m_period {periodLengthInSeconds}
, m_frameRate {frameRate}
, m_state {Idle}
, m_tuning {false}
, m_cwLevel {false}
@ -45,19 +45,15 @@ Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
double frequency, double toneSpacing,
SoundOutput * stream, Channel channel,
bool synchronize, bool fastMode, double dBSNR, int TRperiod)
bool synchronize, bool fastMode, double dBSNR, double TRperiod)
{
Q_ASSERT (stream);
// Time according to this computer which becomes our base time
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
// qDebug() << "ModStart" << symbolsLength << framesPerSymbol
// << frequency << toneSpacing;
if(m_state != Idle) stop ();
if(m_state != Idle) stop();
m_quickClose = false;
m_symbolsLength = symbolsLength;
m_isym0 = std::numeric_limits<unsigned>::max (); // big number
m_frequency0 = 0.;
@ -69,37 +65,33 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
m_toneSpacing = toneSpacing;
m_bFastMode=fastMode;
m_TRperiod=TRperiod;
unsigned delay_ms = 1920 == m_nsps && 15 == m_period ? 500 : 1000;
unsigned delay_ms=1000;
if(m_nsps==1920) delay_ms=500; //FT8
if(m_nsps==576) delay_ms=300; //FT4
// noise generator parameters
// noise generator parameters
if (m_addNoise) {
m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0));
m_fac = 3000.0;
if (m_snr > 1.0) m_fac = 3000.0 / m_snr;
}
unsigned mstr = ms0 % (1000 * m_period); // ms in period
// round up to an exact portion of a second that allows for startup
// delays
// round up to an exact portion of a second that allows for startup delays
m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000;
if(m_bFastMode) m_ic=0;
m_silentFrames = 0;
// calculate number of silent frames to send, so that audio will start at
// the nominal time "delay_ms" into the Tx sequence.
// calculate number of silent frames to send, so that audio will start at
// the nominal time "delay_ms" into the Tx sequence.
if (synchronize && !m_tuning && !m_bFastMode) {
m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000));
}
if(symbolsLength==105 and framesPerSymbol==512
and (toneSpacing==12000.0/512.0 or toneSpacing==-2.0)) {
//### FT4 parameters
m_ic=0;
m_silentFrames=0;
}
// qDebug() << "Mod AA" << symbolsLength << framesPerSymbol << toneSpacing;
// qDebug() << "Mod AB" << delay_ms << mstr << m_ic << m_silentFrames;
// qDebug() << "aa" << QDateTime::currentDateTimeUtc().toString("hh:mm:ss.zzz")
// << m_ic << m_silentFrames << m_silentFrames/48000.0
// << mstr << fmod(double(ms0),1000.0*m_period);
initialize (QIODevice::ReadOnly, channel);
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
@ -183,12 +175,12 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode);
if(m_TRperiod==3) slowCwId=false;
if(m_TRperiod==3.0) slowCwId=false;
bool fastCwId=false;
static bool bCwId=false;
qint64 ms = QDateTime::currentMSecsSinceEpoch();
float tsec=0.001*(ms % (1000*m_TRperiod));
if(m_bFastMode and (icw[0]>0) and (tsec>(m_TRperiod-5.0))) fastCwId=true;
float tsec=0.001*(ms % int(1000*m_TRperiod));
if(m_bFastMode and (icw[0]>0) and (tsec > (m_TRperiod-5.0))) fastCwId=true;
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
// qDebug() << "Mod A" << m_ic << isym << tsec;
@ -259,7 +251,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
i1= m_symbolsLength * 4.0 * m_nsps;
}
if(m_bFastMode and !m_tuning) {
i1=m_TRperiod*48000 - 24000;
i1=m_TRperiod*48000.0 - 24000.0;
i0=i1-816;
}
@ -267,7 +259,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
isym=0;
if(!m_tuning and m_TRperiod!=3) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
if(!m_tuning and m_TRperiod!=3.0) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
if(m_bFastMode) isym=isym%m_symbolsLength;
if (isym != m_isym0 || m_frequency != m_frequency0) {
if(itone[0]>=100) {

View File

@ -23,7 +23,7 @@ class Modulator
public:
enum ModulatorState {Synchronizing, Active, Idle};
Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent = nullptr);
Modulator (unsigned frameRate, double periodLengthInSeconds, QObject * parent = nullptr);
void close () override;
@ -31,14 +31,14 @@ public:
double frequency () const {return m_frequency;}
bool isActive () const {return m_state != Idle;}
void setSpread(double s) {m_fSpread=s;}
void setTRPeriod(unsigned p) {m_period=p;}
void setTRPeriod(double p) {m_period=p;}
void set_nsym(int n) {m_symbolsLength=n;}
void set_ms0(qint64 ms) {m_ms0=ms;}
Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, double frequency,
double toneSpacing, SoundOutput *, Channel = Mono,
bool synchronize = true, bool fastMode = false,
double dBSNR = 99., int TRperiod=60);
double dBSNR = 99., double TRperiod=60.0);
Q_SLOT void stop (bool quick = false);
Q_SLOT void tune (bool newState = true);
Q_SLOT void setFrequency (double newFrequency) {m_frequency = newFrequency;}
@ -72,14 +72,14 @@ private:
double m_fac;
double m_toneSpacing;
double m_fSpread;
double m_TRperiod;
double m_period;
qint64 m_silentFrames;
qint64 m_ms0;
qint32 m_TRperiod;
qint16 m_ramp;
unsigned m_frameRate;
unsigned m_period;
ModulatorState volatile m_state;
bool volatile m_tuning;

View File

@ -65,6 +65,8 @@ namespace
class NameDialog final
: public QDialog
{
Q_OBJECT
public:
explicit NameDialog (QString const& current_name,
QStringList const& current_names,
@ -112,6 +114,8 @@ namespace
class ExistingNameDialog final
: public QDialog
{
Q_OBJECT
public:
explicit ExistingNameDialog (QStringList const& current_names, QWidget * parent = nullptr)
: QDialog {parent}
@ -155,13 +159,16 @@ public:
bool exit ();
QSettings settings_;
QString current_;
// switch to this configuration
void select_configuration (QString const& target_name);
private:
using Dictionary = QMap<QString, QVariant>;
// create a configuration maintenance sub menu
QMenu * create_sub_menu (QMainWindow * main_window,
QMenu * parent,
QMenu * create_sub_menu (QMenu * parent,
QString const& menu_title,
QActionGroup * = nullptr);
@ -171,32 +178,32 @@ private:
// write the settings values from the dictionary to the current group
void load_from (Dictionary const&, bool add_placeholder = true);
// switch to this configuration
void select_configuration (QMainWindow *, QMenu const *);
// clone this configuration
void clone_configuration (QMainWindow * main_window, QMenu *, QMenu const *);
void clone_configuration (QMenu *, QMenu const *);
// update this configuration from another
void clone_into_configuration (QMainWindow *, QMenu const *);
void clone_into_configuration (QMenu const *);
// reset configuration to default values
void reset_configuration (QMainWindow *, QMenu const *);
void reset_configuration (QMenu const *);
// change configuration name
void rename_configuration (QMainWindow *, QMenu *);
void rename_configuration (QMenu *);
// remove a configuration
void delete_configuration (QMainWindow *, QMenu *);
void delete_configuration (QMenu *);
// action to take on restart
enum class RepositionType {unchanged, replace, save_and_replace};
void restart (RepositionType);
MultiSettings const * parent_; // required for emitting signals
QMainWindow * main_window_;
bool name_change_emit_pending_; // delayed until menu built
QFont original_font_;
QString current_;
// action to take on restart
enum class RepositionType {unchanged, replace, save_and_replace} reposition_type_;
RepositionType reposition_type_;
Dictionary new_settings_;
bool exit_flag_; // false means loop around with new
// configuration
@ -263,6 +270,16 @@ void MultiSettings::create_menu_actions (QMainWindow * main_window, QMenu * menu
m_->create_menu_actions (main_window, menu);
}
void MultiSettings::select_configuration (QString const& name)
{
m_->select_configuration (name);
}
QString MultiSettings::configuration_name () const
{
return m_->current_;
}
bool MultiSettings::exit ()
{
return m_->exit ();
@ -271,6 +288,7 @@ bool MultiSettings::exit ()
MultiSettings::impl::impl (MultiSettings const * parent, QString const& config_name)
: settings_ {settings_path (), QSettings::IniFormat}
, parent_ {parent}
, main_window_ {nullptr}
, name_change_emit_pending_ {true}
, reposition_type_ {RepositionType::unchanged}
, exit_flag_ {true}
@ -407,13 +425,14 @@ bool MultiSettings::impl::reposition ()
// and, reset
void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu * menu)
{
main_window_ = main_window;
auto const& current_group = settings_.group ();
if (current_group.size ()) settings_.endGroup ();
SettingsGroup alternatives {&settings_, multi_settings_root_group};
// get the current configuration name
auto const& current_configuration_name = settings_.value (multi_settings_current_name_key, tr (default_string)).toString ();
// add the default configuration sub menu
QMenu * default_menu = create_sub_menu (main_window, menu, current_configuration_name, configurations_group_);
QMenu * default_menu = create_sub_menu (menu, current_configuration_name, configurations_group_);
// and set as the current configuration
default_menu->menuAction ()->setChecked (true);
@ -423,7 +442,7 @@ void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu
// add all the other configurations
for (auto const& configuration_name: available_configurations)
{
create_sub_menu (main_window, menu, configuration_name, configurations_group_);
create_sub_menu (menu, configuration_name, configurations_group_);
}
if (current_group.size ()) settings_.beginGroup (current_group);
@ -446,8 +465,7 @@ bool MultiSettings::impl::exit ()
return reposition ();
}
QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
QMenu * parent_menu,
QMenu * MultiSettings::impl::create_sub_menu (QMenu * parent_menu,
QString const& menu_title,
QActionGroup * action_group)
{
@ -456,7 +474,7 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
sub_menu->menuAction ()->setCheckable (true);
// populate sub-menu actions before showing
connect (sub_menu, &QMenu::aboutToShow, [this, main_window, parent_menu, sub_menu] () {
connect (sub_menu, &QMenu::aboutToShow, [this, parent_menu, sub_menu] () {
// depopulate before populating and showing because on Mac OS X
// there is an issue with depopulating in QMenu::aboutToHide()
// with connections being disconnected before they are actioned
@ -470,16 +488,16 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
{
auto select_action = new QAction {tr ("&Switch To"), this};
sub_menu->addAction (select_action);
connect (select_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
select_configuration (main_window, sub_menu);
connect (select_action, &QAction::triggered, [this, sub_menu] (bool) {
select_configuration (sub_menu->title ());
});
sub_menu->addSeparator ();
}
auto clone_action = new QAction {tr ("&Clone"), this};
sub_menu->addAction (clone_action);
connect (clone_action, &QAction::triggered, [this, main_window, parent_menu, sub_menu] (bool) {
clone_configuration (main_window, parent_menu, sub_menu);
connect (clone_action, &QAction::triggered, [this, parent_menu, sub_menu] (bool) {
clone_configuration (parent_menu, sub_menu);
});
auto const& current_group = settings_.group ();
@ -489,29 +507,29 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
{
auto clone_into_action = new QAction {tr ("Clone &Into ..."), this};
sub_menu->addAction (clone_into_action);
connect (clone_into_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
clone_into_configuration (main_window, sub_menu);
connect (clone_into_action, &QAction::triggered, [this, sub_menu] (bool) {
clone_into_configuration (sub_menu);
});
}
if (current_group.size ()) settings_.beginGroup (current_group);
auto reset_action = new QAction {tr ("R&eset"), this};
sub_menu->addAction (reset_action);
connect (reset_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
reset_configuration (main_window, sub_menu);
connect (reset_action, &QAction::triggered, [this, sub_menu] (bool) {
reset_configuration (sub_menu);
});
auto rename_action = new QAction {tr ("&Rename ..."), this};
sub_menu->addAction (rename_action);
connect (rename_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
rename_configuration (main_window, sub_menu);
connect (rename_action, &QAction::triggered, [this, sub_menu] (bool) {
rename_configuration (sub_menu);
});
if (!is_current)
{
auto delete_action = new QAction {tr ("&Delete"), this};
sub_menu->addAction (delete_action);
connect (delete_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
delete_configuration (main_window, sub_menu);
connect (delete_action, &QAction::triggered, [this, sub_menu] (bool) {
delete_configuration (sub_menu);
});
}
});
@ -554,11 +572,9 @@ void MultiSettings::impl::load_from (Dictionary const& dictionary, bool add_plac
settings_.sync ();
}
void MultiSettings::impl::select_configuration (QMainWindow * main_window, QMenu const * menu)
void MultiSettings::impl::select_configuration (QString const& target_name)
{
auto const& target_name = menu->title ();
if (target_name != current_)
if (main_window_ && target_name != current_)
{
{
auto const& current_group = settings_.group ();
@ -573,13 +589,11 @@ void MultiSettings::impl::select_configuration (QMainWindow * main_window, QMenu
// and set up the restart
current_ = target_name;
Q_EMIT parent_->configurationNameChanged (unescape_ampersands (current_));
reposition_type_ = RepositionType::save_and_replace;
exit_flag_ = false;
main_window->close ();
restart (RepositionType::save_and_replace);
}
}
void MultiSettings::impl::clone_configuration (QMainWindow * main_window, QMenu * parent_menu, QMenu const * menu)
void MultiSettings::impl::clone_configuration (QMenu * parent_menu, QMenu const * menu)
{
auto const& current_group = settings_.group ();
if (current_group.size ()) settings_.endGroup ();
@ -612,12 +626,15 @@ void MultiSettings::impl::clone_configuration (QMainWindow * main_window, QMenu
load_from (source_settings);
// insert the new configuration sub menu in the parent menu
create_sub_menu (main_window, parent_menu, new_name, configurations_group_);
create_sub_menu (parent_menu, new_name, configurations_group_);
if (current_group.size ()) settings_.beginGroup (current_group);
}
void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, QMenu const * menu)
void MultiSettings::impl::clone_into_configuration (QMenu const * menu)
{
Q_ASSERT (main_window_);
if (!main_window_) return;
auto const& current_group = settings_.group ();
if (current_group.size ()) settings_.endGroup ();
auto const& target_name = menu->title ();
@ -638,15 +655,16 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
}
// pick a source configuration
ExistingNameDialog dialog {sources, main_window};
ExistingNameDialog dialog {sources, main_window_};
if (sources.size () && (1 == sources.size () || QDialog::Accepted == dialog.exec ()))
{
QString source_name {1 == sources.size () ? sources.at (0) : dialog.name ()};
if (MessageBox::Yes == MessageBox::query_message (main_window,
tr ("Clone Into Configuration"),
tr ("Confirm overwrite of all values for configuration \"%1\" with values from \"%2\"?")
.arg (unescape_ampersands (target_name))
.arg (unescape_ampersands (source_name))))
if (main_window_
&& MessageBox::Yes == MessageBox::query_message (main_window_,
tr ("Clone Into Configuration"),
tr ("Confirm overwrite of all values for configuration \"%1\" with values from \"%2\"?")
.arg (unescape_ampersands (target_name))
.arg (unescape_ampersands (source_name))))
{
// grab the data to clone from
if (source_name == current_group_name)
@ -665,9 +683,7 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
if (target_name == current_)
{
// restart with new settings
reposition_type_ = RepositionType::replace;
exit_flag_ = false;
main_window->close ();
restart (RepositionType::replace);
}
else
{
@ -682,14 +698,18 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
if (current_group.size ()) settings_.beginGroup (current_group);
}
void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu const * menu)
void MultiSettings::impl::reset_configuration (QMenu const * menu)
{
Q_ASSERT (main_window_);
if (!main_window_) return;
auto const& target_name = menu->title ();
if (MessageBox::Yes != MessageBox::query_message (main_window,
tr ("Reset Configuration"),
tr ("Confirm reset to default values for configuration \"%1\"?")
.arg (unescape_ampersands (target_name))))
if (!main_window_
|| MessageBox::Yes != MessageBox::query_message (main_window_,
tr ("Reset Configuration"),
tr ("Confirm reset to default values for configuration \"%1\"?")
.arg (unescape_ampersands (target_name))))
{
return;
}
@ -697,10 +717,8 @@ void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu
if (target_name == current_)
{
// restart with default settings
reposition_type_ = RepositionType::replace;
new_settings_.clear ();
exit_flag_ = false;
main_window->close ();
restart (RepositionType::replace);
}
else
{
@ -717,8 +735,11 @@ void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu
}
}
void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu * menu)
void MultiSettings::impl::rename_configuration (QMenu * menu)
{
Q_ASSERT (main_window_);
if (!main_window_) return;
auto const& current_group = settings_.group ();
if (current_group.size ()) settings_.endGroup ();
auto const& target_name = menu->title ();
@ -729,7 +750,7 @@ void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu
invalid_names << settings_.value (multi_settings_current_name_key).toString ();
// get the new name
NameDialog dialog {target_name, invalid_names, main_window};
NameDialog dialog {target_name, invalid_names, main_window_};
if (QDialog::Accepted == dialog.exec ())
{
if (target_name == current_)
@ -760,8 +781,9 @@ void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu
if (current_group.size ()) settings_.beginGroup (current_group);
}
void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu * menu)
void MultiSettings::impl::delete_configuration (QMenu * menu)
{
Q_ASSERT (main_window_);
auto const& target_name = menu->title ();
if (target_name == current_)
@ -770,10 +792,11 @@ void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu
}
else
{
if (MessageBox::Yes != MessageBox::query_message (main_window,
tr ("Delete Configuration"),
tr ("Confirm deletion of configuration \"%1\"?")
.arg (unescape_ampersands (target_name))))
if (!main_window_
|| MessageBox::Yes != MessageBox::query_message (main_window_,
tr ("Delete Configuration"),
tr ("Confirm deletion of configuration \"%1\"?")
.arg (unescape_ampersands (target_name))))
{
return;
}
@ -789,3 +812,12 @@ void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu
// update the menu
menu->deleteLater ();
}
void MultiSettings::impl::restart (RepositionType type)
{
Q_ASSERT (main_window_);
reposition_type_ = type;
exit_flag_ = false;
main_window_->close ();
main_window_ = nullptr;
}

View File

@ -80,6 +80,10 @@ public:
// action is triggered.
void create_menu_actions (QMainWindow *, QMenu *);
// switch to this configuration if it exists
Q_SLOT void select_configuration (QString const& name);
QString configuration_name () const;
// Access to the QSettings object instance.
QSettings * settings ();

View File

@ -116,15 +116,18 @@
* Tx Enabled bool
* Transmitting bool
* Decoding bool
* Rx DF qint32
* Tx DF qint32
* Rx DF quint32
* Tx DF quint32
* DE call utf8
* DE grid utf8
* DX grid utf8
* Tx Watchdog bool
* Sub-mode utf8
* Fast mode bool
* Special operation mode quint8
* Special Operation Mode quint8
* Frequency Tolerance quint32
* T/R Period quint32
* Configuration Name utf8
*
* WSJT-X sends this status message when various internal state
* changes to allow the server to track the relevant state of each
@ -133,19 +136,22 @@
*
* Application start up,
* "Enable Tx" button status changes,
* Dial frequency changes,
* Changes to the "DX Call" field,
* Operating mode, sub-mode or fast mode changes,
* Transmit mode changed (in dual JT9+JT65 mode),
* Changes to the "Rpt" spinner,
* After an old decodes replay sequence (see Replay below),
* When switching between Tx and Rx mode,
* At the start and end of decoding,
* When the Rx DF changes,
* When the Tx DF changes,
* When settings are exited,
* When the DX call or grid changes,
* When the Tx watchdog is set or reset.
* dial frequency changes,
* changes to the "DX Call" field,
* operating mode, sub-mode or fast mode changes,
* transmit mode changed (in dual JT9+JT65 mode),
* changes to the "Rpt" spinner,
* after an old decodes replay sequence (see Replay below),
* when switching between Tx and Rx mode,
* at the start and end of decoding,
* when the Rx DF changes,
* when the Tx DF changes,
* when settings are exited,
* when the DX call or grid changes,
* when the Tx watchdog is set or reset,
* when the frequency tolerance is changed,
* when the T/R period is changed,
* when the configuration name changes.
*
* The Special operation mode is an enumeration that indicates the
* setting selected in the WSJT-X "Settings->Advanced->Special
@ -159,6 +165,10 @@
* 5 -> FOX
* 6 -> HOUND
*
* The Frequency Tolerance and T/R period fields may have a value
* of the maximum quint32 value which implies the field is not
* applicable.
*
*
* Decode Out 2 quint32
* Id (unique key) utf8
@ -271,11 +281,12 @@
* button.
*
*
* Close Out 6 quint32
* Close Out/In 6 quint32
* Id (unique key) utf8
*
* Close is sent by a client immediately prior to it shutting
* down gracefully.
* Close is sent by a client immediately prior to it shutting
* down gracefully. When sent by a server it requests the target
* client to close down gracefully.
*
*
* Replay In 7 quint32
@ -414,6 +425,35 @@
* the last instance only instead of all instances of the
* specified call be highlighted or have it's highlighting
* cleared.
*
*
* SwitchConfiguration In 14 quint32
* Id (unique key) utf8
* Configuration Name utf8
*
* The server may send this message at any time. The message
* specifies the name of the configuration to switch to. The new
* configuration must exist.
*
*
* Configure In 15 quint32
* Id (unique key) utf8
* Mode utf8
* Frequency Tolerance quint32
* Submode utf8
* Fast Mode bool
* T/R Period quint32
* Rx DF quint32
* DX Call utf8
* DX Grid utf8
* Generate Messages bool
*
* The server may send this message at any time. The message
* specifies various configuration options. For utf8 string
* fields an empty value implies no change, for the quint32 Rx DF
* and Frequency Tolerance fields the maximum quint32 value
* implies no change. Invalid or unrecognized values will be
* silently ignored.
*/
#include <QDataStream>
@ -443,6 +483,8 @@ namespace NetworkMessage
Location,
LoggedADIF,
HighlightCallsign,
SwitchConfiguration,
Configure,
maximum_message_type_ // ONLY add new message types
// immediately before here
};

View File

@ -4,6 +4,7 @@
#include <QDebug>
#include <objbase.h>
#include <QThread>
#include <QEventLoop>
#include "qt_helpers.hpP"
@ -109,6 +110,15 @@ OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped
{
}
// returns false on time out
bool OmniRigTransceiver::await_notification_with_timeout (int timeout)
{
QEventLoop el;
connect (this, &OmniRigTransceiver::notified, &el, [&el] () {el.exit (1);});
QTimer::singleShot (timeout, Qt::CoarseTimer, &el, [&el] () {el.exit (0);});
return 1 == el.exec (); // wait for notify or timer
}
int OmniRigTransceiver::do_start ()
{
TRACE_CAT ("OmniRigTransceiver", "starting");
@ -182,101 +192,109 @@ int OmniRigTransceiver::do_start ()
.arg (readable_params_, 8, 16, QChar ('0'))
.arg (writable_params_, 8, 16, QChar ('0'))
.arg (rig_number_).toLocal8Bit ());
offline_timer_.reset (new QTimer);
offline_timer_->setSingleShot (true);
offline_timer_->setInterval (5 * 1000);
connect (&*offline_timer_, &QTimer::timeout, this, &OmniRigTransceiver::timeout_check);
for (unsigned tries {0}; tries < 10; ++tries)
for (int i = 0; i < 5; ++i)
{
QThread::msleep (100); // wait until OmniRig polls the rig
auto f = rig_->GetRxFrequency ();
int resolution {0};
if (f)
if (OmniRig::ST_ONLINE == rig_->Status ())
{
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
{
// start with VFO A (probably MAIN) on rigs that we
// can't query VFO but can set explicitly
rig_->SetVfo (OmniRig::PM_VFOA);
}
if (f % 10) return resolution; // 1Hz resolution
auto test_frequency = f - f % 100 + 55;
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (test_frequency);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (test_frequency);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (test_frequency);
}
else
{
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
}
switch (rig_->GetRxFrequency () - test_frequency)
{
case -5: resolution = -1; break; // 10Hz truncated
case 5: resolution = 1; break; // 10Hz rounded
case -15: resolution = -2; break; // 20Hz truncated
case -55: resolution = -2; break; // 100Hz truncated
case 45: resolution = 2; break; // 100Hz rounded
}
if (1 == resolution) // may be 20Hz rounded
{
test_frequency = f - f % 100 + 51;
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (test_frequency);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (test_frequency);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (test_frequency);
}
if (9 == rig_->GetRxFrequency () - test_frequency)
{
resolution = 2; // 20Hz rounded
}
}
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (f);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (f);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (f);
}
update_rx_frequency (f);
return resolution;
break;
}
await_notification_with_timeout (1000);
}
if (OmniRig::ST_ONLINE != rig_->Status ())
{
throw_qstring ("OmniRig: " + rig_->StatusStr ());
}
auto f = rig_->GetRxFrequency ();
for (int i = 0; (f == 0) && (i < 5); ++i)
{
await_notification_with_timeout (1000);
f = rig_->GetRxFrequency ();
}
update_rx_frequency (f);
int resolution {0};
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
{
// start with VFO A (probably MAIN) on rigs that we
// can't query VFO but can set explicitly
rig_->SetVfo (OmniRig::PM_VFOA);
}
f = state ().frequency ();
if (f % 10) return resolution; // 1Hz resolution
auto test_frequency = f - f % 100 + 55;
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (test_frequency);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (test_frequency);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (test_frequency);
}
else
{
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
}
if (!await_notification_with_timeout (1000))
{
TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
}
switch (rig_->GetRxFrequency () - test_frequency)
{
case -5: resolution = -1; break; // 10Hz truncated
case 5: resolution = 1; break; // 10Hz rounded
case -15: resolution = -2; break; // 20Hz truncated
case -55: resolution = -2; break; // 100Hz truncated
case 45: resolution = 2; break; // 100Hz rounded
}
if (1 == resolution) // may be 20Hz rounded
{
test_frequency = f - f % 100 + 51;
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (test_frequency);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (test_frequency);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (test_frequency);
}
if (!await_notification_with_timeout (2000))
{
TRACE_CAT ("OmniRigTransceiver", "do_start 2: wait timed out");
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
}
if (9 == rig_->GetRxFrequency () - test_frequency)
{
resolution = 2; // 20Hz rounded
}
}
throw_qstring (tr ("OmniRig: Initialization timed out"));
return 0; // keep compiler happy
if (OmniRig::PM_FREQ & writable_params_)
{
rig_->SetFreq (f);
}
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
{
rig_->SetFreqB (f);
}
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
{
rig_->SetFreqA (f);
}
update_rx_frequency (f);
return resolution;
}
void OmniRigTransceiver::do_stop ()
{
if (offline_timer_)
{
offline_timer_->stop ();
offline_timer_.reset ();
}
QThread::msleep (200); // leave some time for pending
// commands at the server end
if (port_)
@ -300,17 +318,6 @@ void OmniRigTransceiver::do_stop ()
TRACE_CAT ("OmniRigTransceiver", "stopped");
}
void OmniRigTransceiver::do_sync (bool force_signal, bool /*no_poll*/)
{
// nothing much we can do here, we just have to let OmniRig do its
// stuff and its first poll should send us and update that will
// trigger a update signal from us. Any attempt to query OmniRig
// leads to a whole mess of trouble since its internal state is
// garbage until it has done its first rig poll.
send_update_signal_ = force_signal;
update_complete ();
}
void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
{
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + help + ')');
@ -319,16 +326,20 @@ void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString
void OmniRigTransceiver::handle_visible_change ()
{
if (!omni_rig_ || omni_rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
}
void OmniRigTransceiver::handle_rig_type_change (int rig_number)
{
if (!omni_rig_ || omni_rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", "rig type change: rig =" << rig_number);
if (rig_number_ == rig_number)
{
if (!rig_ || rig_->isNull ()) return;
readable_params_ = rig_->ReadableParams ();
writable_params_ = rig_->WriteableParams ();
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
TRACE_CAT ("OmniRigTransceiver", QString {"rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
.arg (rig_->RigType ())
.arg (readable_params_, 8, 16, QChar ('0'))
.arg (writable_params_, 8, 16, QChar ('0'))
@ -338,48 +349,47 @@ void OmniRigTransceiver::handle_rig_type_change (int rig_number)
void OmniRigTransceiver::handle_status_change (int rig_number)
{
if (!omni_rig_ || omni_rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", QString {"status change for rig %1"}.arg (rig_number).toLocal8Bit ());
if (rig_number_ == rig_number)
{
if (!rig_ || rig_->isNull ()) return;
auto const& status = rig_->StatusStr ().toLocal8Bit ();
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig status change: new status for rig %1 = "}.arg (rig_number).toLocal8Bit () << status);
TRACE_CAT ("OmniRigTransceiver", "OmniRig status change: new status = " << status);
if (OmniRig::ST_ONLINE != rig_->Status ())
{
if (!offline_timer_->isActive ())
{
offline_timer_->start (); // give OmniRig time to recover
}
offline ("Rig went offline");
}
else
{
offline_timer_->stop ();
update_rx_frequency (rig_->GetRxFrequency ());
update_complete ();
TRACE_CAT ("OmniRigTransceiver", "OmniRig frequency:" << state ().frequency ());
Q_EMIT notified ();
}
// else
// {
// update_rx_frequency (rig_->GetRxFrequency ());
// update_complete ();
// TRACE_CAT ("OmniRigTransceiver", "frequency:" << state ().frequency ());
// }
}
}
void OmniRigTransceiver::timeout_check ()
{
offline ("Rig went offline");
}
void OmniRigTransceiver::handle_params_change (int rig_number, int params)
{
if (rig_number_ == rig_number)
{
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig params change: params = 0x%1 for rig %2"}
if (!omni_rig_ || omni_rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
.arg (params, 8, 16, QChar ('0'))
.arg (rig_number).toLocal8Bit ()
<< "state before:" << state ());
if (rig_number_ == rig_number)
{
if (!rig_ || rig_->isNull ()) return;
// starting_ = false;
TransceiverState old_state {state ()};
auto need_frequency = false;
// state_.online = true; // sometimes we don't get an initial
// // OmniRig::ST_ONLINE status change
// // event
if (params & OmniRig::PM_VFOAA)
{
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
update_split (false);
reversed_ = false;
update_rx_frequency (rig_->FreqA ());
@ -387,6 +397,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
}
if (params & OmniRig::PM_VFOAB)
{
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
update_split (true);
reversed_ = false;
update_rx_frequency (rig_->FreqA ());
@ -394,6 +405,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
}
if (params & OmniRig::PM_VFOBA)
{
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
update_split (true);
reversed_ = true;
update_other_frequency (rig_->FreqA ());
@ -401,6 +413,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
}
if (params & OmniRig::PM_VFOBB)
{
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
update_split (false);
reversed_ = true;
update_other_frequency (rig_->FreqA ());
@ -408,154 +421,195 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
}
if (params & OmniRig::PM_VFOA)
{
TRACE_CAT ("OmniRigTransceiver", "VFOA");
reversed_ = false;
need_frequency = true;
}
if (params & OmniRig::PM_VFOB)
{
TRACE_CAT ("OmniRigTransceiver", "VFOB");
reversed_ = true;
need_frequency = true;
}
if (params & OmniRig::PM_FREQ)
{
TRACE_CAT ("OmniRigTransceiver", "FREQ");
need_frequency = true;
}
if (params & OmniRig::PM_FREQA)
{
auto f = rig_->FreqA ();
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
if (reversed_)
{
update_other_frequency (rig_->FreqA ());
update_other_frequency (f);
}
else
{
update_rx_frequency (rig_->FreqA ());
update_rx_frequency (f);
}
}
if (params & OmniRig::PM_FREQB)
{
auto f = rig_->FreqB ();
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
if (reversed_)
{
update_rx_frequency (rig_->FreqB ());
update_rx_frequency (f);
}
else
{
update_other_frequency (rig_->FreqB ());
update_other_frequency (f);
}
}
if (need_frequency)
{
if (readable_params_ & OmniRig::PM_FREQA)
{
if (reversed_)
auto f = rig_->FreqA ();
if (f)
{
update_other_frequency (rig_->FreqA ());
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
if (reversed_)
{
update_other_frequency (f);
}
else
{
update_rx_frequency (f);
}
}
else
{
update_rx_frequency (rig_->FreqA ());
}
need_frequency = false;
}
if (readable_params_ & OmniRig::PM_FREQB)
{
if (reversed_)
auto f = rig_->FreqB ();
if (f)
{
update_rx_frequency (rig_->FreqB ());
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
if (reversed_)
{
update_rx_frequency (f);
}
else
{
update_other_frequency (f);
}
}
}
if (readable_params_ & OmniRig::PM_FREQ && !state ().ptt ())
{
auto f = rig_->Freq ();
if (f)
{
TRACE_CAT ("OmniRigTransceiver", "FREQ = " << f);
update_rx_frequency (f);
}
else
{
update_other_frequency (rig_->FreqB ());
}
need_frequency = false;
}
}
if (need_frequency && (readable_params_ & OmniRig::PM_FREQ)
&& !state ().ptt ())
{
update_rx_frequency (rig_->Freq ());
}
if (params & OmniRig::PM_PITCH)
{
TRACE_CAT ("OmniRigTransceiver", "PITCH");
}
if (params & OmniRig::PM_RITOFFSET)
{
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
}
if (params & OmniRig::PM_RIT0)
{
TRACE_CAT ("OmniRigTransceiver", "RIT0");
}
if (params & OmniRig::PM_VFOEQUAL)
{
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq ();
auto m = map_mode (rig_->Mode ());
TRACE_CAT ("OmniRigTransceiver", QString {"VFOEQUAL f=%1 m=%2"}.arg (f).arg (m));
update_rx_frequency (f);
update_other_frequency (f);
update_mode (map_mode (rig_->Mode ()));
update_mode (m);
}
if (params & OmniRig::PM_VFOSWAP)
{
auto temp = state ().tx_frequency ();
TRACE_CAT ("OmniRigTransceiver", "VFOSWAP");
auto f = state ().tx_frequency ();
update_other_frequency (state ().frequency ());
update_rx_frequency (temp);
update_rx_frequency (f);
update_mode (map_mode (rig_->Mode ()));
}
if (params & OmniRig::PM_SPLITON)
{
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
update_split (true);
}
if (params & OmniRig::PM_SPLITOFF)
{
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
update_split (false);
}
if (params & OmniRig::PM_RITON)
{
TRACE_CAT ("OmniRigTransceiver", "RITON");
}
if (params & OmniRig::PM_RITOFF)
{
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
}
if (params & OmniRig::PM_XITON)
{
TRACE_CAT ("OmniRigTransceiver", "XITON");
}
if (params & OmniRig::PM_XITOFF)
{
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
}
if (params & OmniRig::PM_RX)
{
TRACE_CAT ("OmniRigTransceiver", "RX");
update_PTT (false);
}
if (params & OmniRig::PM_TX)
{
TRACE_CAT ("OmniRigTransceiver", "TX");
update_PTT ();
}
if (params & OmniRig::PM_CW_U)
{
TRACE_CAT ("OmniRigTransceiver", "CW-R");
update_mode (CW_R);
}
if (params & OmniRig::PM_CW_L)
{
TRACE_CAT ("OmniRigTransceiver", "CW");
update_mode (CW);
}
if (params & OmniRig::PM_SSB_U)
{
TRACE_CAT ("OmniRigTransceiver", "USB");
update_mode (USB);
}
if (params & OmniRig::PM_SSB_L)
{
TRACE_CAT ("OmniRigTransceiver", "LSB");
update_mode (LSB);
}
if (params & OmniRig::PM_DIG_U)
{
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
update_mode (DIG_U);
}
if (params & OmniRig::PM_DIG_L)
{
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
update_mode (DIG_L);
}
if (params & OmniRig::PM_AM)
{
TRACE_CAT ("OmniRigTransceiver", "AM");
update_mode (AM);
}
if (params & OmniRig::PM_FM)
{
TRACE_CAT ("OmniRigTransceiver", "FM");
update_mode (FM);
}
@ -566,6 +620,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
}
TRACE_CAT ("OmniRigTransceiver", "OmniRig params change: state after:" << state ());
}
Q_EMIT notified ();
}
void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply)
@ -573,8 +628,10 @@ void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& co
(void)command;
(void)reply;
if (!omni_rig_ || omni_rig_->isNull ()) return;
if (rig_number_ == rig_number)
{
if (!rig_ || rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
<< "with reply" << reply.toString ().toLocal8Bit ()
<< QString ("for rig %1").arg (rig_number).toLocal8Bit ());

View File

@ -38,10 +38,11 @@ public:
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
void do_mode (MODE) override;
void do_ptt (bool on) override;
void do_sync (bool force_signal, bool no_poll) override;
private:
Q_SLOT void timeout_check ();
bool await_notification_with_timeout (int timeout);
Q_SIGNAL void notified () const;
// Q_SLOT void timeout_check ();
Q_SLOT void handle_COM_exception (int, QString, QString, QString);
Q_SLOT void handle_visible_change ();
Q_SLOT void handle_rig_type_change (int rig_number);
@ -62,7 +63,7 @@ private:
QString rig_type_;
int readable_params_;
int writable_params_;
QScopedPointer<QTimer> offline_timer_;
// QScopedPointer<QTimer> offline_timer_;
bool send_update_signal_;
bool reversed_; // some rigs can reverse VFOs
};

View File

@ -129,52 +129,46 @@ bool PollingTransceiver::do_pre_update ()
return true;
}
void PollingTransceiver::do_sync (bool force_signal, bool no_poll)
{
if (!no_poll) poll (); // tell sub-classes to update our state
// Signal new state if it is directly requested or, what we expected
// or, hasn't become what we expected after polls_to_stabilize
// polls. Unsolicited changes will be signalled immediately unless
// they intervene in a expected sequence where they will be delayed.
if (retries_)
{
--retries_;
if (force_signal || state () == next_state_ || !retries_)
{
// our client wants a signal regardless
// or the expected state has arrived
// or there are no more retries
force_signal = true;
}
}
else if (force_signal || state () != last_signalled_state_)
{
// here is the normal passive polling path either our client has
// requested a state update regardless of change or state has
// changed asynchronously
force_signal = true;
}
if (force_signal)
{
// reset everything, record and signal the current state
retries_ = 0;
next_state_ = state ();
last_signalled_state_ = state ();
update_complete (true);
}
}
void PollingTransceiver::handle_timeout ()
{
QString message;
bool force_signal {false};
// we must catch all exceptions here since we are called by Qt and
// inform our parent of the failure via the offline() message
try
{
do_sync ();
do_poll (); // tell sub-classes to update our state
// Signal new state if it what we expected or, hasn't become
// what we expected after polls_to_stabilize polls. Unsolicited
// changes will be signalled immediately unless they intervene
// in a expected sequence where they will be delayed.
if (retries_)
{
--retries_;
if (state () == next_state_ || !retries_)
{
// the expected state has arrived or there are no more
// retries
force_signal = true;
}
}
else if (state () != last_signalled_state_)
{
// here is the normal passive polling path where state has
// changed asynchronously
force_signal = true;
}
if (force_signal)
{
// reset everything, record and signal the current state
retries_ = 0;
next_state_ = state ();
last_signalled_state_ = state ();
update_complete (true);
}
}
catch (std::exception const& e)
{

View File

@ -39,11 +39,9 @@ protected:
QObject * parent);
protected:
void do_sync (bool force_signal = false, bool no_poll = false) override final;
// Sub-classes implement this and fetch what they can from the rig
// in a non-intrusive manner.
virtual void poll () = 0;
virtual void do_poll () = 0;
void do_post_start () override final;
void do_post_stop () override final;

View File

@ -74,14 +74,14 @@ namespace Radio
}
QString frequency_MHz_string (Frequency f, QLocale const& locale)
QString frequency_MHz_string (Frequency f, int precision, QLocale const& locale)
{
return locale.toString (f / MHz_factor, 'f', frequency_precsion);
return locale.toString (f / MHz_factor, 'f', precision);
}
QString frequency_MHz_string (FrequencyDelta d, QLocale const& locale)
QString frequency_MHz_string (FrequencyDelta d, int precision, QLocale const& locale)
{
return locale.toString (d / MHz_factor, 'f', frequency_precsion);
return locale.toString (d / MHz_factor, 'f', precision);
}
QString pretty_frequency_MHz_string (Frequency f, QLocale const& locale)

View File

@ -42,8 +42,8 @@ namespace Radio
//
// Frequency type formatting
//
QString UDP_EXPORT frequency_MHz_string (Frequency, QLocale const& = QLocale ());
QString UDP_EXPORT frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
QString UDP_EXPORT frequency_MHz_string (Frequency, int precision = 6, QLocale const& = QLocale ());
QString UDP_EXPORT frequency_MHz_string (FrequencyDelta, int precision = 6, QLocale const& = QLocale ());
QString UDP_EXPORT pretty_frequency_MHz_string (Frequency, QLocale const& = QLocale ());
QString UDP_EXPORT pretty_frequency_MHz_string (double, int scale, QLocale const& = QLocale ());
QString UDP_EXPORT pretty_frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());

View File

@ -12,6 +12,157 @@
Copyright 2001 - 2019 by Joe Taylor, K1JT.
Release: WSJT-X 2.1.0-rc7
June 3, 2019
-------------------------
This release is a bug fix only release addressing regressions in the
prior RC6 release. There are no functional changes other than an
updated AD1C CTY.DAT database.
Release: WSJT-X 2.1.0-rc6
June 2, 2019
-------------------------
Changes and bug fixes since WSJT-X 2.1.0-rc5:
IMPORTANT CHANGES TO THE FT4 PROTOCOL *** NOT BACKWARD COMPATIBLE ***
- T/R sequence length increased from 6.0 to 7.5 seconds
- Symbol rate decreased from 23.4375 to 20.8333 baud
- Signal bandwidth decreased from 90 Hz to 80 Hz
OTHER FT4 IMPROVEMENTS
- Allowable time offsets -1.0 < DT < +1.0 s
- Tx4 message with RRR now allowed, except in contest messages
- Audio frequency is now sent to PSK Reporter
- Add a third decoding pass
- Add ordered statistics decoding
- Improved sensitivity: threshold S/N is now -17.5 dB
- Improved S/N calculation
- In FT4 mode, Shift+F11/F12 moves Tx freq by +/- 100 Hz
OTHER IMPROVEMENTS
- Improvements to accessibility
- Updates to the User Guide (not yet complete, however)
- New user option: "Calling CQ forces Call 1st"
- N1MM Logger+ now uses the standard WSJT-X UDP messages
- OK/Cancel buttons on Log QSO window maintain fixed positions
- Put EU VHF contest serial numbers into the ADIF SRX and STX fields
- Enhancements to the Omni-Rig CAT interface
- New setting option to include or exclude WAE entities
BUG FIXES
- Fix generation of Tx5 message when one callsign is nonstandard
- Fix a bug that prevented use on macOS
- Fix a bug that caused mode switch from FT4 to FT8
- Fix a bug that caused FT4 to do WSPR-style band hopping
- Fix a bug that caused a Fortran bounds error
- Repaired field editing in the Contest Log window
Release candidate WSJT-X 2.1.0-rc6 will be available for beta-testing
through July 21, 2019. It will be inoperable during the ARRL June VHF
QSO Party (June 8-10) or ARRL Field Day (June 22-23). It will
permanently cease to function after July 21, 2019. If all goes
according to plan, by that time there will be a General
Availability (GA) release of WSJT-X 2.1.0.
Release: WSJT-X 2.1.0-rc5
April 29, 2019
-------------------------
WSJT-X 2.1.0 fifth release candidate is a minor release including the
following.
- Repairs a defect that stopped messages from UDP servers being accepted.
- Improved message sequencing a QSO end for CQing FT4 stations.
- "Best S+P" action times out after two minutes waiting for a candidate.
- Updated macOS Info.plist to comply with latest mic. privacy controls.
- Multi-pass decoding for FT4 inc. prior decode subtraction.
- Fast/Normal/Deep options for the FT4 decoder.
- Proposed suggested working frequencies for the new FT4 mode.
- Repair a defect in RTTY RU where sequencer fails to advance to Tx3.
- Fix a defect where the contest serial # spin box was incorrectly hidden.
- Fix defects in ALL.TXT formatting for JT65 and JT9.
- Reduce timeout for AP decoding with own call from 10 mins to 5 mins.
Release: WSJT-X 2.1.0-rc4
April 10, 2019
-------------------------
WSJT-X 2.1.0 fourth release candidate is a minor release including the
following.
- New "Call Best" button for FT4 mode to select the best reply to a
CQ call based on neediness.
- Fixed UTC display on FT4 waterfall.
This release is made by invitation only to selected testers to trial
the FT4 mode in semi-realistic contest simulations and to elicit
feedback to guide future development.
*Note* this release is not for general public release and we request
that it is not distributed.
Release: WSJT-X 2.1.0-rc3
April 5, 2019
-------------------------
WSJT-X 2.1.0 third release candidate is an enhancement release to
change the implementation of the new FT4 mode to a synchronous T/R
period of 6 seconds.
This release is made by invitation only to selected testers to trial
the FT4 mode in semi-realistic contest simulations and to elicit
feedback to guide future development.
*Note* this release is not for general public release and we request
that it is not distributed.
Release: WSJT-X 2.1.0-rc2
March 29, 2019
-------------------------
WSJT-X 2.1.0 second release candidate is a bug fix release to repair
some usability issues with FT4 contest mode. The following new
features are also included.
- Better options for QSO flow by clicking Tx# buttons to transmit
- A 64-bit package for Windows 64-bit systems
- Improved FT4 sync detection speed
This release is made by invitation only to selected testers to trial
the FT4 mode in semi-realistic contest simulations and to elicit
feedback to guide future development.
*Note* this release is not for general public release and we request
that it is not distributed.
Release: WSJT-X 2.1.0-rc1
March 25, 2019
-------------------------
WSJT-X 2.1.0 first release candidate is a preview alpha quality
release containing the following new features.
- FT4 mode, a new mode targeted at HF digital contesting
- GMSK modulation for FT4 and FT8
- New waterfall option to select between raw sensitivity or a
filtered signal representation for best visualization of signal
quality
This release is made by invitation only to selected testers to trial
the FT4 mode in semi-realistic contest simulations and to elicit
feedback to guide future development.
*Note* this release is not for general public release and we request
that it is not distributed.
Release: WSJT-X 2.0.1
February 25, 2019
---------------------

View File

@ -19,10 +19,10 @@ void TransceiverBase::start (unsigned sequence_number) noexcept
QString message;
try
{
last_sequence_number_ = sequence_number;
may_update u {this, true};
shutdown ();
startup ();
last_sequence_number_ = sequence_number;
}
catch (std::exception const& e)
{
@ -46,6 +46,7 @@ void TransceiverBase::set (TransceiverState const& s,
QString message;
try
{
last_sequence_number_ = sequence_number;
may_update u {this, true};
bool was_online {requested_.online ()};
if (!s.online () && was_online)
@ -115,7 +116,6 @@ void TransceiverBase::set (TransceiverState const& s,
// record what actually changed
requested_.ptt (actual_.ptt ());
}
last_sequence_number_ = sequence_number;
}
catch (std::exception const& e)
{
@ -133,10 +133,27 @@ void TransceiverBase::set (TransceiverState const& s,
void TransceiverBase::startup ()
{
Q_EMIT resolution (do_start ());
do_post_start ();
actual_.online (true);
requested_.online (true);
QString message;
try
{
actual_.online (true);
requested_.online (true);
auto res = do_start ();
do_post_start ();
Q_EMIT resolution (res);
}
catch (std::exception const& e)
{
message = e.what ();
}
catch (...)
{
message = unexpected;
}
if (!message.isEmpty ())
{
offline (message);
}
}
void TransceiverBase::shutdown ()
@ -163,8 +180,8 @@ void TransceiverBase::shutdown ()
}
do_stop ();
do_post_stop ();
actual_.online (false);
requested_.online (false);
actual_ = TransceiverState {};
requested_ = TransceiverState {};
}
void TransceiverBase::stop () noexcept
@ -194,8 +211,11 @@ void TransceiverBase::stop () noexcept
void TransceiverBase::update_rx_frequency (Frequency rx)
{
actual_.frequency (rx);
requested_.frequency (rx); // track rig changes
if (rx)
{
actual_.frequency (rx);
requested_.frequency (rx); // track rig changes
}
}
void TransceiverBase::update_other_frequency (Frequency tx)

View File

@ -112,8 +112,6 @@ protected:
virtual void do_ptt (bool = true) = 0;
virtual void do_post_ptt (bool = true) {}
virtual void do_sync (bool force_signal = false, bool no_poll = false) = 0;
virtual bool do_pre_update () {return true;}
// sub classes report rig state changes with these methods

View File

@ -1,8 +1,11 @@
#include "ClientWidget.hpp"
#include <limits>
#include <QRegExp>
#include <QColor>
#include <QtWidgets>
#include <QAction>
#include <QDebug>
#include "validators/MaidenheadLocatorValidator.hpp"
@ -11,6 +14,9 @@ namespace
//QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
QRegularExpression cq_re {"(CQ|CQDX|QRZ)[^A-Z0-9/]+"};
QRegExpValidator message_validator {message_alphabet};
MaidenheadLocatorValidator locator_validator;
quint32 quint32_max {std::numeric_limits<quint32>::max ()};
void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
{
@ -21,9 +27,10 @@ namespace
}
}
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id)
: client_id_ {client_id}
, rx_df_ (-1)
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
: QSortFilterProxyModel {parent}
, client_id_ {client_id}
, rx_df_ (quint32_max)
{
}
@ -49,7 +56,7 @@ QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int
break;
case 4: // DF
if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10)
if (qAbs (QSortFilterProxyModel::data (proxy_index).toUInt () - rx_df_) <= 10)
{
return QColor {255, 200, 200};
}
@ -87,7 +94,7 @@ void ClientWidget::IdFilterModel::de_call (QString const& call)
}
}
void ClientWidget::IdFilterModel::rx_df (int df)
void ClientWidget::IdFilterModel::rx_df (quint32 df)
{
if (df != rx_df_)
{
@ -119,26 +126,48 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
, QListWidget const * calls_of_interest, QWidget * parent)
: QDockWidget {make_title (id, version, revision), parent}
, id_ {id}
, done_ {false}
, calls_of_interest_ {calls_of_interest}
, decodes_proxy_model_ {id_}
, decodes_proxy_model_ {id}
, beacons_proxy_model_ {id}
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
, decodes_table_view_ {new QTableView}
, beacons_table_view_ {new QTableView}
, message_line_edit_ {new QLineEdit}
, grid_line_edit_ {new QLineEdit}
, decodes_table_view_ {new QTableView {this}}
, beacons_table_view_ {new QTableView {this}}
, message_line_edit_ {new QLineEdit {this}}
, grid_line_edit_ {new QLineEdit {this}}
, generate_messages_push_button_ {new QPushButton {tr ("&Gen Msgs"), this}}
, auto_off_button_ {nullptr}
, halt_tx_button_ {nullptr}
, de_label_ {new QLabel {this}}
, frequency_label_ {new QLabel {this}}
, tx_df_label_ {new QLabel {this}}
, report_label_ {new QLabel {this}}
, configuration_line_edit_ {new QLineEdit {this}}
, mode_line_edit_ {new QLineEdit {this}}
, frequency_tolerance_spin_box_ {new QSpinBox {this}}
, tx_mode_label_ {new QLabel {this}}
, submode_line_edit_ {new QLineEdit {this}}
, fast_mode_check_box_ {new QCheckBox {this}}
, tr_period_spin_box_ {new QSpinBox {this}}
, rx_df_spin_box_ {new QSpinBox {this}}
, dx_call_line_edit_ {new QLineEdit {this}}
, dx_grid_line_edit_ {new QLineEdit {this}}
, decodes_page_ {new QWidget {this}}
, beacons_page_ {new QWidget {this}}
, content_widget_ {new QFrame {this}}
, status_bar_ {new QStatusBar {this}}
, control_button_box_ {new QDialogButtonBox {this}}
, form_layout_ {new QFormLayout}
, horizontal_layout_ {new QHBoxLayout}
, subform1_layout_ {new QFormLayout}
, subform2_layout_ {new QFormLayout}
, subform3_layout_ {new QFormLayout}
, decodes_layout_ {new QVBoxLayout {decodes_page_}}
, beacons_layout_ {new QVBoxLayout {beacons_page_}}
, content_layout_ {new QVBoxLayout {content_widget_}}
, decodes_stack_ {new QStackedLayout}
, auto_off_button_ {new QPushButton {tr ("&Auto Off")}}
, halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}}
, de_label_ {new QLabel}
, mode_label_ {new QLabel}
, fast_mode_ {false}
, frequency_label_ {new QLabel}
, dx_label_ {new QLabel}
, rx_df_label_ {new QLabel}
, tx_df_label_ {new QLabel}
, report_label_ {new QLabel}
, columns_resized_ {false}
{
// set up widgets
@ -152,11 +181,33 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
decodes_table_view_->insertAction (nullptr, erase_rx_frequency_action_);
decodes_table_view_->insertAction (nullptr, erase_both_action_);
auto form_layout = new QFormLayout;
form_layout->addRow (tr ("Free text:"), message_line_edit_);
form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_);
message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this});
grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this});
message_line_edit_->setValidator (&message_validator);
grid_line_edit_->setValidator (&locator_validator);
dx_grid_line_edit_->setValidator (&locator_validator);
tr_period_spin_box_->setRange (5, 30);
tr_period_spin_box_->setSuffix (" s");
rx_df_spin_box_->setRange (200, 5000);
frequency_tolerance_spin_box_->setRange (10, 1000);
frequency_tolerance_spin_box_->setPrefix ("\u00b1");
frequency_tolerance_spin_box_->setSuffix (" Hz");
form_layout_->addRow (tr ("Free text:"), message_line_edit_);
form_layout_->addRow (tr ("Temporary grid:"), grid_line_edit_);
form_layout_->addRow (tr ("Configuration name:"), configuration_line_edit_);
form_layout_->addRow (horizontal_layout_);
subform1_layout_->addRow (tr ("Mode:"), mode_line_edit_);
subform2_layout_->addRow (tr ("Submode:"), submode_line_edit_);
subform3_layout_->addRow (tr ("Fast mode:"), fast_mode_check_box_);
subform1_layout_->addRow (tr ("T/R period:"), tr_period_spin_box_);
subform2_layout_->addRow (tr ("Rx DF:"), rx_df_spin_box_);
subform3_layout_->addRow (tr ("Freq. Tol:"), frequency_tolerance_spin_box_);
subform1_layout_->addRow (tr ("DX call:"), dx_call_line_edit_);
subform2_layout_->addRow (tr ("DX grid:"), dx_grid_line_edit_);
subform3_layout_->addRow (generate_messages_push_button_);
horizontal_layout_->addLayout (subform1_layout_);
horizontal_layout_->addLayout (subform2_layout_);
horizontal_layout_->addLayout (subform3_layout_);
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
Q_EMIT do_free_text (id_, text, false);
});
@ -166,66 +217,102 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT location (id_, grid_line_edit_->text ());
});
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ());
});
connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
Q_EMIT configure (id_, empty, f, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
, quint32_max, quint32_max, empty, empty, false);
});
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state
, quint32_max, quint32_max, empty, empty, false);
});
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, i, quint32_max, empty, empty, false);
});
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, i, empty, empty, false);
});
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false);
});
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
});
auto decodes_page = new QWidget;
auto decodes_layout = new QVBoxLayout {decodes_page};
decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2});
decodes_layout->addWidget (decodes_table_view_);
decodes_layout->addLayout (form_layout);
decodes_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
decodes_layout_->addWidget (decodes_table_view_);
decodes_layout_->addLayout (form_layout_);
auto beacons_proxy_model = new IdFilterModel {id_};
beacons_proxy_model->setSourceModel (beacons_model);
beacons_table_view_->setModel (beacons_proxy_model);
beacons_proxy_model_.setSourceModel (beacons_model);
beacons_table_view_->setModel (&beacons_proxy_model_);
beacons_table_view_->verticalHeader ()->hide ();
beacons_table_view_->hideColumn (0);
beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
beacons_table_view_->setContextMenuPolicy (Qt::ActionsContextMenu);
beacons_table_view_->insertAction (nullptr, erase_action_);
auto beacons_page = new QWidget;
auto beacons_layout = new QVBoxLayout {beacons_page};
beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2});
beacons_layout->addWidget (beacons_table_view_);
beacons_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
beacons_layout_->addWidget (beacons_table_view_);
decodes_stack_->addWidget (decodes_page);
decodes_stack_->addWidget (beacons_page);
decodes_stack_->addWidget (decodes_page_);
decodes_stack_->addWidget (beacons_page_);
// stack alternative views
auto content_layout = new QVBoxLayout;
content_layout->setContentsMargins (QMargins {2, 2, 2, 2});
content_layout->addLayout (decodes_stack_);
content_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
content_layout_->addLayout (decodes_stack_);
// set up controls
auto control_button_box = new QDialogButtonBox;
control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole);
control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole);
auto_off_button_ = control_button_box_->addButton (tr ("&Auto Off"), QDialogButtonBox::ActionRole);
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, true);
});
connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, true);
});
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, false);
});
content_layout->addWidget (control_button_box);
content_layout_->addWidget (control_button_box_);
// set up status area
auto status_bar = new QStatusBar;
status_bar->addPermanentWidget (de_label_);
status_bar->addPermanentWidget (mode_label_);
status_bar->addPermanentWidget (frequency_label_);
status_bar->addPermanentWidget (dx_label_);
status_bar->addPermanentWidget (rx_df_label_);
status_bar->addPermanentWidget (tx_df_label_);
status_bar->addPermanentWidget (report_label_);
content_layout->addWidget (status_bar);
connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
status_bar_->addPermanentWidget (de_label_);
status_bar_->addPermanentWidget (tx_mode_label_);
status_bar_->addPermanentWidget (frequency_label_);
status_bar_->addPermanentWidget (tx_df_label_);
status_bar_->addPermanentWidget (report_label_);
content_layout_->addWidget (status_bar_);
connect (this, &ClientWidget::topLevelChanged, status_bar_, &QStatusBar::setSizeGripEnabled);
// set up central widget
auto content_widget = new QFrame;
content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
content_widget->setLayout (content_layout);
setWidget (content_widget);
content_widget_->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
setWidget (content_widget_);
// setMinimumSize (QSize {550, 0});
setFeatures (DockWidgetMovable | DockWidgetFloatable);
setAllowedAreas (Qt::BottomDockWidgetArea);
setFloating (true);
@ -252,6 +339,25 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
}
}
void ClientWidget::dispose ()
{
done_ = true;
close ();
}
void ClientWidget::closeEvent (QCloseEvent *e)
{
if (!done_)
{
Q_EMIT do_close (id_);
e->ignore (); // defer closure until client actually closes
}
else
{
QDockWidget::closeEvent (e);
}
}
ClientWidget::~ClientWidget ()
{
for (int row = 0; row < calls_of_interest_->count (); ++row)
@ -261,16 +367,45 @@ ClientWidget::~ClientWidget ()
}
}
bool ClientWidget::fast_mode () const
{
return fast_mode_check_box_->isChecked ();
}
namespace
{
void update_line_edit (QLineEdit * le, QString const& value, bool allow_empty = true)
{
le->setEnabled (value.size () || allow_empty);
if (!(le->hasFocus () && le->isModified ()))
{
le->setText (value);
}
}
void update_spin_box (QSpinBox * sb, int value, QString const& special_value = QString {})
{
sb->setSpecialValueText (special_value);
bool enable {0 == special_value.size ()};
sb->setEnabled (enable);
if (!sb->hasFocus () && enable)
{
sb->setValue (value);
}
}
}
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode)
, bool watchdog_timeout, QString const& submode, bool fast_mode
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name)
{
if (id == id_)
if (id == id_)
{
fast_mode_ = fast_mode;
fast_mode_check_box_->setChecked (fast_mode);
decodes_proxy_model_.de_call (de_call);
decodes_proxy_model_.rx_df (rx_df);
QString special;
@ -288,21 +423,25 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
.arg (de_grid.size () ? '(' + de_grid + ')' : QString {})
.arg (special)
: QString {});
mode_label_->setText (QString {"Mode: %1%2%3%4"}
.arg (mode)
.arg (sub_mode)
.arg (fast_mode && !mode.contains (QRegularExpression {R"(ISCAT|MSK144)"}) ? "fast" : "")
update_line_edit (mode_line_edit_, mode);
update_spin_box (frequency_tolerance_spin_box_, frequency_tolerance
, quint32_max == frequency_tolerance ? QString {"n/a"} : QString {});
update_line_edit (submode_line_edit_, submode, false);
tx_mode_label_->setText (QString {"Tx Mode: %1"}
.arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
dx_label_->setText (dx_call.size () >= 0 ? QString {"DX: %1%2"}.arg (dx_call)
.arg (dx_grid.size () ? '(' + dx_grid + ')' : QString {}) : QString {});
rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : "");
tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_df) : "");
update_line_edit (dx_call_line_edit_, dx_call);
update_line_edit (dx_grid_line_edit_, dx_grid);
if (rx_df != quint32_max) update_spin_box (rx_df_spin_box_, rx_df);
update_spin_box (tr_period_spin_box_, tr_period
, quint32_max == tr_period ? QString {"n/a"} : QString {});
tx_df_label_->setText (QString {"Tx: %1"}.arg (tx_df));
report_label_->setText ("SNR: " + report);
update_dynamic_property (frequency_label_, "transmitting", transmitting);
auto_off_button_->setEnabled (tx_enabled);
halt_tx_button_->setEnabled (transmitting);
update_dynamic_property (mode_label_, "decoding", decoding);
update_line_edit (configuration_line_edit_, configuration_name);
update_dynamic_property (mode_line_edit_, "decoding", decoding);
update_dynamic_property (tx_df_label_, "watchdog_timeout", watchdog_timeout);
}
}

View File

@ -1,11 +1,11 @@
#ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#include <QDockWidget>
#include <QObject>
#include <QSortFilterProxyModel>
#include <QString>
#include <QRegularExpression>
#include <QtWidgets>
#include "MessageServer.hpp"
@ -13,6 +13,20 @@ class QAbstractItemModel;
class QModelIndex;
class QColor;
class QAction;
class QListWidget;
class QFormLayout;
class QVBoxLayout;
class QHBoxLayout;
class QStackedLayout;
class QTableView;
class QLineEdit;
class QAbstractButton;
class QLabel;
class QCheckBox;
class QSpinBox;
class QFrame;
class QStatusBar;
class QDialogButtonBox;
using Frequency = MessageServer::Frequency;
@ -25,16 +39,18 @@ public:
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent = nullptr);
void dispose ();
~ClientWidget ();
bool fast_mode () const {return fast_mode_;}
bool fast_mode () const;
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
, QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode);
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name);
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air);
@ -45,6 +61,7 @@ public:
Q_SLOT void decodes_cleared (QString const& client_id);
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0);
Q_SIGNAL void do_close (QString const& id);
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier);
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
@ -52,30 +69,39 @@ public:
Q_SIGNAL void highlight_callsign (QString const& id, QString const& call
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false);
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name);
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
private:
QString id_;
QListWidget const * calls_of_interest_;
class IdFilterModel final
: public QSortFilterProxyModel
{
public:
IdFilterModel (QString const& client_id);
IdFilterModel (QString const& client_id, QObject * = nullptr);
void de_call (QString const&);
void rx_df (int);
void rx_df (quint32);
QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override;
protected:
private:
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
private:
QString client_id_;
QString call_;
QRegularExpression base_call_re_;
int rx_df_;
} decodes_proxy_model_;
quint32 rx_df_;
};
void closeEvent (QCloseEvent *) override;
QString id_;
bool done_;
QListWidget const * calls_of_interest_;
IdFilterModel decodes_proxy_model_;
IdFilterModel beacons_proxy_model_;
QAction * erase_action_;
QAction * erase_rx_frequency_action_;
QAction * erase_both_action_;
@ -83,17 +109,39 @@ private:
QTableView * beacons_table_view_;
QLineEdit * message_line_edit_;
QLineEdit * grid_line_edit_;
QStackedLayout * decodes_stack_;
QAbstractButton * generate_messages_push_button_;
QAbstractButton * auto_off_button_;
QAbstractButton * halt_tx_button_;
QLabel * de_label_;
QLabel * mode_label_;
bool fast_mode_;
QLabel * frequency_label_;
QLabel * dx_label_;
QLabel * rx_df_label_;
QLabel * tx_df_label_;
QLabel * report_label_;
QLineEdit * configuration_line_edit_;
QLineEdit * mode_line_edit_;
QSpinBox * frequency_tolerance_spin_box_;
QLabel * tx_mode_label_;
QLineEdit * submode_line_edit_;
QCheckBox * fast_mode_check_box_;
QSpinBox * tr_period_spin_box_;
QSpinBox * rx_df_spin_box_;
QLineEdit * dx_call_line_edit_;
QLineEdit * dx_grid_line_edit_;
QWidget * decodes_page_;
QWidget * beacons_page_;
QFrame * content_widget_;
QStatusBar * status_bar_;
QDialogButtonBox * control_button_box_;
QFormLayout * form_layout_;
QHBoxLayout * horizontal_layout_;
QFormLayout * subform1_layout_;
QFormLayout * subform2_layout_;
QFormLayout * subform3_layout_;
QVBoxLayout * decodes_layout_;
QVBoxLayout * beacons_layout_;
QVBoxLayout * content_layout_;
QStackedLayout * decodes_stack_;
bool columns_resized_;
};

View File

@ -250,12 +250,15 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
connect (server_, &MessageServer::decodes_cleared, dock, &ClientWidget::decodes_cleared);
connect (dock, &ClientWidget::do_clear_decodes, server_, &MessageServer::clear_decodes);
connect (dock, &ClientWidget::do_close, server_, &MessageServer::close);
connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply);
connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
connect (dock, &ClientWidget::location, server_, &MessageServer::location);
connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign);
connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration);
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
dock_widgets_[id] = dock;
server_->replay (id); // request decodes and status
}
@ -265,7 +268,7 @@ void MessageAggregatorMainWindow::remove_client (QString const& id)
auto iter = dock_widgets_.find (id);
if (iter != std::end (dock_widgets_))
{
(*iter)->close ();
(*iter)->dispose ();
dock_widgets_.erase (iter);
}
}

View File

@ -51,7 +51,8 @@ public:
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/
, quint8 /*special_op_mode*/)
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
, QString const& /*configuration_name*/)
{
if (id == id_)
{

View File

@ -44,6 +44,8 @@ namespace
class Dialog
: public QDialog
{
Q_OBJECT
public:
using BandList = QList<QString>;
@ -69,6 +71,8 @@ private:
static int constexpr band_index_role {Qt::UserRole};
};
#include "WSPRBandHopping.moc"
Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
, QBitArray * bands, int * gray_line_duration, QWidget * parent)
: QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}

View File

@ -8,7 +8,8 @@
#ifdef __cplusplus
#include <cstdbool>
extern "C" {
#else
#endif
#ifndef __cplusplus
#include <stdbool.h>
#endif

1521
cty.dat

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,7 @@ set (UG_SRCS
tutorial-example1.adoc
tutorial-example2.adoc
tutorial-example3.adoc
tutorial-example4.adoc
tutorial-main-window.adoc
tutorial-wide-graph-settings.adoc
utilities.adoc
@ -77,6 +78,8 @@ set (UG_IMGS
images/FreqCal_Graph.png
images/FreqCal_Results.png
images/freemsg.png
images/ft4_decodes.png
images/ft4_waterfall.png
images/ft8_decodes.png
images/FT8_waterfall.png
images/help-menu.png

View File

@ -92,6 +92,7 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice]
:win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages]
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_0_2r.exe[Win32 OpenSSL Lite Package]
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2r.exe[Win64 OpenSSL Lite Package]
:writelog: https://writelog.com/[Writelog]
:wsjt_yahoo_group: https://groups.yahoo.com/neo/groups/wsjtgroup/info[WSJT Group]
:wsjtx: http://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]
@ -115,6 +116,7 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:QRA64_EME: http://physics.princeton.edu/pulsar/K1JT/QRA64_EME.pdf[QRA64 for microwave EME]
:svn: http://subversion.apache.org/packages.html#windows[Subversion]
:win32: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win32.exe[wsjtx-{VERSION}-win32.exe]
:win64: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win64.exe[wsjtx-{VERSION}-win64.exe]
:wsjt-devel: https://lists.sourceforge.net/lists/listinfo/wsjt-devel[here]
:wsjt_repo: https://sourceforge.net/p/wsjt/wsjt_orig/ci/master/tree/[WSJT Source Repository]
:wspr_code: http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe[WSPRcode.exe]

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1,11 +1,12 @@
// Status=review
Download and execute the package file {win32}, following these
instructions:
Download and execute the package file {win32} (WinXP, Vista, Win 7,
Win 8, Win10, 32-bit) or {win64} (Vista, Win 7, Win 8, Win10, 64-bit)
following these instructions:
* Install _WSJT-X_ into its own directory, for example `C:\WSJTX` or `
C:\WSJT\WSJTX`, rather than the conventional location `C:\Program
Files (x86)\WSJTX`.
Files ...\WSJTX`.
* All program files relating to _WSJT-X_ will be stored in the chosen
installation directory and its subdirectories.
@ -29,26 +30,31 @@ TIP: Your computer may be configured so that this directory is
[[OPENSSL]]
* image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error, role="right"]
From this version onward _WSJT-X_ requires the _OpenSSL_ libraries
to be installed. Suitable libraries may already be installed on your
* image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error,
role="right"] _WSJT-X_ requires the _OpenSSL_ libraries to be
installed. Suitable libraries may already be installed on your
system, if they are not you will see this error shortly after
startup. To fix this you need to install the _OpenSSL_ libraries.
** You can download a suitable _OpenSSL_ package for from
{win_openssl_packages}, you need the latest *Win32 v1.0.2 Lite*
version (Note it is the Win32 package even if you are using a
64-bit Windows operating system) which at the time of writing was
{win32_openssl}.
{win_openssl_packages}, you need the latest *Windows v1.0.2 Lite*
version. For the 32-bit _WSJT-X_ build use the Win32 version of the
_OpenSSL_ libraries, for the 64-bit _WSJT-X_ use the Win64 version
of the _OpenSSL_ libraries (Note it is OK to install both versions
on a 64-bit system) which at the time of writing were
{win32_openssl} and {win64_openssl} respectively.
** Install the package and accept the default options, including the
option to copy the _OpenSSL_ DLLs to the Windows system directory
(this is important). +
option to copy the _OpenSSL_ DLLs to the Windows system
directory. There is no obligation to donate to the _OpenSSL_
project, un-check all the donation options if desired. +
NOTE: If you still get the same network error after installing the
_OpenSSL_ libraries then you also need to install the
{msvcpp_redist} component. From the download page select
`vcredist_x86.exe` and run it to install.
`vcredist_x86.exe` for use with the 32-bit _WSJT-X_ build or
`vcredist_x64.exe` with the 64-bit build, then run it to
install.
TIP: If you cannot install the _OpenSSL_ libraries or do not have an
Internet connection on the computer used to run

View File

@ -7,10 +7,10 @@ K1**JT**,`" while the suffix "`-X`" indicates that _WSJT-X_ started as
an extended and experimental branch of the program
_WSJT_.
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers nine different
protocols or modes: *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*, *ISCAT*,
*MSK144*, *WSPR*, and *Echo*. The first five are designed for making
reliable QSOs under extreme weak-signal conditions. They use nearly
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers ten different
protocols or modes: *FT4*, *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*,
*ISCAT*, *MSK144*, *WSPR*, and *Echo*. The first six are designed for
making reliable QSOs under weak-signal conditions. They use nearly
identical message structure and source encoding. JT65 and QRA64 were
designed for EME ("`moonbounce`") on the VHF/UHF bands and have also
proven very effective for worldwide QRP communication on the HF bands.
@ -25,12 +25,19 @@ one-minute timed sequences of alternating transmission and reception,
so a minimal QSO takes four to six minutes — two or three
transmissions by each station, one sending in odd UTC minutes and the
other even. FT8 is operationally similar but four times faster
(15-second T/R sequences) and less sensitive by a few dB. On the HF
bands, world-wide QSOs are possible with any of these modes using
power levels of a few watts (or even milliwatts) and compromise
antennas. On VHF bands and higher, QSOs are possible (by EME and
other propagation types) at signal levels 10 to 15 dB below those
required for CW.
(15-second T/R sequences) and less sensitive by a few dB. FT4 is
faster still (7.5 s T/R sequences) and especially well suited for
radio contesting. On the HF bands, world-wide QSOs are possible with
any of these modes using power levels of a few watts (or even
milliwatts) and compromise antennas. On VHF bands and higher, QSOs
are possible (by EME and other propagation types) at signal levels 10
to 15 dB below those required for CW.
Note that even though their T/R sequences are short, FT4 and FT8 are
classified as slow modes because their message frames are sent only
once per transmission. All fast modes in _WSJT-X_ send their message
frames repeatedly, as many times as will fit into the Tx sequence
length.
*ISCAT*, *MSK144*, and optionally submodes *JT9E-H* are "`fast`"
protocols designed to take advantage of brief signal enhancements from
@ -65,10 +72,10 @@ are available for all three platforms.
*Version Numbers:* _WSJT-X_ release numbers have major, minor, and
patch numbers separated by periods: for example, _WSJT-X_ Version
1.9.0. Temporary "`beta`" release candidates are sometimes made in
2.1.0. Temporary _beta release_ candidates are sometimes made in
advance of a new general-availability release, in order to obtain user
feedback. For example, version 1.9.0-rc1, 1.9.0-rc2, etc., would
be beta releases leading up to the final release of v1.9.0.
feedback. For example, version 2.1.0-rc1, 2.1.0-rc2, etc., would
be beta releases leading up to the final release of v2.1.0.
Release candidates should be used _only_ during a short testing
period. They carry an implied obligation to provide feedback to the
program development group. Candidate releases should not be used on

View File

@ -1,40 +1,11 @@
=== New in Version {VERSION}
For quick reference, here's a short list of features and capabilities
added to _WSJT-X_ since Version 1.9.1:
- New FT8 and MSK144 protocols with 77-bit payloads permit these enhancements:
* Optimized contest messages for NA VHF, EU VHF, Field Day, RTTY Roundup
* Full support for "/R" and "/P" calls in relevant contests
* New logging features for contesting
* Integration with {n1mm_logger} and {writelog} for contesting
* Improved support for compound and nonstandard callsigns
* Nearly equal (or better) sensitivity compared to old protocols
* Lower false decode rates
- Improved color highlighting of received messages
- Improved WSPR sensitivity
- Expanded and improved UDP messages sent to companion programs
- Bug fixes and other minor tweaks to user interface
IMPORTANT: Note that for FT8 and MSK144 there is no backward
compatibility with WSJT-X 1.9.1 and earlier. Everyone using these
modes should upgrade to WSJT-X 2.0 by January 1, 2019.
IMPORTANT: _WSJT-X_ Version 2.0 drops support for Apple Mac OS X 10.9
(Mavericks). It is possible to build from source for this operating
system version but the DMG installer package requires 10.10 or later.
The most important feature added to _WSJT-X_ since Version 2.0.1 is
the new *FT4 protocol*, designed especially for radio contesting. It
has T/R sequence length 7.5 s, bandwidth 80 Hz, and threshold
sensitivity -17.5 dB. Version 2.1.0 also has improvements to FT8
waveform generation, contest logging, rig control, the user interface,
and accessibility, as well as a number of bug fixes.
=== Documentation Conventions

View File

@ -12,10 +12,11 @@ Special cases allow other information such as add-on callsign prefixes
aim is to compress the most common messages used for minimally valid
QSOs into a fixed 72-bit length.
The information payload for FT8 and MSK144 contains 77 bits. The 5
additional bits are used to flag special message types used for FT8
DXpedition Mode, contesting, nonstandard callsigns, and a few other
special types.
The information payload for FT4, FT8, and MSK144 contains 77 bits.
The 5 new bits added to the original 72 are used to flag special
message types signifying special message types used for FT8 DXpedition
Mode, contesting, nonstandard callsigns, and a few other
possibilities.
A standard amateur callsign consists of a one- or two-character
prefix, at least one of which must be a letter, followed by a digit
@ -67,18 +68,29 @@ _WSJT-X_ modes have continuous phase and constant envelope.
[[SLOW_MODES]]
=== Slow Modes
[[FT4PRO]]
==== FT4
Forward error correction (FEC) in FT4 uses a low-density parity check
(LDPC) code with 77 information bits, a 14-bit cyclic redundancy check
(CRC), and 83 parity bits making a 174-bit codeword. It is thus
called an LDPC (174,91) code. Synchronization uses four 4×4 Costas
arrays, and ramp-up and ramp-down symbols are inserted at the start
and end of each transmission. Modulation is 4-tone frequency-shift
keying (4-GFSK) with Gaussian smoothing of frequency transitions. The
keying rate is 12000/576 = 20.8333 baud. Each transmitted symbol
conveys two bits, so the total number of channel symbols is 174/2 + 16
+ 2 = 105. The total bandwidth is 4 × 20.8333 = 83.3 Hz.
[[FT8PRO]]
==== FT8
Forward error correction (FEC) in FT8 uses a low-density parity check
(LDPC) code with 77 information bits, a 14-bit cyclic redundancy check
(CRC), and 83 parity bits making a 174-bit codeword. It is thus
called an LDPC (174,91) code. Synchronization uses 7×7 Costas arrays
at the beginning, middle, and end of each transmission. Modulation is
8-tone frequency-shift keying (8-FSK) at 12000/1920 = 6.25 baud. Each
transmitted symbol carries three bits, so the total number of channel
symbols is 174/3 + 21 = 79. The total occupied bandwidth is 8 × 6.25
= 50 Hz.
FT8 uses the same LDPC (174,91) code as FT4. Modulation is 8-tone
frequency-shift keying (8-GFSK) at 12000/1920 = 6.25 baud.
Synchronization uses 7×7 Costas arrays at the beginning, middle, and
end of each transmission. Transmitted symbols carry three bits, so
the total number of channel symbols is 174/3 + 21 = 79. The total
occupied bandwidth is 8 × 6.25 = 50 Hz.
[[JT4PRO]]
==== JT4
@ -227,7 +239,8 @@ which the probability of decoding is 50% or higher.
|===============================================================================
|Mode |FEC Type |(n,k) | Q|Modulation type|Keying rate (Baud)|Bandwidth (Hz)
|Sync Energy|Tx Duration (s)|S/N Threshold (dB)
|FT8 |LDPC, r=1/2|(174,91)| 8| 8-FSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|FT4 |LDPC, r=1/2|(174,91)| 4| 4-GFSK| 20.8333 | 83.3 | 0.15| 5.04 | -17.5
|FT8 |LDPC, r=1/2|(174,91)| 8| 8-GFSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|JT4A |K=32, r=1/2|(206,72)| 2| 4-FSK| 4.375| 17.5 | 0.50| 47.1 | -23
|JT9A |K=32, r=1/2|(206,72)| 8| 9-FSK| 1.736| 15.6 | 0.19| 49.0 | -27
|JT65A |Reed Solomon|(63,12) |64|65-FSK| 2.692| 177.6 | 0.50| 46.8 | -25
@ -246,6 +259,7 @@ comparable to tone spacing.
[width="50%",cols="h,3*^",frame=topbot,options="header"]
|=====================================
|Mode |Tone Spacing |BW (Hz)|S/N (dB)
|FT4 |20.8333 | 83.3 |-17.5
|FT8 |6.25 | 50.0 |-21
|JT4A |4.375| 17.5 |-23
|JT4B |8.75 | 30.6 |-22

View File

@ -0,0 +1,52 @@
// Status=review
.Main Window:
- Select *FT4* on the *Mode* menu.
- Double-click on *Erase* to clear both text windows.
.Wide Graph Settings:
- *Bins/Pixel* = 7, *Start* = 100 Hz, *N Avg* = 1
- Adjust the width of the Wide Graph window so that the upper
frequency limit is approximately 4000 Hz.
.Open a Wave File:
- Select *File | Open* and navigate to
+...\save\samples\FT4\000000_000002.wav+. The waterfall and Band
Activity window should look something like the following screen shots.
This sample file was recorded during a practice contest test session, so
most of the decoded messages use the *RTTY Roundup* message formats.
[[X15]]
image::ft4_waterfall.png[align="left",alt="Wide Graph Decode FT4"]
image::ft4_decodes.png[align="left"]
- Click with the mouse anywhere on the waterfall display. The green Rx
frequency marker will jump to your selected frequency, and the Rx
frequency control on the main window will be updated accordingly.
- Do the same thing with the *Shift* key held down. Now the red Tx
frequency marker and its associated control on the main window will
follow your frequency selections.
- Do the same thing with the *Ctrl* key held down. Now the both colored
markers and both spinner controls will follow your selections.
- Now double-click on any of the the lines of decoded text in the Band
Activity window. Any line will show similar behavior, setting
Rx frequency to that of the selected message and leaving Tx frequency
unchanged. To change both Rx and Tx frequencies, hold *Ctrl* down
when double-clicking.
TIP: To avoid QRM from competing callers, it is frequently desirable
to answer a CQ on a different frequency from that of the CQing
station. The same is true when you tail-end another QSO. Choose a Tx
frequency that appears to be not in use. You might want to check the
box *Hold Tx Freq*.
TIP: Keyboard shortcuts *Shift+F11* and *Shift+F12* provide an easy
way to move your FT4 Tx frequency down or up in 90 Hz steps.
IMPORTANT: When finished with this Tutorial, don't forget to re-enter
your own callsign as *My Call* on the *Settings | General* tab.

View File

@ -140,6 +140,10 @@ include::tutorial-example2.adoc[]
=== FT8
include::tutorial-example3.adoc[]
[[TUT_EX4]]
=== FT4
include::tutorial-example4.adoc[]
[[MAKE_QSOS]]
== Making QSOs
include::make-qso.adoc[]

View File

@ -1,67 +0,0 @@
#ifndef DATE_TIME_AS_SECS_SINCE_EPOCH_DELEGATE_HPP_
#define DATE_TIME_AS_SECS_SINCE_EPOCH_DELEGATE_HPP_
#include <memory>
#include <QStyledItemDelegate>
#include <QVariant>
#include <QLocale>
#include <QDateTime>
#include <QAbstractItemModel>
#include <QDateTimeEdit>
class DateTimeAsSecsSinceEpochDelegate final
: public QStyledItemDelegate
{
public:
DateTimeAsSecsSinceEpochDelegate (QObject * parent = nullptr)
: QStyledItemDelegate {parent}
{
}
static QVariant to_secs_since_epoch (QDateTime const& date_time)
{
return date_time.toMSecsSinceEpoch () / 1000ull;
}
static QDateTime to_date_time (QModelIndex const& index, int role = Qt::DisplayRole)
{
return to_date_time (index.model ()->data (index, role));
}
static QDateTime to_date_time (QVariant const& value)
{
return QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC);
}
QString displayText (QVariant const& value, QLocale const& locale) const override
{
return locale.toString (to_date_time (value), locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss");
}
QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const& /*option*/, QModelIndex const& /*index*/) const override
{
std::unique_ptr<QDateTimeEdit> editor {new QDateTimeEdit {parent}};
editor->setDisplayFormat (parent->locale ().dateFormat (QLocale::ShortFormat) + " hh:mm:ss");
editor->setTimeSpec (Qt::UTC); // needed because it ignores time
// spec of the QDateTime that it is
// set from
return editor.release ();
}
void setEditorData (QWidget * editor, QModelIndex const& index) const override
{
static_cast<QDateTimeEdit *> (editor)->setDateTime (to_date_time (index, Qt::EditRole));
}
void setModelData (QWidget * editor, QAbstractItemModel * model, QModelIndex const& index) const override
{
model->setData (index, to_secs_since_epoch (static_cast<QDateTimeEdit *> (editor)->dateTime ()));
}
void updateEditorGeometry (QWidget * editor, QStyleOptionViewItem const& option, QModelIndex const& /*index*/) const override
{
editor->setGeometry (option.rect);
}
};
#endif

View File

@ -0,0 +1,27 @@
#include "FrequencyDelegate.hpp"
#include "widgets/FrequencyLineEdit.hpp"
FrequencyDelegate::FrequencyDelegate (QObject * parent)
: QStyledItemDelegate {parent}
{
}
QWidget * FrequencyDelegate::createEditor (QWidget * parent, QStyleOptionViewItem const&
, QModelIndex const&) const
{
auto * editor = new FrequencyLineEdit {parent};
editor->setFrame (false);
return editor;
}
void FrequencyDelegate::setEditorData (QWidget * editor, QModelIndex const& index) const
{
static_cast<FrequencyLineEdit *> (editor)->frequency (index.model ()->data (index, Qt::EditRole).value<Radio::Frequency> ());
}
void FrequencyDelegate::setModelData (QWidget * editor, QAbstractItemModel * model, QModelIndex const& index) const
{
model->setData (index, static_cast<FrequencyLineEdit *> (editor)->frequency (), Qt::EditRole);
}

View File

@ -0,0 +1,21 @@
#ifndef FREQUENCY_DELEGATE_HPP_
#define FREQUENCY_DELEGATE_HPP_
#include <QStyledItemDelegate>
//
// Class FrequencyDelegate
//
// Item delegate for editing a frequency in Hertz but displayed in MHz
//
class FrequencyDelegate final
: public QStyledItemDelegate
{
public:
explicit FrequencyDelegate (QObject * parent = nullptr);
QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override;
void setEditorData (QWidget * editor, QModelIndex const&) const override;
void setModelData (QWidget * editor, QAbstractItemModel *, QModelIndex const&) const override;
};
#endif

View File

@ -0,0 +1,26 @@
#include "FrequencyDeltaDelegate.hpp"
#include "widgets/FrequencyDeltaLineEdit.hpp"
FrequencyDeltaDelegate::FrequencyDeltaDelegate (QObject * parent)
: QStyledItemDelegate {parent}
{
}
QWidget * FrequencyDeltaDelegate::createEditor (QWidget * parent, QStyleOptionViewItem const&
, QModelIndex const&) const
{
auto * editor = new FrequencyDeltaLineEdit {parent};
editor->setFrame (false);
return editor;
}
void FrequencyDeltaDelegate::setEditorData (QWidget * editor, QModelIndex const& index) const
{
static_cast<FrequencyDeltaLineEdit *> (editor)->frequency_delta (index.model ()->data (index, Qt::EditRole).value<Radio::FrequencyDelta> ());
}
void FrequencyDeltaDelegate::setModelData (QWidget * editor, QAbstractItemModel * model, QModelIndex const& index) const
{
model->setData (index, static_cast<FrequencyDeltaLineEdit *> (editor)->frequency_delta (), Qt::EditRole);
}

View File

@ -0,0 +1,21 @@
#ifndef FREQUENCY_DELTA_DELEGATE_HPP_
#define FREQUENCY_DELTA_DELEGATE_HPP_
#include <QStyledItemDelegate>
//
// Class FrequencyDeltaDelegate
//
// Item delegate for editing a frequency delta in Hertz but displayed in MHz
//
class FrequencyDeltaDelegate final
: public QStyledItemDelegate
{
public:
explicit FrequencyDeltaDelegate (QObject * parent = nullptr);
QWidget * createEditor (QWidget * parent, QStyleOptionViewItem const&, QModelIndex const&) const override;
void setEditorData (QWidget * editor, QModelIndex const&) const override;
void setModelData (QWidget * editor, QAbstractItemModel *, QModelIndex const&) const override;
};
#endif

View File

@ -1,12 +1,13 @@
SOURCES += \
item_delegates/ForeignKeyDelegate.cpp \
item_delegates/FrequencyItemDelegate.cpp \
item_delegates/FrequencyDelegate.cpp \
item_delegates/FrequencyDeltaDelegate.cpp \
item_delegates/CallsignDelegate.cpp \
item_delegates/MaidenheadLocatorItemDelegate.cpp
HEADERS += \
item_delegates/ForeignKeyDelegate.hpp \
item_delegates/FrequencyItemDelegate.hpp \
item_delegates/FrequencyDelegate.hpp \
item_delegates/FrequencyDeltaDelegate.hpp \
item_delegates/CallsignDelegate.hpp \
item_delegates/MaidenheadLocatorDelegate.hpp \
item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp
item_delegates/MaidenheadLocatorDelegate.hpp

View File

@ -22,15 +22,10 @@ i3.n3 Example message Bits Total Purpose
2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest
3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup
4 <WA9XYZ> PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls
5 ... tbd
5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ?
6 ... tbd
7 ... tbd
----------------------------------------------------------------------------------
In case we need them, later:
5 TU; W9XYZ K1ABC R 579 8 MA 1 28 28 1 3 6 7 74 CQ WW RTTY
6 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 CQ WPX RTTY
----------------------------------------------------------------------------------
NB: three 74-bit message types and two 71-bit message subtypes are still TBD.
----------------------------------------------------------------------------------

View File

@ -72,7 +72,18 @@ CQ W9XYZ EN37
W9XYZ <YW18FIFA> R-09
YW18FIFA <W9XYZ> RRR
<W9XYZ> YW18FIFA 73
10. Other stuff
10. WWROF FT8/FT4 contest
-----------------------------------------------------------
CQ TEST K1ABC FN42
K1ABC W9XYZ -16 EN
W9XYZ K1ABC R-07 FN
K1ABC W9XYZ RR73
K1ABC G3AAA -11 IO
TU; G3AAA K1ABC R-09 FN
K1ABC G3AAA RR73
11. Other stuff
-----------------------------------------------------------
TNX BOB 73 GL
CQ YW18FIFA

View File

@ -172,6 +172,10 @@ subroutine pack77(msg0,i3,n3,c77)
call pack77_4(nwords,w,i3,n3,c77)
if(i3.ge.0) go to 900
! Check Type 5 (WWROF contest exchange)
call pack77_5(nwords,w,i3,n3,c77)
if(i3.ge.0) go to 900
! It defaults to free text
800 i3=0
n3=0
@ -204,6 +208,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
character*6 cexch,grid6
character*4 grid4,cserial
character*3 csec(NSEC)
character*2 cfield
character*38 c
integer hashmy10,hashmy12,hashmy22,hashdx10,hashdx12,hashdx22
logical unpk28_success,unpk77_success
@ -491,8 +496,31 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
else
msg='CQ '//trim(call_2)
endif
else if(i3.eq.5) then
! 5 TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
read(c77,1041) itu,n28a,n28b,ir,irpt,nexch,i3
1041 format(b1,2b28.28,b1,b7.7,b9.9,b3.3)
call unpack28(n28a,call_1,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
call unpack28(n28b,call_2,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
write(crpt,'(i3.2)') irpt-35
if(crpt(1:1).eq.' ') crpt(1:1)='+'
n1=nexch/18
n2=nexch - 18*n1
cfield(1:1)=char(ichar('A')+n1)
cfield(2:2)=char(ichar('A')+n2)
if(itu.eq.0 .and. ir.eq.0) msg=trim(call_1)//' '//trim(call_2)// &
' '//crpt//' '//cfield
if(itu.eq.1 .and. ir.eq.0) msg='TU; '//trim(call_1)//' '//trim(call_2)// &
' '//crpt//' '//cfield
if(itu.eq.0 .and. ir.eq.1) msg=trim(call_1)//' '//trim(call_2)// &
' R'//crpt//' '//cfield
if(itu.eq.1 .and. ir.eq.1) msg='TU; '//trim(call_1)//' '//trim(call_2)// &
' R'//crpt//' '//cfield
endif
if(msg(1:4).eq.'CQ <') unpk77_success=.false.
! if(msg(1:4).eq.'CQ <') unpk77_success=.false.
return
end subroutine unpack77
@ -1040,12 +1068,11 @@ subroutine pack77_3(nwords,w,i3,n3,c77)
call chkcall(w(i1+1),bcall_2,ok2)
if(.not.ok1 .or. .not.ok2) go to 900
crpt=w(nwords-1)(1:3)
if(index(crpt,'-').ge.1 .or. index(crpt,'+').ge.1) go to 900
if(crpt(1:1).eq.'5' .and. crpt(2:2).ge.'2' .and. crpt(2:2).le.'9' .and. &
crpt(3:3).eq.'9') then
nserial=0
read(w(nwords),*,err=1) nserial
!1 i3=3
! n3=0
endif
1 mult=' '
imult=-1
@ -1150,6 +1177,60 @@ subroutine pack77_4(nwords,w,i3,n3,c77)
900 return
end subroutine pack77_4
subroutine pack77_5(nwords,w,i3,n3,c77)
! Check Type 5 (WWROF contest exchange)
character*13 w(19)
character*77 c77
character*6 bcall_1,bcall_2
character*3 mult
character crpt*4
character c1*1,c2*2
logical ok1,ok2
if(nwords.eq.4 .or. nwords.eq.5 .or. nwords.eq.6) then
i1=1
if(trim(w(1)).eq.'TU;') i1=2
call chkcall(w(i1),bcall_1,ok1)
call chkcall(w(i1+1),bcall_2,ok2)
if(.not.ok1 .or. .not.ok2) go to 900
crpt=w(nwords-1)(1:4)
if(index(crpt,'-').lt.1 .and. index(crpt,'+').lt.1) go to 900
c1=crpt(1:1)
c2=crpt(1:2)
irpt=-1
if(c1.eq.'+' .or. c1.eq.'-') then
ir=0
read(w(nwords-1),*,err=900) irpt
irpt=irpt+35
else if(c2.eq.'R+' .or. c2.eq.'R-') then
ir=1
read(w(nwords-1)(2:),*) irpt
irpt=irpt+35
endif
if(irpt.eq.-1 .or. len(trim(w(nwords))).ne.2) go to 900
c2=w(nwords)(1:2)
n1=ichar(c2(1:1)) - ichar('A')
n2=ichar(c2(2:2)) - ichar('A')
if(n1.lt.0 .or. n1.gt.17) go to 900
if(n2.lt.0 .or. n2.gt.17) go to 900
nexch=18*n1 + n2
i3=5
n3=0
itu=0
if(trim(w(1)).eq.'TU;') itu=1
call pack28(w(1+itu),n28a)
call pack28(w(2+itu),n28b)
! 5 TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
write(c77,1010) itu,n28a,n28b,ir,irpt,nexch,i3
1010 format(b1,2b28.28,b1,b7.7,b9.9,b3.3)
end if
900 return
end subroutine pack77_5
subroutine packtext77(c13,c71)
character*13 c13,w

View File

@ -1,55 +1,97 @@
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &
RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1,width2,bTx, &
AzElFileName,jpleph)
module astro_module
use, intrinsic :: iso_c_binding, only : c_int, c_double, c_bool, c_char, c_ptr, c_size_t, c_f_pointer
implicit none
implicit real*8 (a-h,o-z)
character*6 mygrid,hisgrid,c1*1
character*6 AzElFileName*(*),jpleph*(*)
character*256 jpleph_file_name
logical*1 bTx
common/jplcom/jpleph_file_name
private
public :: astrosub
jpleph_file_name=jpleph
contains
call astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &
dbMoon8,RAMoon8,DecMoon8,HA8,Dgrd8,sd8,poloffset8,xnr8,dfdt,dfdt0, &
width1,width2,xlst8,techo8)
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid_cp,mygrid_len, &
hisgrid_cp,hisgrid_len,AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8, &
ntsky,ndop,ndop00,RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1, &
width2,bTx,AzElFileName_cp,AzElFileName_len,jpleph_cp,jpleph_len) &
bind (C, name="astrosub")
if (len_trim(AzElFileName) .eq. 0) go to 999
imin=60*uth8
isec=3600*uth8
ih=uth8
im=mod(imin,60)
is=mod(isec,60)
open(15,file=AzElFileName,status='unknown',err=900)
c1='R'
nRx=1
if(bTx) then
c1='T'
nRx=0
endif
AzAux=0.
ElAux=0.
nfreq=freq8/1000000
doppler=ndop
doppler00=ndop00
write(15,1010,err=10) ih,im,is,AzMoon8,ElMoon8, &
ih,im,is,AzSun8,ElSun8, &
ih,im,is,AzAux,ElAux, &
nfreq,doppler,dfdt,doppler00,dfdt0,c1
! TXFirst,TRPeriod,poloffset,Dgrd,xnr,ave,rms,nRx
integer, parameter :: dp = selected_real_kind(15, 50)
integer(c_int), intent(in), value :: nyear, month, nday
real(c_double), intent(in), value :: uth8, freq8
real(c_double), intent(out) :: AzSun8, ElSun8, AzMoon8, ElMoon8, AzMoonB8, &
ElMoonB8, Ramoon8, DecMoon8, Dgrd8, poloffset8, xnr8, techo8, width1, &
width2
integer(c_int), intent(out) :: ntsky, ndop, ndop00
logical(c_bool), intent(in), value :: bTx
type(c_ptr), intent(in), value :: mygrid_cp, hisgrid_cp, AzElFileName_cp, jpleph_cp
integer(c_size_t), intent(in), value :: mygrid_len, hisgrid_len, AzElFileName_len, jpleph_len
character(len=6) :: mygrid, hisgrid
character(kind=c_char, len=:), allocatable :: AzElFileName
character(len=1) :: c1
integer :: ih, im, imin, is, isec, nfreq, nRx
real(dp) :: AzAux, ElAux, dbMoon8, dfdt, dfdt0, doppler, doppler00, HA8, sd8, xlst8
character*256 jpleph_file_name
common/jplcom/jpleph_file_name
block
character(kind=c_char, len=mygrid_len), pointer :: mygrid_fp
character(kind=c_char, len=hisgrid_len), pointer :: hisgrid_fp
character(kind=c_char, len=AzElFileName_len), pointer :: AzElFileName_fp
character(kind=c_char, len=jpleph_len), pointer :: jpleph_fp
call c_f_pointer(cptr=mygrid_cp, fptr=mygrid_fp)
mygrid = mygrid_fp
mygrid_fp => null()
call c_f_pointer(cptr=hisgrid_cp, fptr=hisgrid_fp)
hisgrid = hisgrid_fp
hisgrid_fp => null()
call c_f_pointer(cptr=AzElFileName_cp, fptr=AzElFileName_fp)
AzElFileName = AzElFileName_fp
AzElFileName_fp => null()
call c_f_pointer(cptr=jpleph_cp, fptr=jpleph_fp)
jpleph_file_name = jpleph_fp
jpleph_fp => null()
end block
call astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &
dbMoon8,RAMoon8,DecMoon8,HA8,Dgrd8,sd8,poloffset8,xnr8,dfdt,dfdt0, &
width1,width2,xlst8,techo8)
if (len_trim(AzElFileName) .eq. 0) go to 999
imin=60*uth8
isec=3600*uth8
ih=uth8
im=mod(imin,60)
is=mod(isec,60)
open(15,file=AzElFileName,status='unknown',err=900)
c1='R'
nRx=1
if(bTx) then
c1='T'
nRx=0
endif
AzAux=0.
ElAux=0.
nfreq=freq8/1000000
doppler=ndop
doppler00=ndop00
write(15,1010,err=10) ih,im,is,AzMoon8,ElMoon8, &
ih,im,is,AzSun8,ElSun8, &
ih,im,is,AzAux,ElAux, &
nfreq,doppler,dfdt,doppler00,dfdt0,c1
! TXFirst,TRPeriod,poloffset,Dgrd,xnr,ave,rms,nRx
1010 format( &
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Moon'/ &
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Sun'/ &
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Source'/ &
i5,',',f8.1,',',f8.2,',',f8.1,',',f8.2,',Doppler, ',a1)
! i1,',',i3,',',f8.1,','f8.1,',',f8.1,',',f12.3,',',f12.3,',',i1,',RPol')
10 close(15)
go to 999
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Moon'/ &
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Sun'/ &
i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Source'/ &
i5,',',f8.1,',',f8.2,',',f8.1,',',f8.2,',Doppler, ',a1)
! i1,',',i3,',',f8.1,','f8.1,',',f8.1,',',f12.3,',',f12.3,',',i1,',RPol')
10 close(15)
go to 999
900 print*,'Error opening azel.dat'
999 return
end subroutine astrosub
end subroutine astrosub
end module astro_module

View File

@ -1,7 +1,7 @@
subroutine azdist(grid1,grid2,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
subroutine azdist(MyGrid,HisGrid,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
character*(*) grid1,grid2
character*6 MyGrid,HisGrid,mygrid0,hisgrid0
character(len=*) :: MyGrid,HisGrid
character*6 mygrid0,hisgrid0
real*8 utch,utch0
logical HotABetter,IamEast
real eltab(22),daztab(22)
@ -12,11 +12,6 @@ subroutine azdist(grid1,grid2,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
data mygrid0/" "/,hisgrid0/" "/,utch0/-999.d0/
save
MyGrid=grid1//' '
HisGrid=grid2//' '
if(ichar(MyGrid(5:5)).eq.0) MyGrid(5:6)=' '
if(ichar(HisGrid(5:5)).eq.0) HisGrid(5:6)=' '
if(MyGrid.eq.HisGrid) then
naz=0
nel=0

View File

@ -133,8 +133,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
n30fox(j)=n
m=n30max-n
if(len(trim(g2fox(j))).eq.4) then
call azdist(mygrid,g2fox(j),0.d0,nAz,nEl,nDmiles,nDkm, &
nHotAz,nHotABetter)
call azdist(mygrid,g2fox(j)//' ',0.d0,nAz,nEl,nDmiles, &
nDkm,nHotAz,nHotABetter)
else
nDkm=9999
endif
@ -152,8 +152,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
if(params%nmode.eq.5) then
call timer('decft4 ',0)
call my_ft4%decode(ft4_decoded,id2,params%nQSOProgress,params%nfqso, &
params%nutc,params%nfa,params%nfb,params%ndepth,ncontest, &
mycall,hiscall)
params%nutc,params%nfa,params%nfb,params%ndepth, &
logical(params%lapcqonly),ncontest,mycall,hiscall)
call timer('decft4 ',1)
go to 800
endif
@ -517,7 +517,7 @@ contains
decoded0=decoded
annot=' '
if(ncontest.eq.0 .and. nap.ne.0) then
if(nap.ne.0) then
write(annot,'(a1,i1)') 'a',nap
if(qual.lt.0.17) decoded0(37:37)='?'
endif
@ -598,15 +598,15 @@ contains
decoded0=decoded
annot=' '
if(ncontest.eq.0 .and. nap.ne.0) then
if(nap.ne.0) then
write(annot,'(a1,i1)') 'a',nap
if(qual.lt.0.17) decoded0(37:37)='?'
endif
write(*,1001) params%nutc,snr,dt,nint(freq),decoded0,annot
1001 format(i6.6,i4,f5.1,i5,' ~ ',1x,a37,1x,a2)
1001 format(i6.6,i4,f5.1,i5,' + ',1x,a37,1x,a2)
write(13,1002) params%nutc,nint(sync),snr,dt,freq,0,decoded0
1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a37,' FT8')
1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a37,' FT4')
call flush(6)
call flush(13)

View File

@ -1,4 +1,4 @@
subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
subroutine fast_decode(id2,narg,trperiod,line,mycall_12, &
hiscall_12)
parameter (NMAX=30*12000)
@ -6,6 +6,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
integer*2 id2a(NMAX)
integer*2 id2b(NMAX)
integer narg(0:14)
double precision trperiod
real dat(30*12000)
complex cdat(262145),cdat2(262145)
real psavg(450)
@ -41,7 +42,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
nhashcalls=narg(12)
line(1:100)(1:1)=char(0)
if(t0.gt.float(ntrperiod)) go to 900
if(t0.gt.trperiod) go to 900
if(t0.gt.t1) go to 900
if(nmode.eq.102) then
@ -53,7 +54,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
cdat2=cdat
ndat=ndat0
call wav11(id2,ndat,dat)
nzz=11025*ntrperiod
nzz=11025*int(trperiod) !beware if fractional T/R period ever used here
if(ndat.lt.nzz) dat(ndat+1:nzz)=0.0
ndat=min(ndat,30*11025)
call ana932(dat,ndat,cdat,npts) !Make downsampled analytic signal

View File

@ -41,6 +41,7 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
endif
smax=0.
s=0.
ipk=-99
do i=ia,ib
s(i)=real(cx(i))**2 + aimag(cx(i))**2
if(s(i).gt.smax) then
@ -48,25 +49,32 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
ipk=i
endif
enddo
call peakup(s(ipk-1),s(ipk),s(ipk+1),dx)
fpeak=df * (ipk+dx)
ap=(fpeak/fs+1.0/(2.0*NFFT))
an=(fpeak/fs-1.0/(2.0*NFFT))
sp=sum(id2((k-NFFT):k-1)*cmplx(cos(xi*ap),-sin(xi*ap)))
sn=sum(id2((k-NFFT):k-1)*cmplx(cos(xi*an),-sin(xi*an)))
fpeak=fpeak+fs*(abs(sp)-abs(sn))/(abs(sp)+abs(sn))/(2*NFFT)
xsum=0.
nsum=0
do i=ia,ib
if(abs(i-ipk).gt.10) then
xsum=xsum+s(i)
nsum=nsum+1
endif
enddo
ave=xsum/nsum
snr=db(smax/ave)
pave=db(ave) + 8.0
if(ipk.ge.1) then
call peakup(s(ipk-1),s(ipk),s(ipk+1),dx)
fpeak=df * (ipk+dx)
ap=(fpeak/fs+1.0/(2.0*NFFT))
an=(fpeak/fs-1.0/(2.0*NFFT))
sp=sum(id2((k-NFFT):k-1)*cmplx(cos(xi*ap),-sin(xi*ap)))
sn=sum(id2((k-NFFT):k-1)*cmplx(cos(xi*an),-sin(xi*an)))
fpeak=fpeak+fs*(abs(sp)-abs(sn))/(abs(sp)+abs(sn))/(2*NFFT)
xsum=0.
nsum=0
do i=ia,ib
if(abs(i-ipk).gt.10) then
xsum=xsum+s(i)
nsum=nsum+1
endif
enddo
ave=xsum/nsum
snr=db(smax/ave)
pave=db(ave) + 8.0
else
snr=-99.9
pave=-99.9
fpeak=-99.9
ferr=-99.9
endif
cflag=' '
if(snr.lt.20.0) cflag='*'
n=n+1
@ -77,7 +85,7 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
ncal=1
ferr=fpeak-noffset
write(line,1100) nhr,nmin,nsec,nkhz,ncal,noffset,fpeak,ferr,pave, &
snr,cflag,char(0)
snr,cflag,char(0)
1100 format(i2.2,':',i2.2,':',i2.2,i7,i3,i6,2f10.3,2f7.1,2x,a1,a1)
900 return

64
lib/ft4/averaged_mf.f90 Normal file
View File

@ -0,0 +1,64 @@
program averaged_mf
parameter (nsps=32)
complex cgfsk(3*nsps,64)
complex clin(3*nsps,64)
complex cavg(3*nsps,4)
complex cavl(3*nsps,4)
real pulse(3*nsps)
real dphi(3*nsps)
do i=1,3*NSPS
t=(i-1.5*nsps)/real(nsps)
pulse(i)=gfsk_pulse(1.0,t)
enddo
twopi=8.0*atan(1.0)
hmod=1.0
dphi_peak=twopi*hmod/real(nsps)
do iwf=1,64
i0=mod((iwf-1)/16,4)
i1=mod((iwf-1)/4,4)
i2=mod(iwf-1,4)
dphi=0.0
dphi(1:64)=dphi_peak*pulse(33:96)*i1
dphi(1:96)=dphi(1:96)+dphi_peak*pulse(1:96)*i0
dphi(33:96)=dphi(33:96)+dphi_peak*pulse(1:64)*i2
phi=0.0
do j=1,96
cgfsk(j,iwf)=cmplx(cos(phi),sin(phi))
phi=mod(phi+dphi(j),twopi)
enddo
cgfsk(:,iwf)=cgfsk(:,iwf)*conjg(cgfsk(48,iwf))
enddo
do iwf=1,64
i0=mod((iwf-1)/16,4)
i1=mod((iwf-1)/4,4)
i2=mod(iwf-1,4)
dphi=0.0
dphi(1:32)=dphi_peak*i1
dphi(33:64)=dphi_peak*i0
dphi(65:96)=dphi_peak*i2
phi=0.0
do j=1,96
clin(j,iwf)=cmplx(cos(phi),sin(phi))
phi=mod(phi+dphi(j),twopi)
enddo
enddo
do i=1,4
ib=(i-1)*16+1
ie=ib+15
cavg(:,i)=sum(cgfsk(:,ib:ie),2)/16.0
cavl(:,i)=sum(clin(:,ib:ie),2)/16.0
do j=1,96
write(*,*) j
write(21,*) i,j,real(cavg(j,i)),imag(cavg(j,i)),real(cavl(j,i)),imag(cavl(j,i))
enddo
enddo
end program averaged_mf

49
lib/ft4/ft4_baseline.f90 Normal file
View File

@ -0,0 +1,49 @@
subroutine ft4_baseline(s,nfa,nfb,sbase)
! Fit baseline to spectrum
! Input: s(npts) Linear scale in power
! Output: sbase(npts) Baseline
include 'ft4_params.f90'
implicit real*8 (a-h,o-z)
real*4 s(NH1)
real*4 sbase(NH1)
real*4 base
real*8 x(1000),y(1000),a(5)
data nseg/10/,npct/10/
df=12000.0/NFFT1 !5.21 Hz
ia=max(nint(200.0/df),nfa)
ib=min(NH1,nfb)
do i=ia,ib
s(i)=10.0*log10(s(i)) !Convert to dB scale
enddo
nterms=5
nlen=(ib-ia+1)/nseg !Length of test segment
i0=(ib-ia+1)/2 !Midpoint
k=0
do n=1,nseg !Loop over all segments
ja=ia + (n-1)*nlen
jb=ja+nlen-1
call pctile(s(ja),nlen,npct,base) !Find lowest npct of points
do i=ja,jb
if(s(i).le.base) then
if (k.lt.1000) k=k+1 !Save all "lower envelope" points
x(k)=i-i0
y(k)=s(i)
endif
enddo
enddo
kz=k
a=0.
call polyfit(x,y,y,kz,nterms,0,a,chisqr) !Fit a low-order polynomial
do i=ia,ib
t=i-i0
sbase(i)=a(1)+t*(a(2)+t*(a(3)+t*(a(4)+t*(a(5))))) + 0.65
! write(51,3051) i*df,s(i),sbase(i)
!3051 format(3f12.3)
sbase(i)=10**(sbase(i)/10.0)
enddo
return
end subroutine ft4_baseline

View File

@ -1,11 +1,8 @@
subroutine ft4_downsample(iwave,newdata,f0,c)
! Input: i*2 data in iwave() at sample rate 12000 Hz
! Output: Complex data in c(), sampled at 1200 Hz
subroutine ft4_downsample(dd,newdata,f0,c)
include 'ft4_params.f90'
parameter (NFFT2=NMAX/16)
integer*2 iwave(NMAX)
parameter (NFFT2=NMAX/NDOWN)
real dd(NMAX)
complex c(0:NMAX/NDOWN-1)
complex c1(0:NFFT2-1)
complex cx(0:NMAX/2)
@ -33,15 +30,15 @@ subroutine ft4_downsample(iwave,newdata,f0,c)
endif
if(newdata) then
x=iwave
x=dd
call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain
endif
i0=nint(f0/df)
c1=0.
c1(0)=cx(i0)
if(i0.ge.0 .and. i0.le.NMAX/2) c1(0)=cx(i0)
do i=1,NFFT2/2
if(i0+i.le.NMAX/2) c1(i)=cx(i0+i)
if(i0-i.ge.0) c1(NFFT2-i)=cx(i0-i)
if(i0+i.ge.0 .and. i0+i.le.NMAX/2) c1(i)=cx(i0+i)
if(i0-i.ge.0 .and. i0-i.le.NMAX/2) c1(NFFT2-i)=cx(i0-i)
enddo
c1=c1*window/NFFT2
call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain

View File

@ -1,16 +1,16 @@
! FT4
! LDPC(174,91) code, four 4x4 Costas arrays for Sync
! LDPC(174,91) code, four 4x4 Costas arrays for sync, ramp-up and ramp-down symbols
parameter (KK=91) !Information bits (77 + CRC14)
parameter (ND=87) !Data symbols
parameter (NS=16) !Sync symbols
parameter (NN=NS+ND) !Sync and data symbols (103)
parameter (NN2=NS+ND+2) !Total channel symbols (105)
parameter (NSPS=512) !Samples per symbol at 12000 S/s
parameter (NZ=NSPS*NN) !Sync and Data samples (52736)
parameter (NZ2=NSPS*NN2) !Total samples in shaped waveform (53760)
parameter (NMAX=5*12000) !Samples in iwave (60,000)
parameter (NFFT1=2048, NH1=NFFT1/2) !Length of FFTs for symbol spectra
parameter (NSPS=576) !Samples per symbol at 12000 S/s
parameter (NZ=NSPS*NN) !Sync and Data samples (59328)
parameter (NZ2=NSPS*NN2) !Total samples in shaped waveform (60480)
parameter (NMAX=21*3456) !Samples in iwave (72576)
parameter (NFFT1=2304, NH1=NFFT1/2) !Length of FFTs for symbol spectra
parameter (NSTEP=NSPS) !Coarse time-sync step size
parameter (NHSYM=(NMAX-NFFT1)/NSTEP) !Number of symbol spectra (1/4-sym steps)
parameter (NDOWN=16) !Downsample factor
parameter (NDOWN=18) !Downsample factor

View File

@ -1,489 +0,0 @@
subroutine ft4b(cdatetime0,tbuf,nfa,nfb,nQSOProgress,ncontest,nfqso, &
iwave,ndecodes,mycall,hiscall,cqstr,line,data_dir)
use packjt77
include 'ft4_params.f90'
parameter (NSS=NSPS/NDOWN)
character message*37,msgsent*37,msg0*37
character c77*77
character*61 line,linex(100)
character*37 decodes(100)
character*512 data_dir,fname
character*17 cdatetime0
character*12 mycall,hiscall
character*12 mycall0,hiscall0
character*6 hhmmss
character*4 cqstr,cqstr0
complex cd2(0:NMAX/NDOWN-1) !Complex waveform
complex cb(0:NMAX/NDOWN-1)
complex cd(0:NN*NSS-1) !Complex waveform
complex ctwk(2*NSS),ctwk2(2*NSS,-16:16)
complex csymb(NSS)
complex cs(0:3,NN)
real s4(0:3,NN)
real bmeta(2*NN),bmetb(2*NN),bmetc(2*NN)
real a(5)
real llr(2*ND),llra(2*ND),llrb(2*ND),llrc(2*ND),llrd(2*ND)
real s2(0:255)
real candidate(3,100)
real savg(NH1),sbase(NH1)
integer apbits(2*ND)
integer apmy_ru(28),aphis_fd(28)
integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3)
integer*2 iwave(NMAX) !Raw received data
integer*1 message77(77),rvec(77),apmask(2*ND),cw(2*ND)
integer*1 hbits(2*NN)
integer graymap(0:3)
integer ip(1)
integer nappasses(0:5) ! # of decoding passes for QSO States 0-5
integer naptypes(0:5,4) ! nQSOProgress, decoding pass
integer mcq(29)
integer mrrr(19),m73(19),mrr73(19)
logical nohiscall,unpk77_success
logical one(0:255,0:7) ! 256 4-symbol sequences, 8 bits
logical first, dobigfft
data icos4a/0,1,3,2/
data icos4b/1,0,2,3/
data icos4c/2,3,1,0/
data icos4d/3,2,0,1/
data graymap/0,1,3,2/
data msg0/' '/
data first/.true./
data mcq/0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0/
data mrrr/0,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,1/
data m73/0,1,1,1,1,1,1,0,1,0,0,1,0,1,0,0,0,0,1/
data mrr73/0,1,1,1,1,1,1,0,0,1,1,1,0,1,0,1,0,0,1/
data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, &
1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, &
0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/
save fs,dt,tt,txt,twopi,h,one,first,linex,apbits,nappasses,naptypes, &
mycall0,hiscall0,msg0,cqstr0,ctwk2
call clockit('ft4_deco',0)
hhmmss=cdatetime0(8:13)
if(first) then
fs=12000.0/NDOWN !Sample rate after downsampling
dt=1/fs !Sample interval after downsample (s)
tt=NSPS*dt !Duration of "itone" symbols (s)
txt=NZ*dt !Transmission length (s) without ramp up/down
twopi=8.0*atan(1.0)
h=1.0
one=.false.
do i=0,255
do j=0,7
if(iand(i,2**j).ne.0) one(i,j)=.true.
enddo
enddo
do idf=-16,16
a=0.
a(1)=real(idf)
ctwk=1.
call clockit('twkfreq1',0)
call twkfreq1(ctwk,2*NSS,fs/2.0,a,ctwk2(:,idf))
call clockit('twkfreq1',1)
enddo
mrrr=2*mod(mrrr+rvec(59:77),2)-1
m73=2*mod(m73+rvec(59:77),2)-1
mrr73=2*mod(mrr73+rvec(59:77),2)-1
nappasses(0)=2
nappasses(1)=2
nappasses(2)=2
nappasses(3)=2
nappasses(4)=2
nappasses(5)=3
! iaptype
!------------------------
! 1 CQ ??? ??? (29 ap bits)
! 2 MyCall ??? ??? (29 ap bits)
! 3 MyCall DxCall ??? (58 ap bits)
! 4 MyCall DxCall RRR (77 ap bits)
! 5 MyCall DxCall 73 (77 ap bits)
! 6 MyCall DxCall RR73 (77 ap bits)
!********
naptypes(0,1:4)=(/1,2,0,0/) ! Tx6 selected (CQ)
naptypes(1,1:4)=(/2,3,0,0/) ! Tx1
naptypes(2,1:4)=(/2,3,0,0/) ! Tx2
naptypes(3,1:4)=(/3,6,0,0/) ! Tx3
naptypes(4,1:4)=(/3,6,0,0/) ! Tx4
naptypes(5,1:4)=(/3,1,2,0/) ! Tx5
mycall0=''
hiscall0=''
cqstr0=''
first=.false.
endif
if(cqstr.ne.cqstr0) then
i0=index(cqstr,' ')
if(i0.le.1) then
message='CQ A1AA AA01'
else
message='CQ '//cqstr(1:i0-1)//' A1AA AA01'
endif
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
read(c77,'(29i1)') mcq
mcq=2*mod(mcq+rvec(1:29),2)-1
cqstr0=cqstr
endif
l1=index(mycall,char(0))
if(l1.ne.0) mycall(l1:)=" "
l1=index(hiscall,char(0))
if(l1.ne.0) hiscall(l1:)=" "
if(mycall.ne.mycall0 .or. hiscall.ne.hiscall0) then
apbits=0
apbits(1)=99
apbits(30)=99
apmy_ru=0
aphis_fd=0
if(len(trim(mycall)) .lt. 3) go to 10
nohiscall=.false.
hiscall0=hiscall
if(len(trim(hiscall0)).lt.3) then
hiscall0=mycall ! use mycall for dummy hiscall - mycall won't be hashed.
nohiscall=.true.
endif
message=trim(mycall)//' '//trim(hiscall0)//' RR73'
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
if(i3.ne.1 .or. (message.ne.msgsent) .or. .not.unpk77_success) go to 10
read(c77,'(77i1)') message77
apmy_ru=2*mod(message77(1:28)+rvec(2:29),2)-1
aphis_fd=2*mod(message77(30:57)+rvec(29:56),2)-1
message77=mod(message77+rvec,2)
call encode174_91(message77,cw)
apbits=2*cw-1
if(nohiscall) apbits(30)=99
10 continue
mycall0=mycall
hiscall0=hiscall
endif
candidate=0.0
ncand=0
syncmin=1.2
maxcand=100
fa=nfa
fb=nfb
call clockit('getcand4',0)
call getcandidates4(iwave,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
ncand,sbase)
call clockit('getcand4',1)
ndecodes=0
dobigfft=.true.
do icand=1,ncand
f0=candidate(1,icand)
snr=candidate(3,icand)-1.0
if( f0.le.10.0 .or. f0.ge.4990.0 ) cycle
call clockit('ft4_down',0)
call ft4_downsample(iwave,dobigfft,f0,cd2) !Downsample from 512 to 32 Sa/Symbol
if(dobigfft) dobigfft=.false.
call clockit('ft4_down',1)
sum2=sum(cd2*conjg(cd2))/(real(NMAX)/real(NDOWN))
if(sum2.gt.0.0) cd2=cd2/sqrt(sum2)
! Sample rate is now 12000/16 = 750 samples/second
do isync=1,2
if(isync.eq.1) then
idfmin=-12
idfmax=12
idfstp=3
ibmin=0
ibmax=216 !Max DT = 216/750 = 0.288 s
ibstp=4
else
idfmin=idfbest-4
idfmax=idfbest+4
idfstp=1
ibmin=max(0,ibest-5)
ibmax=min(ibest+5,NMAX/NDOWN-1)
ibstp=1
endif
ibest=-1
smax=-99.
idfbest=0
do idf=idfmin,idfmax,idfstp
call clockit('sync4d ',0)
do istart=ibmin,ibmax,ibstp
call sync4d(cd2,istart,ctwk2(:,idf),1,sync) !Find sync power
if(sync.gt.smax) then
smax=sync
ibest=istart
idfbest=idf
endif
enddo
call clockit('sync4d ',1)
enddo
enddo
f0=f0+real(idfbest)
if( f0.le.10.0 .or. f0.ge.4990.0 ) cycle
call clockit('ft4down ',0)
call ft4_downsample(iwave,dobigfft,f0,cb) !Final downsample with corrected f0
call clockit('ft4down ',1)
sum2=sum(abs(cb)**2)/(real(NSS)*NN)
if(sum2.gt.0.0) cb=cb/sqrt(sum2)
cd=cb(ibest:ibest+NN*NSS-1)
call clockit('four2a ',0)
do k=1,NN
i1=(k-1)*NSS
csymb=cd(i1:i1+NSS-1)
call four2a(csymb,NSS,1,-1,1)
cs(0:3,k)=csymb(1:4)
s4(0:3,k)=abs(csymb(1:4))
enddo
call clockit('four2a ',1)
! Sync quality check
is1=0
is2=0
is3=0
is4=0
do k=1,4
ip=maxloc(s4(:,k))
if(icos4a(k-1).eq.(ip(1)-1)) is1=is1+1
ip=maxloc(s4(:,k+33))
if(icos4b(k-1).eq.(ip(1)-1)) is2=is2+1
ip=maxloc(s4(:,k+66))
if(icos4c(k-1).eq.(ip(1)-1)) is3=is3+1
ip=maxloc(s4(:,k+99))
if(icos4d(k-1).eq.(ip(1)-1)) is4=is4+1
enddo
nsync=is1+is2+is3+is4 !Number of correct hard sync symbols, 0-16
if(smax .lt. 0.7 .or. nsync .lt. 8) cycle
do nseq=1,3 !Try coherent sequences of 1, 2, and 4 symbols
if(nseq.eq.1) nsym=1
if(nseq.eq.2) nsym=2
if(nseq.eq.3) nsym=4
nt=2**(2*nsym)
do ks=1,NN-nsym+1,nsym !87+16=103 symbols.
amax=-1.0
do i=0,nt-1
i1=i/64
i2=iand(i,63)/16
i3=iand(i,15)/4
i4=iand(i,3)
if(nsym.eq.1) then
s2(i)=abs(cs(graymap(i4),ks))
elseif(nsym.eq.2) then
s2(i)=abs(cs(graymap(i3),ks)+cs(graymap(i4),ks+1))
elseif(nsym.eq.4) then
s2(i)=abs(cs(graymap(i1),ks ) + &
cs(graymap(i2),ks+1) + &
cs(graymap(i3),ks+2) + &
cs(graymap(i4),ks+3) &
)
else
print*,"Error - nsym must be 1, 2, or 4."
endif
enddo
ipt=1+(ks-1)*2
if(nsym.eq.1) ibmax=1
if(nsym.eq.2) ibmax=3
if(nsym.eq.4) ibmax=7
do ib=0,ibmax
bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - &
maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib))
if(ipt+ib.gt.2*NN) cycle
if(nsym.eq.1) then
bmeta(ipt+ib)=bm
elseif(nsym.eq.2) then
bmetb(ipt+ib)=bm
elseif(nsym.eq.4) then
bmetc(ipt+ib)=bm
endif
enddo
enddo
enddo
bmetb(205:206)=bmeta(205:206)
bmetc(201:204)=bmetb(201:204)
bmetc(205:206)=bmeta(205:206)
call clockit('normaliz',0)
call normalizebmet(bmeta,2*NN)
call normalizebmet(bmetb,2*NN)
call normalizebmet(bmetc,2*NN)
call clockit('normaliz',1)
hbits=0
where(bmeta.ge.0) hbits=1
ns1=count(hbits( 1: 8).eq.(/0,0,0,1,1,0,1,1/))
ns2=count(hbits( 67: 74).eq.(/0,1,0,0,1,1,1,0/))
ns3=count(hbits(133:140).eq.(/1,1,1,0,0,1,0,0/))
ns4=count(hbits(199:206).eq.(/1,0,1,1,0,0,0,1/))
nsync_qual=ns1+ns2+ns3+ns4
if(nsync_qual.lt. 20) cycle
scalefac=2.83
llra( 1: 58)=bmeta( 9: 66)
llra( 59:116)=bmeta( 75:132)
llra(117:174)=bmeta(141:198)
llra=scalefac*llra
llrb( 1: 58)=bmetb( 9: 66)
llrb( 59:116)=bmetb( 75:132)
llrb(117:174)=bmetb(141:198)
llrb=scalefac*llrb
llrc( 1: 58)=bmetc( 9: 66)
llrc( 59:116)=bmetc( 75:132)
llrc(117:174)=bmetc(141:198)
llrc=scalefac*llrc
apmag=maxval(abs(llra))*1.1
npasses=3+nappasses(nQSOProgress)
if(ncontest.ge.5) npasses=3 ! Don't support Fox and Hound
do ipass=1,npasses
if(ipass.eq.1) llr=llra
if(ipass.eq.2) llr=llrb
if(ipass.eq.3) llr=llrc
if(ipass.le.3) then
apmask=0
iaptype=0
endif
if(ipass .gt. 3) then
llrd=llrc
iaptype=naptypes(nQSOProgress,ipass-3)
! ncontest=0 : NONE
! 1 : NA_VHF
! 2 : EU_VHF
! 3 : FIELD DAY
! 4 : RTTY
! 5 : FOX
! 6 : HOUND
!
! Conditions that cause us to bail out of AP decoding
napwid=50
if(ncontest.le.4 .and. iaptype.ge.3 .and. (abs(f0-nfqso).gt.napwid) ) cycle
if(iaptype.ge.2 .and. apbits(1).gt.1) cycle ! No, or nonstandard, mycall
if(iaptype.ge.3 .and. apbits(30).gt.1) cycle ! No, or nonstandard, dxcall
if(iaptype.eq.1) then ! CQ or CQ TEST or CQ FD or CQ RU or CQ SCC
apmask=0
apmask(1:29)=1
llrd(1:29)=apmag*mcq(1:29)
endif
if(iaptype.eq.2) then ! MyCall,???,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1) then
apmask(1:29)=1
llrd(1:29)=apmag*apbits(1:29)
else if(ncontest.eq.2) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.3) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.4) then
apmask(2:29)=1
llrd(2:29)=apmag*apmy_ru(1:28)
endif
endif
if(iaptype.eq.3) then ! MyCall,DxCall,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1.or.ncontest.eq.2) then
apmask(1:58)=1
llrd(1:58)=apmag*apbits(1:58)
else if(ncontest.eq.3) then ! Field Day
apmask(1:56)=1
llrd(1:28)=apmag*apbits(1:28)
llrd(29:56)=apmag*aphis_fd(1:28)
else if(ncontest.eq.4) then ! RTTY RU
apmask(2:57)=1
llrd(2:29)=apmag*apmy_ru(1:28)
llrd(30:57)=apmag*apbits(30:57)
endif
endif
if(iaptype.eq.4 .or. iaptype.eq.5 .or. iaptype.eq.6) then
apmask=0
if(ncontest.le.4) then
apmask(1:91)=1 ! mycall, hiscall, RRR|73|RR73
if(iaptype.eq.6) llrd(1:91)=apmag*apbits(1:91)
endif
endif
llr=llrd
endif
max_iterations=40
message77=0
call clockit('bpdecode',0)
call bpdecode174_91(llr,apmask,max_iterations,message77, &
cw,nharderror,niterations)
call clockit('bpdecode',1)
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
message77=mod(message77+rvec,2) ! remove rvec scrambling
write(c77,'(77i1)') message77(1:77)
call unpack77(c77,1,message,unpk77_success)
idupe=0
do i=1,ndecodes
if(decodes(i).eq.message) idupe=1
enddo
if(ibest.le.10 .and. message.eq.msg0) idupe=1 !Already decoded
if(idupe.eq.1) exit
ndecodes=ndecodes+1
decodes(ndecodes)=message
if(snr.gt.0.0) then
xsnr=10*log10(snr)-14.0
else
xsnr=-20.0
endif
nsnr=nint(max(-20.0,xsnr))
freq=f0
tsig=mod(tbuf + ibest/750.0,100.0)
write(line,1000) hhmmss,nsnr,tsig,nint(freq),message
1000 format(a6,i4,f5.1,i5,' + ',1x,a37)
l1=index(data_dir,char(0))-1
if(l1.ge.1) data_dir(l1+1:l1+1)="/"
fname=data_dir(1:l1+1)//'all_ft4.txt'
open(24,file=trim(fname),status='unknown',position='append')
write(24,1002) cdatetime0,nsnr,tsig,nint(freq),message, &
nharderror,nsync_qual,ipass,niterations,iaptype,nsync
if(hhmmss.eq.' ') write(*,1002) cdatetime0,nsnr, &
tsig,nint(freq),message,nharderror,nsync_qual,ipass, &
niterations,iaptype
1002 format(a17,i4,f5.1,i5,' Rx ',a37,6i4)
close(24)
linex(ndecodes)=line
if(ibest.ge.ibmax-15) msg0=message !Possible dupe candidate
exit
endif
enddo !Sequence estimation
enddo !Candidate list
call clockit('ft4_deco',1)
call clockit2(data_dir)
call clockit('ft4_deco',101)
return
entry get_ft4msg(idecode,line)
line=linex(idecode)
return
end subroutine ft4b

View File

@ -1,83 +0,0 @@
program ft4d
include 'ft4_params.f90'
character*8 arg
character*17 cdatetime
character*512 data_dir
character*12 mycall
character*12 hiscall
character*80 infile
character*61 line
character*4 cqstr
real*8 fMHz
integer ihdr(11)
integer*2 iwave(240000) !20*12000
fs=12000.0/NDOWN !Sample rate
dt=1/fs !Sample interval after downsample (s)
tt=NSPS*dt !Duration of "itone" symbols (s)
baud=1.0/tt !Keying rate for "itone" symbols (baud)
txt=NZ*dt !Transmission length (s)
nargs=iargc()
if(nargs.lt.1) then
print*,'Usage: ft4d [-a <data_dir>] [-f fMHz] [-n nQSOProgress] file1 [file2 ...]'
go to 999
endif
iarg=1
data_dir="."
call getarg(iarg,arg)
if(arg(1:2).eq.'-a') then
call getarg(iarg+1,data_dir)
iarg=iarg+2
endif
call getarg(iarg,arg)
if(arg(1:2).eq.'-f') then
call getarg(iarg+1,arg)
read(arg,*) fMHz
iarg=iarg+2
endif
nQSOProgress=0
if(arg(1:2).eq.'-n') then
call getarg(iarg+1,arg)
read(arg,*) nQSOProgress
iarg=iarg+2
endif
nfa=10
nfb=4990
ndecodes=0
nfqso=1500
mycall="K9AN"
hiscall="K1JT"
ncontest=4
cqstr="RU "
do ifile=iarg,nargs
call getarg(ifile,infile)
open(10,file=infile,status='old',access='stream')
read(10) ihdr
npts=min(ihdr(11)/2,180000)
read(10) iwave(1:npts)
close(10)
cdatetime=infile
j2=index(infile,'.wav')
if(j2.ge.14) cdatetime=infile(j2-13:j2)//'000'
istep=3456
nsteps=(npts-52800)/istep + 1
do n=1,nsteps
i0=(n-1)*istep + 1
tbuf=(i0-1)/12000.0
call ft4b(cdatetime,tbuf,nfa,nfb,nQSOProgress,ncontest, &
nfqso,iwave(i0),ndecodes,mycall,hiscall,cqstr,line,data_dir)
do idecode=1,ndecodes
call get_ft4msg(idecode,line)
write(*,'(a61)') line
enddo
enddo !steps
enddo !files
call four2a(xx,-1,1,-1,1) !Destroy FFTW plans to free their memory
999 end program ft4d

View File

@ -6,7 +6,7 @@ program ft4sim
use packjt77
include 'ft4_params.f90' !Set various constants
parameter (NWAVE=NN*NSPS)
parameter (NZZ=18*3456) !62208
parameter (NZZ=21*3456) !72576
type(hdr) h !Header for .wav file
character arg*12,fname*17
character msg37*37,msgsent37*37
@ -51,19 +51,18 @@ program ft4sim
hmod=1.0 !Modulation index (0.5 is MSK, 1.0 is FSK)
tt=NSPS*dt !Duration of symbols (s)
baud=1.0/tt !Keying rate (baud)
txt=NZ*dt !Transmission length (s)
txt=NZ2*dt !Transmission length (s)
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
txt=NN*NSPS/12000.0
! Source-encode, then get itone()
i3=-1
n3=-1
call pack77(msg37,i3,n3,c77)
read(c77,'(77i1)') msgbits
call genft4(msg37,0,msgsent37,itone)
call genft4(msg37,0,msgsent37,msgbits,itone)
write(*,*)
write(*,'(a9,a37,3x,a7,i1,a1,i1)') 'Message: ',msgsent37,'i3.n3: ',i3,'.',n3
write(*,1000) f0,xdt,txt,snrdb
@ -111,8 +110,10 @@ program ft4sim
c0((NN+1)*NSPS:(NN+2)*NSPS-1)=c0((NN+1)*NSPS:(NN+2)*NSPS-1)*(1.0+cos(twopi*(/(i,i=0,NSPS-1)/)/(2.0*NSPS) ))/2.0
c0((NN+2)*NSPS:)=0.
k=nint((xdt+0.5)/dt)
k=nint((xdt+0.5)/dt)-NSPS
c0=cshift(c0,-k)
if(k.gt.0) c0(0:k-1)=0.0
if(k.lt.0) c0(NZZ+k:NZZ-1)=0.0
do ifile=1,nfiles
c=c0

View File

@ -6,17 +6,17 @@ program ft4sim_mult
use packjt77
include 'ft4_params.f90' !FT4 protocol constants
parameter (NWAVE=NN*NSPS)
parameter (NZZ=65760) !Length of .wav file (4.48+1.0)*12000
parameter (NZZ=72576) !Length of .wav file (21*3456)
type(hdr) h !Header for .wav file
character arg*12,fname*17,cjunk*4
character msg37*37,msgsent37*37,c77*77
complex cwave0((NN+2)*NSPS)
real wave0((NN+2)*NSPS)
real wave(NZZ)
real tmp(NZZ)
integer itone(NN)
integer*1 msgbits(77)
integer*2 iwave(NZZ) !Generated full-length waveform
integer icos4(4)
data icos4/0,1,3,2/
! Get command-line argument(s)
nargs=iargc()
@ -53,17 +53,18 @@ program ft4sim_mult
read(10,1003,end=100) cjunk,isnr,xdt0,ifreq,msg37
1003 format(a4,30x,i3,f5.1,i5,1x,a37)
if(cjunk.eq.'File') go to 100
if(isnr.lt.-16) isnr=-16
f0=ifreq*93.75/50.0
if(isnr.lt.-17) isnr=-17
f0=ifreq*960.0/576.0
call random_number(r)
xdt=r-0.5
! Source-encode, then get itone()
i3=-1
n3=-1
call pack77(msg37,i3,n3,c77)
call genft4(msg37,0,msgsent37,itone)
call genft4(msg37,0,msgsent37,msgbits,itone)
nwave0=(NN+2)*NSPS
call gen_ft4wave(itone,NN,NSPS,12000.0,f0,wave0,nwave0)
icmplx=0
call gen_ft4wave(itone,NN,NSPS,12000.0,f0,cwave0,wave0,icmplx,nwave0)
k0=nint((xdt+0.5)/dt)
if(k0.lt.1) k0=1

View File

@ -1,8 +1,9 @@
subroutine gen_ft4wave(itone,nsym,nsps,fsample,f0,wave,nwave)
subroutine gen_ft4wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave)
real wave(nwave)
real pulse(6144) !512*4*3
real dphi(0:240000-1)
complex cwave(nwave)
real pulse(6912) !576*4*3
real dphi(0:250000-1)
integer itone(nsym)
logical first
data first/.true./
@ -12,7 +13,7 @@ subroutine gen_ft4wave(itone,nsym,nsps,fsample,f0,wave,nwave)
twopi=8.0*atan(1.0)
dt=1.0/fsample
hmod=1.0
! Compute the frequency-smoothing pulse
! Compute the smoothed frequency-deviation pulse
do i=1,3*nsps
tt=(i-1.5*nsps)/real(nsps)
pulse(i)=gfsk_pulse(1.0,tt)
@ -34,19 +35,32 @@ subroutine gen_ft4wave(itone,nsym,nsps,fsample,f0,wave,nwave)
phi=0.0
dphi = dphi + twopi*f0*dt !Shift frequency up by f0
wave=0.
if(icmplx.eq.1) cwave=0.
k=0
do j=0,nwave-1
k=k+1
wave(k)=sin(phi)
if(icmplx.eq.0) then
wave(k)=sin(phi)
else
cwave(k)=cmplx(cos(phi),sin(phi))
endif
phi=mod(phi+dphi(j),twopi)
enddo
! Compute the ramp-up and ramp-down symbols
wave(1:nsps)=wave(1:nsps) * &
(1.0-cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
k1=(nsym+1)*nsps+1
wave(k1:k1+nsps-1)=wave(k1:k1+nsps-1) * &
(1.0+cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
if(icmplx.eq.0) then
wave(1:nsps)=wave(1:nsps) * &
(1.0-cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
k1=(nsym+1)*nsps+1
wave(k1:k1+nsps-1)=wave(k1:k1+nsps-1) * &
(1.0+cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
else
cwave(1:nsps)=cwave(1:nsps) * &
(1.0-cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
k1=(nsym+1)*nsps+1
cwave(k1:k1+nsps-1)=cwave(k1:k1+nsps-1) * &
(1.0+cos(twopi*(/(i,i=0,nsps-1)/)/(2.0*nsps)))/2.0
endif
return
end subroutine gen_ft4wave

View File

@ -1,4 +1,4 @@
subroutine genft4(msg0,ichk,msgsent,i4tone)
subroutine genft4(msg0,ichk,msgsent,msgbits,i4tone)
! Encode an FT4 message
! Input:
@ -11,7 +11,7 @@ subroutine genft4(msg0,ichk,msgsent,i4tone)
! s16 + 87symbols + 2 ramp up/down = 105 total channel symbols
! r1 + s4 + d29 + s4 + d29 + s4 + d29 + s4 + r1
! Message duration: TxT = 105*512/12000 = 4.48 s
! Message duration: TxT = 105*576/12000 = 5.04 s
! use iso_c_binding, only: c_loc,c_size_t
@ -52,8 +52,16 @@ subroutine genft4(msg0,ichk,msgsent,i4tone)
call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent
if(ichk.eq.1) go to 999
read(c77,"(77i1)") msgbits
msgbits=mod(msgbits+rvec,2)
read(c77,'(77i1)',err=1) msgbits
if(unpk77_success) go to 2
1 msgbits=0
itone=0
msgsent='*** bad message *** '
go to 999
entry get_ft4_tones_from_77bits(msgbits,i4tone)
2 msgbits=mod(msgbits+rvec,2)
call encode174_91(msgbits,codeword)
! Grayscale mapping:

View File

@ -0,0 +1,114 @@
subroutine get_ft4_bitmetrics(cd,bitmetrics,badsync)
include 'ft4_params.f90'
parameter (NSS=NSPS/NDOWN,NDMAX=NMAX/NDOWN)
complex cd(0:NN*NSS-1)
complex cs(0:3,NN)
complex csymb(NSS)
integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3)
integer graymap(0:3)
integer ip(1)
logical one(0:255,0:7) ! 256 4-symbol sequences, 8 bits
logical first
logical badsync
real bitmetrics(2*NN,3)
real s2(0:255)
real s4(0:3,NN)
data icos4a/0,1,3,2/
data icos4b/1,0,2,3/
data icos4c/2,3,1,0/
data icos4d/3,2,0,1/
data graymap/0,1,3,2/
data first/.true./
save first,one
if(first) then
one=.false.
do i=0,255
do j=0,7
if(iand(i,2**j).ne.0) one(i,j)=.true.
enddo
enddo
first=.false.
endif
do k=1,NN
i1=(k-1)*NSS
csymb=cd(i1:i1+NSS-1)
call four2a(csymb,NSS,1,-1,1)
cs(0:3,k)=csymb(1:4)
s4(0:3,k)=abs(csymb(1:4))
enddo
! Sync quality check
is1=0
is2=0
is3=0
is4=0
badsync=.false.
do k=1,4
ip=maxloc(s4(:,k))
if(icos4a(k-1).eq.(ip(1)-1)) is1=is1+1
ip=maxloc(s4(:,k+33))
if(icos4b(k-1).eq.(ip(1)-1)) is2=is2+1
ip=maxloc(s4(:,k+66))
if(icos4c(k-1).eq.(ip(1)-1)) is3=is3+1
ip=maxloc(s4(:,k+99))
if(icos4d(k-1).eq.(ip(1)-1)) is4=is4+1
enddo
nsync=is1+is2+is3+is4 !Number of correct hard sync symbols, 0-16
if(nsync .lt. 8) then
badsync=.true.
return
endif
do nseq=1,3 !Try coherent sequences of 1, 2, and 4 symbols
if(nseq.eq.1) nsym=1
if(nseq.eq.2) nsym=2
if(nseq.eq.3) nsym=4
nt=2**(2*nsym)
do ks=1,NN-nsym+1,nsym !87+16=103 symbols.
amax=-1.0
do i=0,nt-1
i1=i/64
i2=iand(i,63)/16
i3=iand(i,15)/4
i4=iand(i,3)
if(nsym.eq.1) then
s2(i)=abs(cs(graymap(i4),ks))
elseif(nsym.eq.2) then
s2(i)=abs(cs(graymap(i3),ks)+cs(graymap(i4),ks+1))
elseif(nsym.eq.4) then
s2(i)=abs(cs(graymap(i1),ks ) + &
cs(graymap(i2),ks+1) + &
cs(graymap(i3),ks+2) + &
cs(graymap(i4),ks+3) &
)
else
print*,"Error - nsym must be 1, 2, or 4."
endif
enddo
ipt=1+(ks-1)*2
if(nsym.eq.1) ibmax=1
if(nsym.eq.2) ibmax=3
if(nsym.eq.4) ibmax=7
do ib=0,ibmax
bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - &
maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib))
if(ipt+ib.gt.2*NN) cycle
bitmetrics(ipt+ib,nseq)=bm
enddo
enddo
enddo
bitmetrics(205:206,2)=bitmetrics(205:206,1)
bitmetrics(201:204,3)=bitmetrics(201:204,2)
bitmetrics(205:206,3)=bitmetrics(205:206,1)
call normalizebmet(bitmetrics(:,1),2*NN)
call normalizebmet(bitmetrics(:,2),2*NN)
call normalizebmet(bitmetrics(:,3),2*NN)
return
end subroutine get_ft4_bitmetrics

View File

@ -1,4 +1,4 @@
subroutine getcandidates4(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
subroutine getcandidates4(dd,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
ncand,sbase)
include 'ft4_params.f90'
@ -8,9 +8,8 @@ subroutine getcandidates4(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
real x(NFFT1)
real window(NFFT1)
complex cx(0:NH1)
real candidate(3,maxcand)
integer*2 id(NMAX)
integer indx(NH1)
real candidate(2,maxcand),candidatet(2,maxcand)
real dd(NMAX)
integer ipk(1)
equivalence (x,cx)
logical first
@ -26,42 +25,35 @@ subroutine getcandidates4(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
! Compute symbol spectra, stepping by NSTEP steps.
savg=0.
tstep=NSTEP/12000.0
df=12000.0/NFFT1
fac=1.0/300.0
do j=1,NHSYM
ia=(j-1)*NSTEP + 1
ib=ia+NFFT1-1
if(ib.gt.NMAX) exit
x=fac*id(ia:ib)*window
x=fac*dd(ia:ib)*window
call four2a(x,NFFT1,1,-1,0) !r2c FFT
do i=1,NH1
s(i,j)=real(cx(i))**2 + aimag(cx(i))**2
enddo
savg=savg + s(1:NH1,j) !Average spectrum
enddo
savg=savg/NHSYM
savsm=0.
do i=8,NH1-7
savsm(i)=sum(savg(i-7:i+7))/15.
enddo
nfa=fa/df
if(nfa.lt.1) nfa=1
nfb=fb/df
if(nfb.gt.nint(5000.0/df)) nfb=nint(5000.0/df)
n300=300/df
n2500=2500/df
! np=nfb-nfa+1
np=n2500-n300+1
indx=0
call indexx(savsm(n300:n2500),np,indx)
xn=savsm(n300+indx(nint(0.3*np)))
ncand=0
if(xn.le.1.e-8) return
savsm=savsm/xn
! call ft4_baseline(savg,nfa,nfb,sbase)
! savsm=savsm/sbase
f_offset = -1.5*12000/512
nfa=fa/df
if(nfa.lt.nint(200.0/df)) nfa=nint(200.0/df)
nfb=fb/df
if(nfb.gt.nint(4910.0/df)) nfb=nint(4910.0/df)
call ft4_baseline(savg,nfa,nfb,sbase)
if(any(sbase(nfa:nfb).le.0)) return
savsm(nfa:nfb)=savsm(nfa:nfb)/sbase(nfa:nfb)
f_offset = -1.5*12000.0/NSPS
ncand=0
candidatet=0
do i=nfa+1,nfb-1
if(savsm(i).ge.savsm(i-1) .and. savsm(i).ge.savsm(i+1) .and. &
savsm(i).ge.syncmin) then
@ -69,18 +61,26 @@ subroutine getcandidates4(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
del=0.
if(den.ne.0.0) del=0.5*(savsm(i-1)-savsm(i+1))/den
fpeak=(i+del)*df+f_offset
if(fpeak.lt.200.0 .or. fpeak.gt.4910.0) cycle
speak=savsm(i) - 0.25*(savsm(i-1)-savsm(i+1))*del
ncand=ncand+1
if(ncand.gt.maxcand) then
ncand=maxcand
exit
endif
candidate(1,ncand)=fpeak
candidate(2,ncand)=-99.99
candidate(3,ncand)=speak
candidatet(1,ncand)=fpeak
candidatet(2,ncand)=speak
if(ncand.eq.maxcand) exit
endif
enddo
candidate=0
nq=count(abs(candidatet(1,1:ncand)-nfqso).le.20.0)
n1=1
n2=nq+1
do i=1,ncand
if(abs(candidatet(1,i)-nfqso).le.20.0) then
candidate(1:2,n1)=candidatet(1:2,i)
n1=n1+1
else
candidate(1:2,n2)=candidatet(1:2,i)
n2=n2+1
endif
enddo
return
end subroutine getcandidates4

66
lib/ft4/subtractft4.f90 Normal file
View File

@ -0,0 +1,66 @@
subroutine subtractft4(dd,itone,f0,dt)
! Subtract an ft4 signal
!
! Measured signal : dd(t) = a(t)cos(2*pi*f0*t+theta(t))
! Reference signal : cref(t) = exp( j*(2*pi*f0*t+phi(t)) )
! Complex amp : cfilt(t) = LPF[ dd(t)*CONJG(cref(t)) ]
! Subtract : dd(t) = dd(t) - 2*REAL{cref*cfilt}
use timer_module, only: timer
parameter (NMAX=21*3456,NSPS=576,NFFT=NMAX,NFILT=1400)
parameter (NFRAME=(103+2)*NSPS)
real*4 dd(NMAX), window(-NFILT/2:NFILT/2), xjunk
complex cref,camp,cfilt,cw
integer itone(103)
logical first
data first/.true./
common/heap8/cref(NFRAME),camp(NMAX),cfilt(NMAX),cw(NMAX),xjunk(NFRAME)
save first
nstart=dt*12000+1-NSPS
nsym=103
fs=12000.0
icmplx=1
bt=1.0
nss=NSPS
call gen_ft4wave(itone,nsym,nss,fs,f0,cref,xjunk,icmplx,NFRAME)
camp=0.
do i=1,nframe
id=nstart-1+i
if(id.ge.1.and.id.le.NMAX) camp(i)=dd(id)*conjg(cref(i))
enddo
if(first) then
! Create and normalize the filter
pi=4.0*atan(1.0)
fac=1.0/float(nfft)
sum=0.0
do j=-NFILT/2,NFILT/2
window(j)=cos(pi*j/NFILT)**2
sum=sum+window(j)
enddo
cw=0.
cw(1:NFILT+1)=window/sum
cw=cshift(cw,NFILT/2+1)
call four2a(cw,nfft,1,-1,1)
cw=cw*fac
first=.false.
endif
cfilt=0.0
cfilt(1:nframe)=camp(1:nframe)
call four2a(cfilt,nfft,1,-1,1)
cfilt(1:nfft)=cfilt(1:nfft)*cw(1:nfft)
call four2a(cfilt,nfft,1,1,1)
! Subtract the reconstructed signal
do i=1,nframe
j=nstart+i-1
if(j.ge.1 .and. j.le.NMAX) dd(j)=dd(j)-2*REAL(cfilt(i)*cref(i))
enddo
return
end subroutine subtractft4

View File

@ -9,7 +9,6 @@ subroutine sync4d(cd0,i0,ctwk,itwk,sync)
complex csync2(2*NSS)
complex ctwk(2*NSS)
complex z1,z2,z3,z4
complex zz1,zz2,zz3,zz4
logical first
integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3)
data icos4a/0,1,3,2/
@ -19,7 +18,7 @@ subroutine sync4d(cd0,i0,ctwk,itwk,sync)
data first/.true./
save first,twopi,csynca,csyncb,csyncc,csyncd,fac
p(z1)=real(z1*fac)**2 + aimag(z1*fac)**2 !Statement function for power
p(z1)=(real(z1*fac)**2 + aimag(z1*fac)**2)**0.5 !Statement function for power
if( first ) then
twopi=8.0*atan(1.0)
@ -60,7 +59,17 @@ subroutine sync4d(cd0,i0,ctwk,itwk,sync)
z4=0.
if(itwk.eq.1) csync2=ctwk*csynca !Tweak the frequency
if(i1.ge.0 .and. i1+4*NSS-1.le.NP-1) z1=sum(cd0(i1:i1+4*NSS-1:2)*conjg(csync2))
z1=0.
if(i1.ge.0 .and. i1+4*NSS-1.le.NP-1) then
z1=sum(cd0(i1:i1+4*NSS-1:2)*conjg(csync2))
elseif( i1.lt.0 ) then
npts=(i1+4*NSS-1)/2
if(npts.le.16) then
z1=0.
else
z1=sum(cd0(0:i1+4*NSS-1:2)*conjg(csync2(2*NSS-npts:)))
endif
endif
if(itwk.eq.1) csync2=ctwk*csyncb !Tweak the frequency
if(i2.ge.0 .and. i2+4*NSS-1.le.NP-1) z2=sum(cd0(i2:i2+4*NSS-1:2)*conjg(csync2))
@ -69,7 +78,17 @@ subroutine sync4d(cd0,i0,ctwk,itwk,sync)
if(i3.ge.0 .and. i3+4*NSS-1.le.NP-1) z3=sum(cd0(i3:i3+4*NSS-1:2)*conjg(csync2))
if(itwk.eq.1) csync2=ctwk*csyncd !Tweak the frequency
if(i4.ge.0 .and. i4+4*NSS-1.le.NP-1) z4=sum(cd0(i4:i4+4*NSS-1:2)*conjg(csync2))
z4=0.
if(i4.ge.0 .and. i4+4*NSS-1.le.NP-1) then
z4=sum(cd0(i4:i4+4*NSS-1:2)*conjg(csync2))
elseif( i4+4*NSS-1.gt.NP-1 ) then
npts=(NP-1-i4+1)/2
if(npts.le.16) then
z4=0.
else
z4=sum(cd0(i4:i4+2*npts-1:2)*conjg(csync2(1:npts)))
endif
endif
sync = p(z1) + p(z2) + p(z3) + p(z4)

View File

@ -1,145 +0,0 @@
subroutine syncft4(iwave,nfa,nfb,syncmin,nfqso,maxcand,s,candidate, &
ncand,sbase)
include 'ft4_params.f90'
! Search over +/- 2.5s relative to 0.5s TX start time.
parameter (JZ=20)
complex cx(0:NH1)
real s(NH1,NHSYM)
real savg(NH1)
real sbase(NH1)
real x(NFFT1)
real sync2d(NH1,-JZ:JZ)
real red(NH1)
real candidate0(3,maxcand)
real candidate(3,maxcand)
real dd(NMAX)
integer jpeak(NH1)
integer indx(NH1)
integer ii(1)
integer*2 iwave(NMAX)
integer icos4(0:3)
data icos4/0,1,3,2/ !Costas 4x4 tone pattern
equivalence (x,cx)
dd=iwave/1e3
! Compute symbol spectra, stepping by NSTEP steps.
savg=0.
tstep=NSTEP/12000.0
df=12000.0/NFFT1
fac=1.0/300.0
do j=1,NHSYM
ia=(j-1)*NSTEP + 1
ib=ia+NSPS-1
x(1:NSPS)=fac*dd(ia:ib)
x(NSPS+1:)=0.
call four2a(x,NFFT1,1,-1,0) !r2c FFT
do i=1,NH1
s(i,j)=real(cx(i))**2 + aimag(cx(i))**2
enddo
savg=savg + s(1:NH1,j) !Average spectrum
enddo
call baseline(savg,nfa,nfb,sbase)
ia=max(1,nint(nfa/df))
ib=nint(nfb/df)
nssy=NSPS/NSTEP ! # steps per symbol
nfos=NFFT1/NSPS ! # frequency bin oversampling factor
jstrt=0.25/tstep
candidate0=0.
k=0
do i=ia,ib
do j=-JZ,+JZ
ta=0.
tb=0.
tc=0.
t0a=0.
t0b=0.
t0c=0.
do n=0,3
m=j+jstrt+nssy*n
if(m.ge.1.and.m.le.NHSYM) then
ta=ta + s(i+nfos*icos4(n),m)
t0a=t0a + sum(s(i:i+nfos*3:nfos,m))
endif
tb=tb + s(i+nfos*icos4(n),m+nssy*36)
t0b=t0b + sum(s(i:i+nfos*3:nfos,m+nssy*36))
if(m+nssy*72.le.NHSYM) then
tc=tc + s(i+nfos*icos4(n),m+nssy*72)
t0c=t0c + sum(s(i:i+nfos*3:nfos,m+nssy*72))
endif
enddo
t=ta+tb+tc
t0=t0a+t0b+t0c
t0=(t0-t)/3.0
sync_abc=t/t0
t=tb+tc
t0=t0b+t0c
t0=(t0-t)/3.0
sync_bc=t/t0
sync2d(i,j)=max(sync_abc,sync_bc)
enddo
enddo
red=0.
do i=ia,ib
ii=maxloc(sync2d(i,-JZ:JZ)) - 1 - JZ
j0=ii(1)
jpeak(i)=j0
red(i)=sync2d(i,j0)
enddo
iz=ib-ia+1
call indexx(red(ia:ib),iz,indx)
ibase=indx(nint(0.40*iz)) - 1 + ia
if(ibase.lt.1) ibase=1
if(ibase.gt.nh1) ibase=nh1
base=red(ibase)
red=red/base
do i=1,min(maxcand,iz)
n=ia + indx(iz+1-i) - 1
if(red(n).lt.syncmin.or.isnan(red(n)).or.k.eq.maxcand) exit
k=k+1
! candidate0(1,k)=n*df+37.5*1.5
candidate0(1,k)=n*df
candidate0(2,k)=(jpeak(n)-1)*tstep
candidate0(3,k)=red(n)
enddo
ncand=k
! Put nfqso at top of list, and save only the best of near-dupe freqs.
do i=1,ncand
if(abs(candidate0(1,i)-nfqso).lt.10.0) candidate0(1,i)=-candidate0(1,i)
if(i.ge.2) then
do j=1,i-1
fdiff=abs(candidate0(1,i))-abs(candidate0(1,j))
if(abs(fdiff).lt.4.0) then
if(candidate0(3,i).ge.candidate0(3,j)) candidate0(3,j)=0.
if(candidate0(3,i).lt.candidate0(3,j)) candidate0(3,i)=0.
endif
enddo
endif
enddo
fac=20.0/maxval(s)
s=fac*s
! Sort by sync
! call indexx(candidate0(3,1:ncand),ncand,indx)
! Sort by frequency
call indexx(candidate0(1,1:ncand),ncand,indx)
k=1
! do i=ncand,1,-1
do i=1,ncand
j=indx(i)
! if( candidate0(3,j) .ge. syncmin .and. candidate0(2,j).ge.-1.5 ) then
if( candidate0(3,j) .ge. syncmin ) then
candidate(2:3,k)=candidate0(2:3,j)
candidate(1,k)=abs(candidate0(1,j))
k=k+1
endif
enddo
ncand=k-1
return
end subroutine syncft4

View File

@ -1,128 +1,115 @@
module ft4_decode
type :: ft4_decoder
procedure(ft4_decode_callback), pointer :: callback
contains
procedure :: decode
end type ft4_decoder
abstract interface
subroutine ft4_decode_callback (this,sync,snr,dt,freq,decoded,nap,qual)
import ft4_decoder
implicit none
class(ft4_decoder), intent(inout) :: this
real, intent(in) :: sync
integer, intent(in) :: snr
real, intent(in) :: dt
real, intent(in) :: freq
character(len=37), intent(in) :: decoded
integer, intent(in) :: nap
real, intent(in) :: qual
end subroutine ft4_decode_callback
end interface
type :: ft4_decoder
procedure(ft4_decode_callback), pointer :: callback
contains
procedure :: decode
end type ft4_decoder
abstract interface
subroutine ft4_decode_callback (this,sync,snr,dt,freq,decoded,nap,qual)
import ft4_decoder
implicit none
class(ft4_decoder), intent(inout) :: this
real, intent(in) :: sync
integer, intent(in) :: snr
real, intent(in) :: dt
real, intent(in) :: freq
character(len=37), intent(in) :: decoded
integer, intent(in) :: nap
real, intent(in) :: qual
end subroutine ft4_decode_callback
end interface
contains
subroutine decode(this,callback,iwave,nQSOProgress,nfqso, &
nutc,nfa,nfb,ndepth,ncontest,mycall,hiscall)
use timer_module, only: timer
use packjt77
include 'ft4/ft4_params.f90'
class(ft4_decoder), intent(inout) :: this
procedure(ft4_decode_callback) :: callback
parameter (NSS=NSPS/NDOWN)
parameter (NZZ=18*3456)
character message*37,msgsent*37,msg0*37
character c77*77
character*37 decodes(100)
character*512 data_dir,fname
character*17 cdatetime0
character*12 mycall,hiscall
character*12 mycall0,hiscall0
character*6 hhmmss
character*4 cqstr,cqstr0
subroutine decode(this,callback,iwave,nQSOProgress,nfqso, &
nutc,nfa,nfb,ndepth,lapcqonly,ncontest,mycall,hiscall)
use timer_module, only: timer
use packjt77
include 'ft4/ft4_params.f90'
class(ft4_decoder), intent(inout) :: this
procedure(ft4_decode_callback) :: callback
parameter (NSS=NSPS/NDOWN,NDMAX=NMAX/NDOWN)
character message*37,msgsent*37
character c77*77
character*37 decodes(100)
character*512 data_dir,fname
character*17 cdatetime0
character*12 mycall,hiscall
character*12 mycall0,hiscall0
character*6 hhmmss
character*4 cqstr,cqstr0
complex cd2(0:NZZ/NDOWN-1) !Complex waveform
complex cb(0:NZZ/NDOWN-1+NN*NSS)
complex cd(0:NN*NSS-1) !Complex waveform
complex ctwk(2*NSS),ctwk2(2*NSS,-16:16)
complex csymb(NSS)
complex cs(0:3,NN)
real s4(0:3,NN)
complex cd2(0:NDMAX-1) !Complex waveform
complex cb(0:NDMAX-1)
complex cd(0:NN*NSS-1) !Complex waveform
complex ctwk(2*NSS),ctwk2(2*NSS,-16:16)
real bmeta(2*NN),bmetb(2*NN),bmetc(2*NN)
real a(5)
real llr(2*ND),llra(2*ND),llrb(2*ND),llrc(2*ND),llrd(2*ND)
real s2(0:255)
real candidate(3,100)
real savg(NH1),sbase(NH1)
real a(5)
real bitmetrics(2*NN,3)
real dd(NMAX)
real llr(2*ND),llra(2*ND),llrb(2*ND),llrc(2*ND),llrd(2*ND)
real candidate(2,100)
real savg(NH1),sbase(NH1)
integer apbits(2*ND)
integer apmy_ru(28),aphis_fd(28)
integer icos4a(0:3),icos4b(0:3),icos4c(0:3),icos4d(0:3)
integer*2 iwave(NZZ) !Raw received data
integer*1 message77(77),rvec(77),apmask(2*ND),cw(2*ND)
integer*1 hbits(2*NN)
integer graymap(0:3)
integer ip(1)
integer nappasses(0:5) ! # of decoding passes for QSO States 0-5
integer naptypes(0:5,4) ! nQSOProgress, decoding pass
integer mcq(29)
integer mrrr(19),m73(19),mrr73(19)
integer apbits(2*ND)
integer apmy_ru(28),aphis_fd(28)
integer*2 iwave(NMAX) !Raw received data
integer*1 message77(77),rvec(77),apmask(2*ND),cw(2*ND)
integer*1 hbits(2*NN)
integer i4tone(103)
integer nappasses(0:5) ! # of decoding passes for QSO States 0-5
integer naptypes(0:5,4) ! nQSOProgress, decoding pass
integer mcq(29)
integer mrrr(19),m73(19),mrr73(19)
logical nohiscall,unpk77_success
logical one(0:255,0:7) ! 256 4-symbol sequences, 8 bits
logical first, dobigfft
logical nohiscall,unpk77_success
logical first, dobigfft
logical dosubtract,doosd
logical badsync
logical, intent(in) :: lapcqonly
data icos4a/0,1,3,2/
data icos4b/1,0,2,3/
data icos4c/2,3,1,0/
data icos4d/3,2,0,1/
data graymap/0,1,3,2/
data msg0/' '/
data first/.true./
data mcq/0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0/
data mrrr/0,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,1/
data m73/0,1,1,1,1,1,1,0,1,0,0,1,0,1,0,0,0,0,1/
data mrr73/0,1,1,1,1,1,1,0,0,1,1,1,0,1,0,1,0,0,1/
data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, &
1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, &
0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/
save fs,dt,tt,txt,twopi,h,one,first,apbits,nappasses,naptypes, &
mycall0,hiscall0,msg0,cqstr0,ctwk2
data first/.true./
data mcq/0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0/
data mrrr/0,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,1/
data m73/0,1,1,1,1,1,1,0,1,0,0,1,0,1,0,0,0,0,1/
data mrr73/0,1,1,1,1,1,1,0,0,1,1,1,0,1,0,1,0,0,1/
data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, &
1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, &
0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/
save fs,dt,tt,txt,twopi,h,first,apbits,nappasses,naptypes, &
mycall0,hiscall0,cqstr0,ctwk2
this%callback => callback
hhmmss=cdatetime0(8:13)
if(first) then
fs=12000.0/NDOWN !Sample rate after downsampling
dt=1/fs !Sample interval after downsample (s)
tt=NSPS*dt !Duration of "itone" symbols (s)
txt=NZ*dt !Transmission length (s) without ramp up/down
twopi=8.0*atan(1.0)
h=1.0
one=.false.
do i=0,255
do j=0,7
if(iand(i,2**j).ne.0) one(i,j)=.true.
this%callback => callback
hhmmss=cdatetime0(8:13)
dxcall13=hiscall
mycall13=mycall
if(first) then
fs=12000.0/NDOWN !Sample rate after downsampling
dt=1/fs !Sample interval after downsample (s)
tt=NSPS*dt !Duration of "itone" symbols (s)
txt=NZ*dt !Transmission length (s) without ramp up/down
twopi=8.0*atan(1.0)
h=1.0
do idf=-16,16
a=0.
a(1)=real(idf)
ctwk=1.
call twkfreq1(ctwk,2*NSS,fs/2.0,a,ctwk2(:,idf))
enddo
enddo
do idf=-16,16
a=0.
a(1)=real(idf)
ctwk=1.
call twkfreq1(ctwk,2*NSS,fs/2.0,a,ctwk2(:,idf))
enddo
mrrr=2*mod(mrrr+rvec(59:77),2)-1
m73=2*mod(m73+rvec(59:77),2)-1
mrr73=2*mod(mrr73+rvec(59:77),2)-1
nappasses(0)=2
nappasses(1)=2
nappasses(2)=2
nappasses(3)=2
nappasses(4)=2
nappasses(5)=3
mrrr=2*mod(mrrr+rvec(59:77),2)-1
m73=2*mod(m73+rvec(59:77),2)-1
mrr73=2*mod(mrr73+rvec(59:77),2)-1
nappasses(0)=2
nappasses(1)=2
nappasses(2)=2
nappasses(3)=2
nappasses(4)=2
nappasses(5)=3
! iaptype
!------------------------
@ -133,257 +120,230 @@ contains
! 5 MyCall DxCall 73 (77 ap bits)
! 6 MyCall DxCall RR73 (77 ap bits)
!********
naptypes(0,1:4)=(/1,2,0,0/) ! Tx6 selected (CQ)
naptypes(1,1:4)=(/2,3,0,0/) ! Tx1
naptypes(2,1:4)=(/2,3,0,0/) ! Tx2
naptypes(3,1:4)=(/3,6,0,0/) ! Tx3
naptypes(4,1:4)=(/3,6,0,0/) ! Tx4
naptypes(5,1:4)=(/3,1,2,0/) ! Tx5
naptypes(0,1:4)=(/1,2,0,0/) ! Tx6 selected (CQ)
naptypes(1,1:4)=(/2,3,0,0/) ! Tx1
naptypes(2,1:4)=(/2,3,0,0/) ! Tx2
naptypes(3,1:4)=(/3,6,0,0/) ! Tx3
naptypes(4,1:4)=(/3,6,0,0/) ! Tx4
naptypes(5,1:4)=(/3,1,2,0/) ! Tx5
mycall0=''
hiscall0=''
cqstr0=''
first=.false.
endif
if(cqstr.ne.cqstr0) then
i0=index(cqstr,' ')
if(i0.le.1) then
message='CQ A1AA AA01'
else
message='CQ '//cqstr(1:i0-1)//' A1AA AA01'
mycall0=''
hiscall0=''
cqstr0=''
first=.false.
endif
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
read(c77,'(29i1)') mcq
mcq=2*mod(mcq+rvec(1:29),2)-1
cqstr0=cqstr
endif
l1=index(mycall,char(0))
if(l1.ne.0) mycall(l1:)=" "
l1=index(hiscall,char(0))
if(l1.ne.0) hiscall(l1:)=" "
if(mycall.ne.mycall0 .or. hiscall.ne.hiscall0) then
apbits=0
apbits(1)=99
apbits(30)=99
apmy_ru=0
aphis_fd=0
if(len(trim(mycall)) .lt. 3) go to 10
nohiscall=.false.
hiscall0=hiscall
if(len(trim(hiscall0)).lt.3) then
hiscall0=mycall ! use mycall for dummy hiscall - mycall won't be hashed.
nohiscall=.true.
endif
message=trim(mycall)//' '//trim(hiscall0)//' RR73'
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
if(i3.ne.1 .or. (message.ne.msgsent) .or. .not.unpk77_success) go to 10
read(c77,'(77i1)') message77
apmy_ru=2*mod(message77(1:28)+rvec(2:29),2)-1
aphis_fd=2*mod(message77(30:57)+rvec(29:56),2)-1
message77=mod(message77+rvec,2)
call encode174_91(message77,cw)
apbits=2*cw-1
if(nohiscall) apbits(30)=99
10 continue
mycall0=mycall
hiscall0=hiscall
endif
candidate=0.0
ncand=0
syncmin=1.2
maxcand=100
fa=nfa
fb=nfb
call timer('getcand4',0)
call getcandidates4(iwave,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
ncand,sbase)
call timer('getcand4',1)
ndecodes=0
dobigfft=.true.
do icand=1,ncand
f0=candidate(1,icand)
snr=candidate(3,icand)-1.0
call timer('ft4_down',0)
call ft4_downsample(iwave,dobigfft,f0,cd2) !Downsample to 32 Sam/Sym
call timer('ft4_down',1)
if(dobigfft) dobigfft=.false.
sum2=sum(cd2*conjg(cd2))/(real(NZZ)/real(NDOWN))
if(sum2.gt.0.0) cd2=cd2/sqrt(sum2)
! Sample rate is now 12000/16 = 750 samples/second
do isync=1,2
if(isync.eq.1) then
idfmin=-12
idfmax=12
idfstp=3
ibmin=0
ibmax=800
ibstp=4
if(cqstr.ne.cqstr0) then
i0=index(cqstr,' ')
if(i0.le.1) then
message='CQ A1AA AA01'
else
idfmin=idfbest-4
idfmax=idfbest+4
idfstp=1
ibmin=max(0,ibest-5)
ibmax=min(ibest+5,NZZ/NDOWN-1)
ibstp=1
message='CQ '//cqstr(1:i0-1)//' A1AA AA01'
endif
ibest=-1
smax=-99.
idfbest=0
call timer('sync4d ',0)
do idf=idfmin,idfmax,idfstp
do istart=ibmin,ibmax,ibstp
call sync4d(cd2,istart,ctwk2(:,idf),1,sync) !Find sync power
if(sync.gt.smax) then
smax=sync
ibest=istart
idfbest=idf
endif
enddo
enddo
call timer('sync4d ',1)
enddo
f0=f0+real(idfbest)
if( f0.le.10.0 .or. f0.ge.4990.0 ) cycle
! write(*,3002) smax,ibest/750.0,f0
!3002 format('b',3f8.2)
call timer('ft4down ',0)
call ft4_downsample(iwave,dobigfft,f0,cb) !Final downsample, corrected f0
call timer('ft4down ',1)
sum2=sum(abs(cb)**2)/(real(NSS)*NN)
if(sum2.gt.0.0) cb=cb/sqrt(sum2)
cd=cb(ibest:ibest+NN*NSS-1)
call timer('four2a ',0)
do k=1,NN
i1=(k-1)*NSS
csymb=cd(i1:i1+NSS-1)
call four2a(csymb,NSS,1,-1,1)
cs(0:3,k)=csymb(1:4)
s4(0:3,k)=abs(csymb(1:4))
enddo
call timer('four2a ',1)
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
read(c77,'(29i1)') mcq
mcq=2*mod(mcq+rvec(1:29),2)-1
cqstr0=cqstr
endif
! Sync quality check
is1=0
is2=0
is3=0
is4=0
do k=1,4
ip=maxloc(s4(:,k))
if(icos4a(k-1).eq.(ip(1)-1)) is1=is1+1
ip=maxloc(s4(:,k+33))
if(icos4b(k-1).eq.(ip(1)-1)) is2=is2+1
ip=maxloc(s4(:,k+66))
if(icos4c(k-1).eq.(ip(1)-1)) is3=is3+1
ip=maxloc(s4(:,k+99))
if(icos4d(k-1).eq.(ip(1)-1)) is4=is4+1
enddo
nsync=is1+is2+is3+is4 !Number of correct hard sync symbols, 0-16
if(smax .lt. 0.7 .or. nsync .lt. 8) cycle
l1=index(mycall,char(0))
if(l1.ne.0) mycall(l1:)=" "
l1=index(hiscall,char(0))
if(l1.ne.0) hiscall(l1:)=" "
if(mycall.ne.mycall0 .or. hiscall.ne.hiscall0) then
apbits=0
apbits(1)=99
apbits(30)=99
apmy_ru=0
aphis_fd=0
do nseq=1,3 !Try coherent sequences of 1, 2, and 4 symbols
if(nseq.eq.1) nsym=1
if(nseq.eq.2) nsym=2
if(nseq.eq.3) nsym=4
nt=2**(2*nsym)
do ks=1,NN-nsym+1,nsym !87+16=103 symbols.
amax=-1.0
do i=0,nt-1
i1=i/64
i2=iand(i,63)/16
i3=iand(i,15)/4
i4=iand(i,3)
if(nsym.eq.1) then
s2(i)=abs(cs(graymap(i4),ks))
elseif(nsym.eq.2) then
s2(i)=abs(cs(graymap(i3),ks)+cs(graymap(i4),ks+1))
elseif(nsym.eq.4) then
s2(i)=abs(cs(graymap(i1),ks ) + &
cs(graymap(i2),ks+1) + &
cs(graymap(i3),ks+2) + &
cs(graymap(i4),ks+3) &
)
if(len(trim(mycall)) .lt. 3) go to 10
nohiscall=.false.
hiscall0=hiscall
if(len(trim(hiscall0)).lt.3) then
hiscall0=mycall ! use mycall for dummy hiscall - mycall won't be hashed.
nohiscall=.true.
endif
message=trim(mycall)//' '//trim(hiscall0)//' RR73'
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,1,msgsent,unpk77_success)
if(i3.ne.1 .or. (message.ne.msgsent) .or. .not.unpk77_success) go to 10
read(c77,'(77i1)') message77
apmy_ru=2*mod(message77(1:28)+rvec(2:29),2)-1
aphis_fd=2*mod(message77(30:57)+rvec(29:56),2)-1
message77=mod(message77+rvec,2)
call encode174_91(message77,cw)
apbits=2*cw-1
if(nohiscall) apbits(30)=99
10 continue
mycall0=mycall
hiscall0=hiscall
endif
maxcand=100
ndecodes=0
decodes=' '
fa=nfa
fb=nfb
dd=iwave
! ndepth=3: 3 passes, bp+osd
! ndepth=2: 3 passes, bp only
! ndepth=1: 1 pass, no subtraction
max_iterations=40
syncmin=1.2
dosubtract=.true.
doosd=.true.
nsp=3
if(ndepth.eq.2) then
doosd=.false.
endif
if(ndepth.eq.1) then
nsp=1
dosubtract=.false.
doosd=.false.
endif
do isp = 1,nsp
if(isp.eq.2) then
if(ndecodes.eq.0) exit
nd1=ndecodes
elseif(isp.eq.3) then
nd2=ndecodes-nd1
if(nd2.eq.0) exit
endif
candidate=0.0
ncand=0
call timer('getcand4',0)
call getcandidates4(dd,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
ncand,sbase)
call timer('getcand4',1)
dobigfft=.true.
do icand=1,ncand
f0=candidate(1,icand)
snr=candidate(2,icand)-1.0
call timer('ft4_down',0)
call ft4_downsample(dd,dobigfft,f0,cd2) !Downsample to 32 Sam/Sym
call timer('ft4_down',1)
if(dobigfft) dobigfft=.false.
sum2=sum(cd2*conjg(cd2))/(real(NMAX)/real(NDOWN))
if(sum2.gt.0.0) cd2=cd2/sqrt(sum2)
! Sample rate is now 12000/18 = 666.67 samples/second
do iseg=1,3 ! DT search is done over 3 segments
do isync=1,2
if(isync.eq.1) then
idfmin=-12
idfmax=12
idfstp=3
ibmin=-344
ibmax=1012
if(iseg.eq.1) then
ibmin=108
ibmax=560
elseif(iseg.eq.2) then
smax1=smax
ibmin=560
ibmax=1012
elseif(iseg.eq.3) then
ibmin=-344
ibmax=108
endif
ibstp=4
else
idfmin=idfbest-4
idfmax=idfbest+4
idfstp=1
ibmin=ibest-5
ibmax=ibest+5
ibstp=1
endif
ibest=-1
idfbest=0
smax=-99.
call timer('sync4d ',0)
do idf=idfmin,idfmax,idfstp
do istart=ibmin,ibmax,ibstp
call sync4d(cd2,istart,ctwk2(:,idf),1,sync) !Find sync power
if(sync.gt.smax) then
smax=sync
ibest=istart
idfbest=idf
endif
enddo
enddo
call timer('sync4d ',1)
enddo
if(iseg.eq.1) smax1=smax
if(smax.lt.1.2) cycle
if(iseg.gt.1 .and. smax.lt.smax1) cycle
f1=f0+real(idfbest)
if( f1.le.10.0 .or. f1.ge.4990.0 ) cycle
call timer('ft4down ',0)
call ft4_downsample(dd,dobigfft,f1,cb) !Final downsample, corrected f0
call timer('ft4down ',1)
sum2=sum(abs(cb)**2)/(real(NSS)*NN)
if(sum2.gt.0.0) cb=cb/sqrt(sum2)
cd=0.
if(ibest.ge.0) then
it=min(NDMAX-1,ibest+NN*NSS-1)
np=it-ibest+1
cd(0:np-1)=cb(ibest:it)
else
print*,"Error - nsym must be 1, 2, or 4."
cd(-ibest:ibest+NN*NSS-1)=cb(0:NN*NSS+2*ibest-1)
endif
enddo
ipt=1+(ks-1)*2
if(nsym.eq.1) ibmax=1
if(nsym.eq.2) ibmax=3
if(nsym.eq.4) ibmax=7
do ib=0,ibmax
bm=maxval(s2(0:nt-1),one(0:nt-1,ibmax-ib)) - &
maxval(s2(0:nt-1),.not.one(0:nt-1,ibmax-ib))
if(ipt+ib.gt.2*NN) cycle
if(nsym.eq.1) then
bmeta(ipt+ib)=bm
elseif(nsym.eq.2) then
bmetb(ipt+ib)=bm
elseif(nsym.eq.4) then
bmetc(ipt+ib)=bm
endif
enddo
enddo
enddo
call timer('bitmet ',0)
call get_ft4_bitmetrics(cd,bitmetrics,badsync)
call timer('bitmet ',1)
if(badsync) cycle
hbits=0
where(bitmetrics(:,1).ge.0) hbits=1
ns1=count(hbits( 1: 8).eq.(/0,0,0,1,1,0,1,1/))
ns2=count(hbits( 67: 74).eq.(/0,1,0,0,1,1,1,0/))
ns3=count(hbits(133:140).eq.(/1,1,1,0,0,1,0,0/))
ns4=count(hbits(199:206).eq.(/1,0,1,1,0,0,0,1/))
nsync_qual=ns1+ns2+ns3+ns4
if(nsync_qual.lt. 20) cycle
bmetb(205:206)=bmeta(205:206)
bmetc(201:204)=bmetb(201:204)
bmetc(205:206)=bmeta(205:206)
scalefac=2.83
llra( 1: 58)=bitmetrics( 9: 66, 1)
llra( 59:116)=bitmetrics( 75:132, 1)
llra(117:174)=bitmetrics(141:198, 1)
llra=scalefac*llra
llrb( 1: 58)=bitmetrics( 9: 66, 2)
llrb( 59:116)=bitmetrics( 75:132, 2)
llrb(117:174)=bitmetrics(141:198, 2)
llrb=scalefac*llrb
llrc( 1: 58)=bitmetrics( 9: 66, 3)
llrc( 59:116)=bitmetrics( 75:132, 3)
llrc(117:174)=bitmetrics(141:198, 3)
llrc=scalefac*llrc
call normalizebmet(bmeta,2*NN)
call normalizebmet(bmetb,2*NN)
call normalizebmet(bmetc,2*NN)
apmag=maxval(abs(llra))*1.1
npasses=3+nappasses(nQSOProgress)
if(lapcqonly) npasses=4
if(ndepth.eq.1) npasses=3
if(ncontest.ge.5) npasses=3 ! Don't support Fox and Hound
do ipass=1,npasses
if(ipass.eq.1) llr=llra
if(ipass.eq.2) llr=llrb
if(ipass.eq.3) llr=llrc
if(ipass.le.3) then
apmask=0
iaptype=0
endif
hbits=0
where(bmeta.ge.0) hbits=1
ns1=count(hbits( 1: 8).eq.(/0,0,0,1,1,0,1,1/))
ns2=count(hbits( 67: 74).eq.(/0,1,0,0,1,1,1,0/))
ns3=count(hbits(133:140).eq.(/1,1,1,0,0,1,0,0/))
ns4=count(hbits(199:206).eq.(/1,0,1,1,0,0,0,1/))
nsync_qual=ns1+ns2+ns3+ns4
if(nsync_qual.lt. 20) cycle
scalefac=2.83
llra( 1: 58)=bmeta( 9: 66)
llra( 59:116)=bmeta( 75:132)
llra(117:174)=bmeta(141:198)
llra=scalefac*llra
llrb( 1: 58)=bmetb( 9: 66)
llrb( 59:116)=bmetb( 75:132)
llrb(117:174)=bmetb(141:198)
llrb=scalefac*llrb
llrc( 1: 58)=bmetc( 9: 66)
llrc( 59:116)=bmetc( 75:132)
llrc(117:174)=bmetc(141:198)
llrc=scalefac*llrc
apmag=maxval(abs(llra))*1.1
npasses=3+nappasses(nQSOProgress)
if(ncontest.ge.5) npasses=3 ! Don't support Fox and Hound
do ipass=1,npasses
if(ipass.eq.1) llr=llra
if(ipass.eq.2) llr=llrb
if(ipass.eq.3) llr=llrc
if(ipass.le.3) then
apmask=0
iaptype=0
endif
if(ipass .gt. 3) then
llrd=llrc
iaptype=naptypes(nQSOProgress,ipass-3)
if(ipass .gt. 3) then
llrd=llra
iaptype=naptypes(nQSOProgress,ipass-3)
if(lapcqonly) iaptype=1
! ncontest=0 : NONE
! 1 : NA_VHF
@ -394,94 +354,114 @@ contains
! 6 : HOUND
!
! Conditions that cause us to bail out of AP decoding
napwid=50
if(ncontest.le.4 .and. iaptype.ge.3 .and. (abs(f0-nfqso).gt.napwid) ) cycle
if(iaptype.ge.2 .and. apbits(1).gt.1) cycle ! No, or nonstandard, mycall
if(iaptype.ge.3 .and. apbits(30).gt.1) cycle ! No, or nonstandard, dxcall
napwid=50
if(ncontest.le.4 .and. iaptype.ge.3 .and. (abs(f1-nfqso).gt.napwid) ) cycle
if(iaptype.ge.2 .and. apbits(1).gt.1) cycle ! No, or nonstandard, mycall
if(iaptype.ge.3 .and. apbits(30).gt.1) cycle ! No, or nonstandard, dxcall
if(iaptype.eq.1) then ! CQ or CQ TEST or CQ FD or CQ RU or CQ SCC
apmask=0
apmask(1:29)=1
llrd(1:29)=apmag*mcq(1:29)
endif
if(iaptype.eq.1) then ! CQ or CQ TEST or CQ FD or CQ RU or CQ SCC
apmask=0
apmask(1:29)=1
llrd(1:29)=apmag*mcq(1:29)
endif
if(iaptype.eq.2) then ! MyCall,???,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1) then
apmask(1:29)=1
llrd(1:29)=apmag*apbits(1:29)
else if(ncontest.eq.2) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.3) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.4) then
apmask(2:29)=1
llrd(2:29)=apmag*apmy_ru(1:28)
endif
endif
if(iaptype.eq.2) then ! MyCall,???,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1) then
apmask(1:29)=1
llrd(1:29)=apmag*apbits(1:29)
else if(ncontest.eq.2) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.3) then
apmask(1:28)=1
llrd(1:28)=apmag*apbits(1:28)
else if(ncontest.eq.4) then
apmask(2:29)=1
llrd(2:29)=apmag*apmy_ru(1:28)
endif
endif
if(iaptype.eq.3) then ! MyCall,DxCall,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1.or.ncontest.eq.2) then
apmask(1:58)=1
llrd(1:58)=apmag*apbits(1:58)
else if(ncontest.eq.3) then ! Field Day
apmask(1:56)=1
llrd(1:28)=apmag*apbits(1:28)
llrd(29:56)=apmag*aphis_fd(1:28)
else if(ncontest.eq.4) then ! RTTY RU
apmask(2:57)=1
llrd(2:29)=apmag*apmy_ru(1:28)
llrd(30:57)=apmag*apbits(30:57)
endif
endif
if(iaptype.eq.3) then ! MyCall,DxCall,???
apmask=0
if(ncontest.eq.0.or.ncontest.eq.1.or.ncontest.eq.2) then
apmask(1:58)=1
llrd(1:58)=apmag*apbits(1:58)
else if(ncontest.eq.3) then ! Field Day
apmask(1:56)=1
llrd(1:28)=apmag*apbits(1:28)
llrd(29:56)=apmag*aphis_fd(1:28)
else if(ncontest.eq.4) then ! RTTY RU
apmask(2:57)=1
llrd(2:29)=apmag*apmy_ru(1:28)
llrd(30:57)=apmag*apbits(30:57)
endif
endif
if(iaptype.eq.4 .or. iaptype.eq.5 .or. iaptype.eq.6) then
apmask=0
if(ncontest.le.4) then
apmask(1:91)=1 ! mycall, hiscall, RRR|73|RR73
if(iaptype.eq.6) llrd(1:91)=apmag*apbits(1:91)
endif
endif
if(iaptype.eq.4 .or. iaptype.eq.5 .or. iaptype.eq.6) then
apmask=0
if(ncontest.le.4) then
apmask(1:77)=1 ! mycall, hiscall, RRR|73|RR73
if(iaptype.eq.6) llrd(1:77)=apmag*apbits(1:77)
endif
endif
llr=llrd
endif
max_iterations=40
message77=0
call timer('bpdec174',0)
call bpdecode174_91(llr,apmask,max_iterations,message77, &
cw,nharderror,niterations)
call timer('bpdec174',1)
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
message77=mod(message77+rvec,2) ! remove rvec scrambling
write(c77,'(77i1)') message77(1:77)
call unpack77(c77,1,message,unpk77_success)
idupe=0
do i=1,ndecodes
if(decodes(i).eq.message) idupe=1
enddo
if(ibest.le.10 .and. message.eq.msg0) idupe=1 !Already decoded
if(idupe.eq.1) exit
ndecodes=ndecodes+1
decodes(ndecodes)=message
if(snr.gt.0.0) then
xsnr=10*log10(snr)-14.0
else
xsnr=-20.0
endif
nsnr=nint(max(-20.0,xsnr))
xdt=ibest/750.0 - 0.5
call this%callback(sync,nsnr,xdt,f0,message,iaptype,qual)
if(ibest.ge.ibmax-15) msg0=message !Possible dupe candidate
exit
endif
enddo !Sequence estimation
enddo !Candidate list
llr=llrd
endif
message77=0
dmin=0.0
call timer('bpdec174',0)
call bpdecode174_91(llr,apmask,max_iterations,message77, &
cw,nharderror,niterations)
call timer('bpdec174',1)
return
end subroutine decode
if(doosd .and. nharderror.lt.0) then
ndeep=3
! if(abs(nfqso-f1).le.napwid) then
! ndeep=4
! endif
call timer('osd174_91 ',0)
call osd174_91(llr,apmask,ndeep,message77,cw,nharderror,dmin)
call timer('osd174_91 ',1)
endif
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
message77=mod(message77+rvec,2) ! remove rvec scrambling
write(c77,'(77i1)') message77(1:77)
call unpack77(c77,1,message,unpk77_success)
if(unpk77_success.and.dosubtract) then
call get_ft4_tones_from_77bits(message77,i4tone)
dt=real(ibest)/666.67
call timer('subtract',0)
call subtractft4(dd,i4tone,f1,dt)
call timer('subtract',1)
endif
idupe=0
do i=1,ndecodes
if(decodes(i).eq.message) idupe=1
enddo
if(idupe.eq.1) exit
ndecodes=ndecodes+1
decodes(ndecodes)=message
if(snr.gt.0.0) then
xsnr=10*log10(snr)-14.8
else
xsnr=-21.0
endif
nsnr=nint(max(-21.0,xsnr))
xdt=ibest/666.67 - 0.5
!write(21,'(i6.6,i5,2x,f4.1,i6,2x,a37,2x,f4.1,3i3,f5.1,i4,i4,i4)') &
! nutc,nsnr,xdt,nint(f1),message,smax,iaptype,ipass,isp,dmin,nsync_qual,nharderror,iseg
call this%callback(smax,nsnr,xdt,f1,message,iaptype,qual)
exit
endif
enddo !Sequence estimation
if(nharderror.ge.0) exit
enddo !3 DT segments
enddo !Candidate list
enddo !Subtraction loop
return
end subroutine decode
end module ft4_decode

View File

@ -396,7 +396,7 @@ subroutine ft8b(dd0,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,lapcqonly, &
cycle
endif
nbadcrc=0 ! If we get this far: valid codeword, valid (i3,n3), nonquirky message.
call get_tones_from_77bits(message77,itone)
call get_ft8_tones_from_77bits(message77,itone)
if(lsubtract) call subtractft8(dd0,itone,f1,xdt)
xsig=0.0
xnoi=0.0

View File

@ -91,7 +91,7 @@ program ft8sim
msg0=msg
do ifile=1,nfiles
k=nint((xdt+0.5)/dt)
ia=k
ia=max(1,k)
phi=0.0
c0=0.0
do j=1,NN !Generate complex waveform
@ -105,7 +105,7 @@ program ft8sim
if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c0,NMAX,NWAVE,fs,delay,fspread)
c=sig*c0
ib=k
ib=min(k,NMAX)
wave=real(c)
peak=maxval(abs(wave(ia:ib)))
nslots=1

View File

@ -57,6 +57,7 @@ program ft8sim_gfsk
baud=1.0/tt !Keying rate (baud)
bw=8*baud !Occupied bandwidth (Hz)
txt=NZ*dt !Transmission length (s)
bt=2.0
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
@ -67,7 +68,7 @@ program ft8sim_gfsk
n3=-1
call pack77(msg37,i3,n3,c77)
call genft8(msg37,i3,n3,msgsent37,msgbits,itone)
call gen_ft8wave(itone,NN,NSPS,fs,f0,cwave,xjunk,1,NWAVE) !Generate complex cwave
call gen_ft8wave(itone,NN,NSPS,bt,fs,f0,cwave,xjunk,1,NWAVE) !Generate complex cwave
write(*,*)
write(*,'(a23,a37,3x,a7,i1,a1,i1)') 'New Style FT8 Message: ',msgsent37,'i3.n3: ',i3,'.',n3

View File

@ -1,4 +1,4 @@
subroutine gen_ft8wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave)
subroutine gen_ft8wave(itone,nsym,nsps,bt,fsample,f0,cwave,wave,icmplx,nwave)
!
! generate ft8 waveform using Gaussian-filtered frequency pulses.
!
@ -9,21 +9,20 @@ subroutine gen_ft8wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave)
real pulse(23040)
real dphi(0:(nsym+2)*nsps-1)
integer itone(nsym)
logical first
data first/.true./
save pulse,first,twopi,dt,hmod
data ibt0/0/
save pulse,twopi,dt,hmod,ibt0
if(first) then
ibt=nint(10*bt)
if(ibt0.ne.ibt) then
twopi=8.0*atan(1.0)
dt=1.0/fsample
hmod=1.0
bt=2.0
! Compute the frequency-smoothing pulse
do i=1,3*nsps
tt=(i-1.5*nsps)/real(nsps)
pulse(i)=gfsk_pulse(bt,tt)
enddo
first=.false.
ibt0=nint(10*bt)
endif
! Compute the smoothed frequency waveform.
@ -43,6 +42,7 @@ subroutine gen_ft8wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave)
phi=0.0
dphi = dphi + twopi*f0*dt !Shift frequency up by f0
wave=0.
if (icmplx .ne. 0) cwave=0. ! avoid writing to memory we may not have access to
k=0
do j=nsps,nsps+nwave-1 !Don't include dummy symbols
k=k+1

View File

@ -25,7 +25,7 @@ subroutine genft8(msg,i3,n3,msgsent,msgbits,itone)
msgsent='*** bad message *** '
go to 900
entry get_tones_from_77bits(msgbits,itone)
entry get_ft8_tones_from_77bits(msgbits,itone)
2 call encode174_91(msgbits,codeword) !Encode the test message

View File

@ -11,16 +11,22 @@ subroutine subtractft8(dd,itone,f0,dt)
parameter (NMAX=15*12000,NFRAME=1920*79)
parameter (NFFT=NMAX,NFILT=1400)
real*4 dd(NMAX), window(-NFILT/2:NFILT/2)
real*4 dd(NMAX), window(-NFILT/2:NFILT/2), xjunk
complex cref,camp,cfilt,cw
integer itone(79)
logical first
data first/.true./
common/heap8/cref(NFRAME),camp(NMAX),cfilt(NMAX),cw(NMAX)
common/heap8/cref(NFRAME),camp(NMAX),cfilt(NMAX),cw(NMAX),xjunk(NFRAME)
save first
nstart=dt*12000+1
call genft8refsig(itone,cref,f0)
! call genft8refsig(itone,cref,f0)
nsym=79
nsps=1920
fs=12000.0
icmplx=1
bt=4.0 ! Temporary compromise?
call gen_ft8wave(itone,nsym,nsps,bt,fs,f0,cref,xjunk,icmplx,NFRAME)
camp=0.
do i=1,nframe
id=nstart-1+i

View File

@ -89,7 +89,12 @@ subroutine sync8(dd,nfa,nfb,syncmin,nfqso,maxcand,s,candidate, &
enddo
iz=ib-ia+1
call indexx(red(ia:ib),iz,indx)
ibase=indx(nint(0.40*iz)) - 1 + ia
npctile=nint(0.40*iz)
if(npctile.lt.1) then ! something is wrong; bail out
ncand=0
return;
endif
ibase=indx(npctile) - 1 + ia
if(ibase.lt.1) ibase=1
if(ibase.gt.nh1) ibase=nh1
base=red(ibase)

View File

@ -156,7 +156,7 @@ contains
nfreqz=nint(dfx)
call timer('sync4 ',1)
nsnr=nint(snrx)
nsnr=-26
if(sync.lt.syncmin) then
if (associated (this%decode_callback)) then
call this%decode_callback(nsnr,dtxz,nfreqz,.false.,csync, &
@ -166,6 +166,7 @@ contains
endif
! We have achieved sync
nsnr=nint(snrsync - 22.9)
decoded=blank
deepmsg=blank
special=' '

View File

@ -22,7 +22,8 @@ program jt9
!### ndepth was defined as 60001. Why???
integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, &
fhigh=4000,nrxfreq=1500,ntrperiod=1,ndepth=1,nexp_decode=0
logical :: read_files = .true., tx9 = .false., display_help = .false.
logical :: read_files = .true., tx9 = .false., display_help = .false., &
bLowSidelobes = .false.
type (option) :: long_options(26) = [ &
option ('help', .false., 'h', 'Display this help message', ''), &
option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), &
@ -138,7 +139,7 @@ program jt9
read (optarg(:arglen), *) nexp_decode
end select
end do
if (display_help .or. stat .lt. 0 &
.or. (.not. read_files .and. remain .gt. 0) &
.or. (read_files .and. remain .lt. 1)) then
@ -156,7 +157,7 @@ program jt9
end do
go to 999
endif
iret=fftwf_init_threads() !Initialize FFTW threading
! Default to 1 thread, but use nthreads for the big ones
@ -224,7 +225,7 @@ program jt9
endif
shared_data%id2=0 !??? Why is this necessary ???
if(mode.eq.5) npts=21*3456
do iblk=1,npts/kstep
k=iblk*kstep
if(mode.eq.8 .and. k.gt.179712) exit
@ -232,7 +233,7 @@ program jt9
read(unit=wav%lun,end=3) shared_data%id2(k-kstep+1:k)
go to 4
3 call timer('read_wav',1)
print*,'EOF on input file ',infile
print*,'EOF on input file ',trim(infile)
exit
4 call timer('read_wav',1)
nhsym=(k-2048)/kstep
@ -242,7 +243,7 @@ program jt9
ingain=0
call timer('symspec ',0)
nminw=1
call symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb, &
call symspec(shared_data,k,ntrperiod,nsps,ingain,bLowSidelobes,nminw,pxdb, &
s,df3,ihsym,npts8,pxdbmax)
call timer('symspec ',1)
endif

View File

@ -53,7 +53,8 @@ contains
type(riff_descriptor) :: desc
character(len=4) :: riff_type
open (newunit=this%lun, file=filename, access='stream', form='unformatted', status='old')
this%lun=26
open (unit=this%lun, file=filename, access='stream',status='old')
read (unit=this%lun) desc,riff_type
inquire (unit=this%lun, pos=filepos)
do
@ -67,5 +68,6 @@ contains
end if
filepos = filepos + (desc%size + 1) / 2 * 2 ! pad to even alignment
end do
return
end subroutine read
end module readwav

102
lib/rtty_spec.f90 Normal file
View File

@ -0,0 +1,102 @@
program rtty_spec
! Generate simulated data for standard RTTY and WSJT-X modes FT8, FT4
use wavhdr
use packjt
parameter (NMAX=15*12000)
type(hdr) h
complex cwave(NMAX)
real wave(NMAX)
real*4 dat(NMAX) !Generated waveform
integer*2 iwave(NMAX) !Generated waveform
integer itone(680) !Channel symbols (values 0-1, 0-3, 0-7)
integer*1 msgbits(77)
character*37 msg37,msgsent37
character*8 arg
nargs=iargc()
if(nargs.ne.1) then
print*,'Usage: rtty_spec <snr>'
go to 999
endif
call getarg(1,arg)
read(arg,*) snrdb !S/N in dB (2500 hz reference BW)
rmsdb=25.
rms=10.0**(0.05*rmsdb)
sig=10.0**(0.05*snrdb)
npts=NMAX
do i=1,NMAX !Generate gaussian noise
dat(i)=gran()
enddo
! Add the RTTY signal
fsample=12000.0 !Sample rate (Hz)
dt=1.0/fsample !Sample interval (s)
twopi=8.0*atan(1.0)
phi=0.
dphi=0.
j0=-1
do i=6001,NMAX-6000
j=nint(i*dt/0.022)
if(j.ne.j0) then
f0=1415.0
call random_number(rr)
if(rr.gt.0.5) f0=1585.0
dphi=twopi*f0*dt
j0=j
endif
phi=phi+dphi
if(phi.gt.twopi) phi=phi-twopi
dat(i)=dat(i) + sig*sin(phi)
enddo
! FT8 signal (FSK)
i3=0
n3=0
msg37='WB9XYZ KA2ABC FN42'
call genft8(msg37,i3,n3,msgsent37,msgbits,itone)
nsym=79
nsps=1920
bt=99.0
f0=3500.0
icmplx=0
nwave=nsym*nsps
call gen_ft8wave(itone,nsym,nsps,bt,fsample,f0,cwave,wave,icmplx,nwave)
dat(6001:6000+nwave)=dat(6001:6000+nwave) + sig*wave(1:nwave)
! FT8 signal (GFSK)
i3=0
n3=0
msg37='WB9XYZ KA2ABC FN42'
call genft8(msg37,i3,n3,msgsent37,msgbits,itone)
nsym=79
nsps=1920
bt=2.0
f0=4000.0
icmplx=0
nwave=nsym*nsps
call gen_ft8wave(itone,nsym,nsps,bt,fsample,f0,cwave,wave,icmplx,nwave)
dat(6001:6000+nwave)=dat(6001:6000+nwave) + sig*wave(1:nwave)
! Add the FT4 signal
ichk=0
call genft4(msg37,ichk,msgsent37,msgbits,itone)
nsym=103
nsps=576
f0=4500.0
icmplx=0
nwave=(nsym+2)*nsps
call gen_ft4wave(itone,nsym,nsps,fsample,f0,cwave,wave,icmplx,nwave)
dat(6001:6000+nwave)=dat(6001:6000+nwave) + sig*wave(1:nwave)
h=default_header(12000,NMAX)
datmax=maxval(abs(dat))
iwave=nint(32767.0*dat/datmax)
open(10,file='000000_000001.wav',access='stream',status='unknown')
write(10) h,iwave
close(10)
999 end program rtty_spec

View File

@ -16,6 +16,7 @@
#include <QTextStream>
#include <QDebug>
#include <QDebugStateSaver>
#include "Configuration.hpp"
#include "Radio.hpp"
#include "pimpl_impl.hpp"
@ -155,14 +156,16 @@ typedef multi_index_container<
class AD1CCty::impl final
{
public:
explicit impl ()
using entity_by_id = entities_type::index<id>::type;
explicit impl (Configuration const * configuration)
: configuration_ {configuration}
{
}
Record fixup (QString call, prefix const& p) const
entity_by_id::iterator lookup_entity (QString call, prefix const& p) const
{
call = call.toUpper ();
using entity_by_id = entities_type::index<id>::type;
entity_by_id::iterator e; // iterator into entity set
//
@ -171,23 +174,26 @@ public:
if (call.startsWith ("KG4") && call.size () != 5 && call.size () != 3)
{
// KG4 2x1 and 2x3 calls that map to Gitmo are mainland US not Gitmo
e = entities_.project<id> (entities_.get<primary_prefix> ().find ("K"));
return entities_.project<id> (entities_.get<primary_prefix> ().find ("K"));
}
else
{
e = entities_.get<id> ().find (p.entity_id_);
return entities_.get<id> ().find (p.entity_id_);
}
}
Record fixup (prefix const& p, entity const& e) const
{
Record result;
result.continent = e->continent_;
result.CQ_zone = e->CQ_zone_;
result.ITU_zone = e->ITU_zone_;
result.entity_name = e->name_;
result.WAE_only = e->WAE_only_;
result.latitude = e->lat_;
result.longtitude = e->long_;
result.UTC_offset = e->UTC_offset_;
result.primary_prefix = e->primary_prefix_;
result.continent = e.continent_;
result.CQ_zone = e.CQ_zone_;
result.ITU_zone = e.ITU_zone_;
result.entity_name = e.name_;
result.WAE_only = e.WAE_only_;
result.latitude = e.lat_;
result.longtitude = e.long_;
result.UTC_offset = e.UTC_offset_;
result.primary_prefix = e.primary_prefix_;
// check for overrides
bool ok1 {true}, ok2 {true}, ok3 {true}, ok4 {true}, ok5 {true};
@ -220,6 +226,7 @@ public:
return false;
}
Configuration const * configuration_;
QString path_;
entities_type entities_;
prefixes_type prefixes_;
@ -307,8 +314,13 @@ char const * AD1CCty::continent (Continent c)
}
}
AD1CCty::AD1CCty ()
AD1CCty::AD1CCty (Configuration const * configuration)
: m_ {configuration}
{
Q_ASSERT (configuration);
// TODO: G4WJS - consider doing the following asynchronously to
// speed up startup. Not urgent as it takes less than 1s on a Core
// i7 reading BIG CTY.DAT.
QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)};
m_->path_ = dataPath.exists (file_name)
? dataPath.absoluteFilePath (file_name) // user override
@ -389,7 +401,7 @@ auto AD1CCty::lookup (QString const& call) const -> Record
auto p = m_->prefixes_.find (exact_search);
if (p != m_->prefixes_.end () && p->exact_)
{
return m_->fixup (call, *p);
return m_->fixup (*p, *m_->lookup_entity (call, *p));
}
}
while (search_prefix.size ())
@ -397,9 +409,11 @@ auto AD1CCty::lookup (QString const& call) const -> Record
auto p = m_->prefixes_.find (search_prefix);
if (p != m_->prefixes_.end ())
{
if (!p->exact_ || call.size () == search_prefix.size ())
impl::entity_by_id::iterator e = m_->lookup_entity (call, *p);
if ((m_->configuration_->include_WAE_entities () || !e->WAE_only_)
&& (!p->exact_ || call.size () == search_prefix.size ()))
{
return m_->fixup (call, *p);
return m_->fixup (*p, *e);
}
}
search_prefix = search_prefix.left (search_prefix.size () - 1);

View File

@ -1,17 +1,19 @@
#ifndef AD1C_CTY_HPP_
#define AD1C_CTY_HPP_
#include <boost/core/noncopyable.hpp>
#include <QObject>
#include <QDebug>
#include "pimpl_h.hpp"
class QString;
class Configuration;
//
// AD1CCty - Fast access database of Jim Reisert, AD1C's, cty.dat
// entity and entity override information file.
//
class AD1CCty final
: public QObject
, private boost::noncopyable
{
Q_OBJECT
@ -39,7 +41,7 @@ public:
QString primary_prefix;
};
explicit AD1CCty ();
explicit AD1CCty (Configuration const *);
~AD1CCty ();
Record lookup (QString const& call) const;

44
logbook/Multiplier.cpp Normal file
View File

@ -0,0 +1,44 @@
#include "Multiplier.hpp"
#include <QSet>
#include <QString>
#include <QDebug>
#include "models/CabrilloLog.hpp"
#include "pimpl_impl.hpp"
class Multiplier::impl
{
public:
impl (AD1CCty const * countries)
: countries_ {countries}
{
}
AD1CCty const * countries_;
worked_set entities_worked_;
worked_set grids_worked_;
};
Multiplier::Multiplier (AD1CCty const * countries)
: m_ {countries}
{
}
Multiplier::~Multiplier ()
{
}
void Multiplier::reload (CabrilloLog const * log)
{
m_->entities_worked_ = log->unique_DXCC_entities (m_->countries_);
}
auto Multiplier::entities_worked () const -> worked_set const&
{
return m_->entities_worked_;
}
auto Multiplier::grids_worked () const -> worked_set const&
{
return m_->grids_worked_;
}

Some files were not shown because too many files have changed in this diff Show More