Merge branch 'release-2.1.0'

This commit is contained in:
Bill Somerville 2019-07-13 23:45:38 +01:00
commit a6eecf3b23
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
209 changed files with 23173 additions and 2769 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
.gitattributes export-ignore .gitattributes export-ignore
/samples export-ignore /samples export-ignore
/lib/fsk4hf 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 PATH_SUFFIXES lib/pkgconfig lib64/pkgconfig
) )
if (__hamlib_pc_path) if (__hamlib_pc_path)
set (ENV{PKG_CONFIG_PATH} "${__hamlib_pc_path}" "$ENV{PKG_CONFIG_PATH}") set (__pc_path $ENV{PKG_CONFIG_PATH})
unset (__hamlib_pc_path CACHE) list (APPEND __pc_path "${__hamlib_pc_path}")
set (ENV{PKG_CONFIG_PATH} "${__pc_path}")
unset (__pc_path CACHE)
endif () endif ()
unset (__hamlib_pc_path CACHE)
# Use pkg-config to get hints about paths, libs and, flags # Use pkg-config to get hints about paths, libs and, flags
unset (__pkg_config_checked_hamlib CACHE) 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 cmake_policy (SET CMP0063 NEW) # honour visibility properties for all library types
endif (POLICY CMP0063) 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) include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake)
message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}") message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
@ -181,7 +185,11 @@ attach a debugger which will then receive the console output inside its console.
set (PROJECT_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}") set (PROJECT_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
if (NOT PROJECT_ARCHITECTURE) if (NOT PROJECT_ARCHITECTURE)
# This is supposed to happen already on Windows # This is supposed to happen already on Windows
set (PROJECT_ARCHITECTURE "$ENV{PROCESSOR_ARCHITECTURE}") if (CMAKE_SIZEOF_VOID_P MATCHES 8)
set (PROJECT_ARCHITECTURE "x64")
else ()
set (PROJECT_ARCHITECTURE "$ENV{PROCESSOR_ARCHITECTURE}")
endif ()
endif (NOT PROJECT_ARCHITECTURE) endif (NOT PROJECT_ARCHITECTURE)
message (STATUS "******************************************************") message (STATUS "******************************************************")
message (STATUS "Building for for: ${CMAKE_SYSTEM_NAME}-${PROJECT_ARCHITECTURE}") message (STATUS "Building for for: ${CMAKE_SYSTEM_NAME}-${PROJECT_ARCHITECTURE}")
@ -229,6 +237,7 @@ set (wsjt_qt_CXXSRCS
models/FrequencyList.cpp models/FrequencyList.cpp
models/StationList.cpp models/StationList.cpp
widgets/FrequencyLineEdit.cpp widgets/FrequencyLineEdit.cpp
widgets/FrequencyDeltaLineEdit.cpp
item_delegates/CandidateKeyFilter.cpp item_delegates/CandidateKeyFilter.cpp
item_delegates/ForeignKeyDelegate.cpp item_delegates/ForeignKeyDelegate.cpp
validators/LiveFrequencyValidator.cpp validators/LiveFrequencyValidator.cpp
@ -271,9 +280,12 @@ set (wsjt_qt_CXXSRCS
widgets/CabrilloLogWindow.cpp widgets/CabrilloLogWindow.cpp
item_delegates/CallsignDelegate.cpp item_delegates/CallsignDelegate.cpp
item_delegates/MaidenheadLocatorDelegate.cpp item_delegates/MaidenheadLocatorDelegate.cpp
item_delegates/FrequencyDelegate.cpp
item_delegates/FrequencyDeltaDelegate.cpp
models/CabrilloLog.cpp models/CabrilloLog.cpp
logbook/AD1CCty.cpp logbook/AD1CCty.cpp
logbook/WorkedBefore.cpp logbook/WorkedBefore.cpp
logbook/Multiplier.cpp
) )
set (wsjt_qtmm_CXXSRCS set (wsjt_qtmm_CXXSRCS
@ -356,6 +368,7 @@ set (wsjt_FSRCS
lib/jt65_decode.f90 lib/jt65_decode.f90
lib/jt65_mod.f90 lib/jt65_mod.f90
lib/ft8_decode.f90 lib/ft8_decode.f90
lib/ft4_decode.f90
lib/jt9_decode.f90 lib/jt9_decode.f90
lib/options.f90 lib/options.f90
lib/packjt.f90 lib/packjt.f90
@ -381,6 +394,7 @@ set (wsjt_FSRCS
lib/azdist.f90 lib/azdist.f90
lib/badmsg.f90 lib/badmsg.f90
lib/ft8/baseline.f90 lib/ft8/baseline.f90
lib/ft4/ft4_baseline.f90
lib/bpdecode40.f90 lib/bpdecode40.f90
lib/bpdecode128_90.f90 lib/bpdecode128_90.f90
lib/ft8/bpdecode174_91.f90 lib/ft8/bpdecode174_91.f90
@ -394,6 +408,7 @@ set (wsjt_FSRCS
lib/chkhist.f90 lib/chkhist.f90
lib/chkmsg.f90 lib/chkmsg.f90
lib/chkss2.f90 lib/chkss2.f90
lib/ft4/clockit.f90
lib/ft8/compress.f90 lib/ft8/compress.f90
lib/coord.f90 lib/coord.f90
lib/db.f90 lib/db.f90
@ -455,6 +470,7 @@ set (wsjt_FSRCS
lib/ft8.f90 lib/ft8.f90
lib/ft8dec.f90 lib/ft8dec.f90
lib/ft8/ft8sim.f90 lib/ft8/ft8sim.f90
lib/ft8/ft8sim_gfsk.f90
lib/gen4.f90 lib/gen4.f90
lib/gen65.f90 lib/gen65.f90
lib/gen9.f90 lib/gen9.f90
@ -462,12 +478,16 @@ set (wsjt_FSRCS
lib/ft8/genft8.f90 lib/ft8/genft8.f90
lib/genmsk_128_90.f90 lib/genmsk_128_90.f90
lib/genmsk40.f90 lib/genmsk40.f90
lib/ft4/genft4.f90
lib/ft4/gen_ft4wave.f90
lib/ft8/gen_ft8wave.f90
lib/genqra64.f90 lib/genqra64.f90
lib/ft8/genft8refsig.f90 lib/ft8/genft8refsig.f90
lib/genwspr.f90 lib/genwspr.f90
lib/geodist.f90 lib/geodist.f90
lib/getlags.f90 lib/getlags.f90
lib/getmet4.f90 lib/getmet4.f90
lib/ft2/gfsk_pulse.f90
lib/graycode.f90 lib/graycode.f90
lib/graycode65.f90 lib/graycode65.f90
lib/grayline.f90 lib/grayline.f90
@ -506,6 +526,10 @@ set (wsjt_FSRCS
lib/msk144signalquality.f90 lib/msk144signalquality.f90
lib/msk144sim.f90 lib/msk144sim.f90
lib/mskrtd.f90 lib/mskrtd.f90
lib/nuttal_window.f90
lib/ft4/ft4sim.f90
lib/ft4/ft4sim_mult.f90
lib/ft4/ft4_downsample.f90
lib/77bit/my_hash.f90 lib/77bit/my_hash.f90
lib/wsprd/osdwspr.f90 lib/wsprd/osdwspr.f90
lib/ft8/osd174_91.f90 lib/ft8/osd174_91.f90
@ -539,6 +563,7 @@ set (wsjt_FSRCS
lib/stdmsg.f90 lib/stdmsg.f90
lib/subtract65.f90 lib/subtract65.f90
lib/ft8/subtractft8.f90 lib/ft8/subtractft8.f90
lib/ft4/subtractft4.f90
lib/sun.f90 lib/sun.f90
lib/symspec.f90 lib/symspec.f90
lib/symspec2.f90 lib/symspec2.f90
@ -546,8 +571,11 @@ set (wsjt_FSRCS
lib/sync4.f90 lib/sync4.f90
lib/sync64.f90 lib/sync64.f90
lib/sync65.f90 lib/sync65.f90
lib/ft4/getcandidates4.f90
lib/ft4/get_ft4_bitmetrics.f90
lib/ft8/sync8.f90 lib/ft8/sync8.f90
lib/ft8/sync8d.f90 lib/ft8/sync8d.f90
lib/ft4/sync4d.f90
lib/sync9.f90 lib/sync9.f90
lib/sync9f.f90 lib/sync9f.f90
lib/sync9w.f90 lib/sync9w.f90
@ -794,7 +822,7 @@ if (WIN32)
endif (NOT AXSERVER) endif (NOT AXSERVER)
string (REPLACE "\"" "" AXSERVER ${AXSERVER}) string (REPLACE "\"" "" AXSERVER ${AXSERVER})
file (TO_CMAKE_PATH ${AXSERVER} AXSERVERSRCS) file (TO_CMAKE_PATH ${AXSERVER} AXSERVERSRCS)
endif (WIN32) endif ()
# #
@ -827,9 +855,6 @@ if (Boost_NO_SYSTEM_PATHS)
set (BOOST_ROOT ${PROJECT_SOURCE_DIR}/boost) set (BOOST_ROOT ${PROJECT_SOURCE_DIR}/boost)
endif () endif ()
find_package (Boost 1.63 REQUIRED) find_package (Boost 1.63 REQUIRED)
if (Boost_FOUND)
include_directories (${Boost_INCLUDE_DIRS})
endif ()
# #
# OpenMP # OpenMP
@ -860,10 +885,7 @@ message (STATUS "hamlib_LIBRARY_DIRS: ${hamlib_LIBRARY_DIRS}")
# #
# Widgets finds its own dependencies. # Widgets finds its own dependencies.
find_package (Qt5Widgets 5 REQUIRED) find_package (Qt5 COMPONENTS Widgets Multimedia PrintSupport Sql LinguistTools REQUIRED)
find_package (Qt5Multimedia 5 REQUIRED)
find_package (Qt5PrintSupport 5 REQUIRED)
find_package (Qt5Sql 5 REQUIRED)
if (WIN32) if (WIN32)
add_definitions (-DQT_NEEDS_QTMAIN) add_definitions (-DQT_NEEDS_QTMAIN)
@ -1069,11 +1091,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}) 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 # embedded resources
function (add_resources resources path) function (add_resources resources path)
foreach (resource_file_ ${ARGN}) foreach (resource_file_ ${ARGN})
get_filename_component (name_ ${resource_file_} NAME) 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_) file (TO_NATIVE_PATH ${path}/${name_} dest_)
set (resources_ "${resources_}\n <file alias=\"${dest_}\">${source_}</file>") set (resources_ "${resources_}\n <file alias=\"${dest_}\">${source_}</file>")
set (${resources} ${${resources}}${resources_} PARENT_SCOPE) set (${resources} ${${resources}}${resources_} PARENT_SCOPE)
@ -1082,6 +1134,7 @@ endfunction (add_resources resources path)
add_resources (wsjtx_RESOURCES "" ${TOP_LEVEL_RESOURCES}) add_resources (wsjtx_RESOURCES "" ${TOP_LEVEL_RESOURCES})
add_resources (wsjtx_RESOURCES /Palettes ${PALETTE_FILES}) add_resources (wsjtx_RESOURCES /Palettes ${PALETTE_FILES})
add_resources (wsjtx_RESOURCES /Translations ${QM_FILES})
configure_file (wsjtx.qrc.in wsjtx.qrc @ONLY) configure_file (wsjtx.qrc.in wsjtx.qrc @ONLY)
@ -1102,7 +1155,6 @@ if (WIN32)
wrap_ax_server (GENAXSRCS ${AXSERVERSRCS}) wrap_ax_server (GENAXSRCS ${AXSERVERSRCS})
endif (WIN32) endif (WIN32)
# #
# targets # targets
# #
@ -1221,6 +1273,9 @@ target_link_libraries (jt49sim wsjt_fort wsjt_cxx)
add_executable (allsim lib/allsim.f90 wsjtx.rc) add_executable (allsim lib/allsim.f90 wsjtx.rc)
target_link_libraries (allsim wsjt_fort wsjt_cxx) 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) add_executable (jt65code lib/jt65code.f90 wsjtx.rc)
target_link_libraries (jt65code wsjt_fort wsjt_cxx) target_link_libraries (jt65code wsjt_fort wsjt_cxx)
@ -1254,9 +1309,21 @@ target_link_libraries (ft8 wsjt_fort wsjt_cxx)
add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc) add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc)
target_link_libraries (ft8sim wsjt_fort wsjt_cxx) target_link_libraries (ft8sim wsjt_fort wsjt_cxx)
add_executable (ft8sim_gfsk lib/ft8/ft8sim_gfsk.f90 wsjtx.rc)
target_link_libraries (ft8sim_gfsk wsjt_fort wsjt_cxx)
add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc) add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc)
target_link_libraries (msk144sim wsjt_fort wsjt_cxx) 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 (ft4sim_mult lib/ft4/ft4sim_mult.f90 wsjtx.rc)
target_link_libraries (ft4sim_mult 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) endif(WSJT_BUILD_UTILS)
# build the main application # build the main application
@ -1606,6 +1673,7 @@ if (NOT is_debug_build)
install ( install (
DIRECTORY DIRECTORY
${QT_PLUGINS_DIR}/platforms ${QT_PLUGINS_DIR}/platforms
${QT_PLUGINS_DIR}/styles
${QT_PLUGINS_DIR}/accessible ${QT_PLUGINS_DIR}/accessible
${QT_PLUGINS_DIR}/audio ${QT_PLUGINS_DIR}/audio
${QT_PLUGINS_DIR}/imageformats ${QT_PLUGINS_DIR}/imageformats

View File

@ -167,8 +167,11 @@
#include "MetaDataRegistry.hpp" #include "MetaDataRegistry.hpp"
#include "SettingsGroup.hpp" #include "SettingsGroup.hpp"
#include "widgets/FrequencyLineEdit.hpp" #include "widgets/FrequencyLineEdit.hpp"
#include "widgets/FrequencyDeltaLineEdit.hpp"
#include "item_delegates/CandidateKeyFilter.hpp" #include "item_delegates/CandidateKeyFilter.hpp"
#include "item_delegates/ForeignKeyDelegate.hpp" #include "item_delegates/ForeignKeyDelegate.hpp"
#include "item_delegates/FrequencyDelegate.hpp"
#include "item_delegates/FrequencyDeltaDelegate.hpp"
#include "TransceiverFactory.hpp" #include "TransceiverFactory.hpp"
#include "Transceiver.hpp" #include "Transceiver.hpp"
#include "models/Bands.hpp" #include "models/Bands.hpp"
@ -209,6 +212,7 @@ namespace
|LB|NU|YT|PEI |LB|NU|YT|PEI
|DC # District of Columbia |DC # District of Columbia
|DX # anyone else |DX # anyone else
|SCC # Slovenia Contest Club contest
) )
)", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption}; )", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption};
@ -247,6 +251,8 @@ namespace
class FrequencyDialog final class FrequencyDialog final
: public QDialog : public QDialog
{ {
Q_OBJECT
public: public:
using Item = FrequencyList_v2::Item; using Item = FrequencyList_v2::Item;
@ -293,6 +299,8 @@ private:
class StationDialog final class StationDialog final
: public QDialog : public QDialog
{ {
Q_OBJECT
public: public:
explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr) explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr)
: QDialog {parent} : QDialog {parent}
@ -563,6 +571,7 @@ private:
DecodeHighlightingModel decode_highlighing_model_; DecodeHighlightingModel decode_highlighing_model_;
DecodeHighlightingModel next_decode_highlighing_model_; DecodeHighlightingModel next_decode_highlighing_model_;
bool highlight_by_mode_; bool highlight_by_mode_;
bool include_WAE_entities_;
int LotW_days_since_upload_; int LotW_days_since_upload_;
TransceiverFactory::ParameterPack rig_params_; TransceiverFactory::ParameterPack rig_params_;
@ -608,6 +617,7 @@ private:
bool miles_; bool miles_;
bool quick_call_; bool quick_call_;
bool disable_TX_on_73_; bool disable_TX_on_73_;
bool force_call_1st_;
bool alternate_bindings_; bool alternate_bindings_;
int watchdog_; int watchdog_;
bool TX_messages_; bool TX_messages_;
@ -630,6 +640,7 @@ private:
bool udpWindowToFront_; bool udpWindowToFront_;
bool udpWindowRestore_; bool udpWindowRestore_;
DataMode data_mode_; DataMode data_mode_;
bool bLowSidelobes_;
bool pwrBandTxMemory_; bool pwrBandTxMemory_;
bool pwrBandTuneMemory_; bool pwrBandTuneMemory_;
@ -703,6 +714,7 @@ bool Configuration::clear_DX () const {return m_->clear_DX_;}
bool Configuration::miles () const {return m_->miles_;} bool Configuration::miles () const {return m_->miles_;}
bool Configuration::quick_call () const {return m_->quick_call_;} bool Configuration::quick_call () const {return m_->quick_call_;}
bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;} 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_;} bool Configuration::alternate_bindings() const {return m_->alternate_bindings_;}
int Configuration::watchdog () const {return m_->watchdog_;} int Configuration::watchdog () const {return m_->watchdog_;}
bool Configuration::TX_messages () const {return m_->TX_messages_;} bool Configuration::TX_messages () const {return m_->TX_messages_;}
@ -714,12 +726,14 @@ bool Configuration::x2ToneSpacing() const {return m_->x2ToneSpacing_;}
bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;} bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;}
bool Configuration::split_mode () const {return m_->split_mode ();} bool Configuration::split_mode () const {return m_->split_mode ();}
QString Configuration::opCall() const {return m_->opCall_;} 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_;} QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;} auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;}
bool Configuration::broadcast_to_n1mm () const {return m_->broadcast_to_n1mm_;} bool Configuration::broadcast_to_n1mm () const {return m_->broadcast_to_n1mm_;}
bool Configuration::lowSidelobes() const {return m_->bLowSidelobes_;}
bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;} bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;}
bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;} bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;}
Bands * Configuration::bands () {return &m_->bands_;} Bands * Configuration::bands () {return &m_->bands_;}
@ -739,6 +753,7 @@ bool Configuration::pwrBandTuneMemory () const {return m_->pwrBandTuneMemory_;}
LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;} LotWUsers const& Configuration::lotw_users () const {return m_->lotw_users_;}
DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;} DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;}
bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;} 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) void Configuration::set_calibration (CalibrationParams params)
{ {
@ -944,6 +959,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, station_insert_action_ {tr ("&Insert ..."), nullptr} , station_insert_action_ {tr ("&Insert ..."), nullptr}
, station_dialog_ {new StationDialog {&next_stations_, &bands_, this}} , station_dialog_ {new StationDialog {&next_stations_, &bands_, this}}
, highlight_by_mode_ {false} , highlight_by_mode_ {false}
, include_WAE_entities_ {false}
, LotW_days_since_upload_ {0} , LotW_days_since_upload_ {0}
, last_port_type_ {TransceiverFactory::Capabilities::none} , last_port_type_ {TransceiverFactory::Capabilities::none}
, rig_is_dummy_ {false} , rig_is_dummy_ {false}
@ -1012,6 +1028,9 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
}); });
lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv")); lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv"));
// load the dictionary if it exists
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), false);
// //
// validation // validation
ui_->callsign_line_edit->setValidator (new CallsignValidator {this}); ui_->callsign_line_edit->setValidator (new CallsignValidator {this});
@ -1070,6 +1089,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
// //
fill_port_combo_box (ui_->PTT_port_combo_box); fill_port_combo_box (ui_->PTT_port_combo_box);
ui_->PTT_port_combo_box->addItem ("CAT"); ui_->PTT_port_combo_box->addItem ("CAT");
ui_->PTT_port_combo_box->setItemData (ui_->PTT_port_combo_box->count () - 1, "Delegate to proxy CAT service", Qt::ToolTipRole);
// //
// setup hooks to keep audio channels aligned with devices // setup hooks to keep audio channels aligned with devices
@ -1109,9 +1129,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true); ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true);
// delegates // delegates
auto frequencies_item_delegate = new QStyledItemDelegate {this}; ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this});
frequencies_item_delegate->setItemEditorFactory (item_editor_factory ());
ui_->frequencies_table_view->setItemDelegate (frequencies_item_delegate);
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {&regions_, 0, 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}); ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::mode_column, new ForeignKeyDelegate {&modes_, 0, this});
@ -1150,9 +1168,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder); ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder);
// stations delegates // stations delegates
auto stations_item_delegate = new QStyledItemDelegate {this}; ui_->stations_table_view->setItemDelegateForColumn (StationList::offset_column, new FrequencyDeltaDelegate {this});
stations_item_delegate->setItemEditorFactory (item_editor_factory ());
ui_->stations_table_view->setItemDelegate (stations_item_delegate);
ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this}); ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this});
// stations actions // stations actions
@ -1235,6 +1251,7 @@ void Configuration::impl::initialize_models ()
ui_->miles_check_box->setChecked (miles_); ui_->miles_check_box->setChecked (miles_);
ui_->quick_call_check_box->setChecked (quick_call_); ui_->quick_call_check_box->setChecked (quick_call_);
ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_); 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_->alternate_bindings_check_box->setChecked (alternate_bindings_);
ui_->tx_watchdog_spin_box->setValue (watchdog_); ui_->tx_watchdog_spin_box->setValue (watchdog_);
ui_->TX_messages_check_box->setChecked (TX_messages_); ui_->TX_messages_check_box->setChecked (TX_messages_);
@ -1285,6 +1302,8 @@ void Configuration::impl::initialize_models ()
ui_->udpWindowRestore->setChecked(udpWindowRestore_); ui_->udpWindowRestore->setChecked(udpWindowRestore_);
ui_->calibration_intercept_spin_box->setValue (calibration_.intercept); ui_->calibration_intercept_spin_box->setValue (calibration_.intercept);
ui_->calibration_slope_ppm_spin_box->setValue (calibration_.slope_ppm); ui_->calibration_slope_ppm_spin_box->setValue (calibration_.slope_ppm);
ui_->rbLowSidelobes->setChecked(bLowSidelobes_);
if(!bLowSidelobes_) ui_->rbMaxSensitivity->setChecked(true);
if (rig_params_.ptt_port.isEmpty ()) if (rig_params_.ptt_port.isEmpty ())
{ {
@ -1306,6 +1325,7 @@ void Configuration::impl::initialize_models ()
next_decode_highlighing_model_.items (decode_highlighing_model_.items ()); next_decode_highlighing_model_.items (decode_highlighing_model_.items ());
ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_); 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_); ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_);
set_rig_invariants (); set_rig_invariants ();
@ -1454,6 +1474,7 @@ void Configuration::impl::read_settings ()
if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items (); if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items ();
decode_highlighing_model_.items (highlight_items); decode_highlighing_model_.items (highlight_items);
highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool (); 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_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt ();
lotw_users_.set_age_constraint (LotW_days_since_upload_); lotw_users_.set_age_constraint (LotW_days_since_upload_);
@ -1476,6 +1497,7 @@ void Configuration::impl::read_settings ()
rig_params_.audio_source = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value<TransceiverFactory::TXAudioSource> (); rig_params_.audio_source = settings_->value ("TXAudioSource", QVariant::fromValue (TransceiverFactory::TX_audio_source_front)).value<TransceiverFactory::TXAudioSource> ();
rig_params_.ptt_port = settings_->value ("PTTport").toString (); rig_params_.ptt_port = settings_->value ("PTTport").toString ();
data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value<Configuration::DataMode> (); data_mode_ = settings_->value ("DataMode", QVariant::fromValue (data_mode_none)).value<Configuration::DataMode> ();
bLowSidelobes_ = settings_->value("LowSidelobes",true).toBool();
prompt_to_log_ = settings_->value ("PromptToLog", false).toBool (); prompt_to_log_ = settings_->value ("PromptToLog", false).toBool ();
autoLog_ = settings_->value ("AutoLog", false).toBool (); autoLog_ = settings_->value ("AutoLog", false).toBool ();
decodes_from_top_ = settings_->value ("DecodesFromTop", false).toBool (); decodes_from_top_ = settings_->value ("DecodesFromTop", false).toBool ();
@ -1486,6 +1508,7 @@ void Configuration::impl::read_settings ()
miles_ = settings_->value ("Miles", false).toBool (); miles_ = settings_->value ("Miles", false).toBool ();
quick_call_ = settings_->value ("QuickCall", false).toBool (); quick_call_ = settings_->value ("QuickCall", false).toBool ();
disable_TX_on_73_ = settings_->value ("73TxDisable", 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 (); alternate_bindings_ = settings_->value ("AlternateBindings", false).toBool ();
watchdog_ = settings_->value ("TxWatchdog", 6).toInt (); watchdog_ = settings_->value ("TxWatchdog", 6).toInt ();
TX_messages_ = settings_->value ("Tx2QSO", true).toBool (); TX_messages_ = settings_->value ("Tx2QSO", true).toBool ();
@ -1565,6 +1588,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ())); settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ()));
settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ())); settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ()));
settings_->setValue ("HighlightByMode", highlight_by_mode_); settings_->setValue ("HighlightByMode", highlight_by_mode_);
settings_->setValue ("IncludeWAEEntities", include_WAE_entities_);
settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_); settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_);
settings_->setValue ("toRTTY", log_as_RTTY_); settings_->setValue ("toRTTY", log_as_RTTY_);
settings_->setValue ("dBtoComments", report_in_comments_); settings_->setValue ("dBtoComments", report_in_comments_);
@ -1577,6 +1601,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.stop_bits)); settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.stop_bits));
settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.handshake)); settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.handshake));
settings_->setValue ("DataMode", QVariant::fromValue (data_mode_)); settings_->setValue ("DataMode", QVariant::fromValue (data_mode_));
settings_->setValue ("LowSidelobes",bLowSidelobes_);
settings_->setValue ("PromptToLog", prompt_to_log_); settings_->setValue ("PromptToLog", prompt_to_log_);
settings_->setValue ("AutoLog", autoLog_); settings_->setValue ("AutoLog", autoLog_);
settings_->setValue ("DecodesFromTop", decodes_from_top_); settings_->setValue ("DecodesFromTop", decodes_from_top_);
@ -1587,6 +1612,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("Miles", miles_); settings_->setValue ("Miles", miles_);
settings_->setValue ("QuickCall", quick_call_); settings_->setValue ("QuickCall", quick_call_);
settings_->setValue ("73TxDisable", disable_TX_on_73_); settings_->setValue ("73TxDisable", disable_TX_on_73_);
settings_->setValue ("ForceCallFirst", force_call_1st_);
settings_->setValue ("AlternateBindings", alternate_bindings_); settings_->setValue ("AlternateBindings", alternate_bindings_);
settings_->setValue ("TxWatchdog", watchdog_); settings_->setValue ("TxWatchdog", watchdog_);
settings_->setValue ("Tx2QSO", TX_messages_); settings_->setValue ("Tx2QSO", TX_messages_);
@ -2031,10 +2057,12 @@ void Configuration::impl::accept ()
miles_ = ui_->miles_check_box->isChecked (); miles_ = ui_->miles_check_box->isChecked ();
quick_call_ = ui_->quick_call_check_box->isChecked (); quick_call_ = ui_->quick_call_check_box->isChecked ();
disable_TX_on_73_ = ui_->disable_TX_on_73_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 (); alternate_bindings_ = ui_->alternate_bindings_check_box->isChecked ();
watchdog_ = ui_->tx_watchdog_spin_box->value (); watchdog_ = ui_->tx_watchdog_spin_box->value ();
TX_messages_ = ui_->TX_messages_check_box->isChecked (); TX_messages_ = ui_->TX_messages_check_box->isChecked ();
data_mode_ = static_cast<DataMode> (ui_->TX_mode_button_group->checkedId ()); data_mode_ = static_cast<DataMode> (ui_->TX_mode_button_group->checkedId ());
bLowSidelobes_ = ui_->rbLowSidelobes->isChecked();
save_directory_ = ui_->save_path_display_label->text (); save_directory_ = ui_->save_path_display_label->text ();
azel_directory_ = ui_->azel_path_display_label->text (); azel_directory_ = ui_->azel_path_display_label->text ();
enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked (); enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked ();
@ -2064,7 +2092,12 @@ void Configuration::impl::accept ()
Q_EMIT self_->udp_server_port_changed (new_port); 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_name_ = ui_->n1mm_server_name_line_edit->text ();
n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value (); n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value ();
broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked (); broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked ();
@ -2097,6 +2130,7 @@ void Configuration::impl::accept ()
Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_); Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_);
} }
highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked (); 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_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value ();
lotw_users_.set_age_constraint (LotW_days_since_upload_); lotw_users_.set_age_constraint (LotW_days_since_upload_);
@ -2155,7 +2189,7 @@ void Configuration::impl::on_rescan_log_push_button_clicked (bool /*clicked*/)
void Configuration::impl::on_LotW_CSV_fetch_push_button_clicked (bool /*checked*/) void Configuration::impl::on_LotW_CSV_fetch_push_button_clicked (bool /*checked*/)
{ {
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), true); lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), true, true);
ui_->LotW_CSV_fetch_push_button->setEnabled (false); ui_->LotW_CSV_fetch_push_button->setEnabled (false);
} }
@ -2919,9 +2953,15 @@ void Configuration::impl::fill_port_combo_box (QComboBox * cb)
// remove possibly confusing Windows device path (OK because // remove possibly confusing Windows device path (OK because
// it gets added back by Hamlib) // it gets added back by Hamlib)
cb->addItem (p.systemLocation ().remove (QRegularExpression {R"(^\\\\\.\\)"})); cb->addItem (p.systemLocation ().remove (QRegularExpression {R"(^\\\\\.\\)"}));
auto tip = QString {"%1 %2 %3"}.arg (p.manufacturer ()).arg (p.serialNumber ()).arg (p.description ()).trimmed ();
if (tip.size ())
{
cb->setItemData (cb->count () - 1, tip, Qt::ToolTipRole);
}
} }
} }
cb->addItem("USB"); cb->addItem ("USB");
cb->setItemData (cb->count () - 1, "Custom USB device", Qt::ToolTipRole);
cb->setEditText (current_text); cb->setEditText (current_text);
} }

View File

@ -127,6 +127,7 @@ public:
bool miles () const; bool miles () const;
bool quick_call () const; bool quick_call () const;
bool disable_TX_on_73 () const; bool disable_TX_on_73 () const;
bool force_call_1st() const;
bool alternate_bindings() const; bool alternate_bindings() const;
int watchdog () const; int watchdog () const;
bool TX_messages () const; bool TX_messages () const;
@ -137,6 +138,7 @@ public:
bool twoPass() const; bool twoPass() const;
bool bFox() const; bool bFox() const;
bool bHound() const; bool bHound() const;
bool bLowSidelobes() const;
bool x2ToneSpacing() const; bool x2ToneSpacing() const;
bool x4ToneSpacing() const; bool x4ToneSpacing() const;
bool MyDx() const; bool MyDx() const;
@ -146,12 +148,14 @@ public:
bool EMEonly() const; bool EMEonly() const;
bool post_decodes () const; bool post_decodes () const;
QString opCall() const; QString opCall() const;
void opCall (QString const&);
QString udp_server_name () const; QString udp_server_name () const;
port_type udp_server_port () const; port_type udp_server_port () const;
QString n1mm_server_name () const; QString n1mm_server_name () const;
port_type n1mm_server_port () const; port_type n1mm_server_port () const;
bool valid_n1mm_info () const; bool valid_n1mm_info () const;
bool broadcast_to_n1mm() const; bool broadcast_to_n1mm() const;
bool lowSidelobes() const;
bool accept_udp_requests () const; bool accept_udp_requests () const;
bool udpWindowToFront () const; bool udpWindowToFront () const;
bool udpWindowRestore () const; bool udpWindowRestore () const;
@ -173,6 +177,7 @@ public:
LotWUsers const& lotw_users () const; LotWUsers const& lotw_users () const;
DecodeHighlightingModel const& decode_highlighting () const; DecodeHighlightingModel const& decode_highlighting () const;
bool highlight_by_mode () const; bool highlight_by_mode () const;
bool include_WAE_entities () const;
enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, FOX, HOUND}; enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, FOX, HOUND};
SpecialOperatingActivity special_op_id () const; SpecialOperatingActivity special_op_id () const;
@ -266,6 +271,7 @@ public:
// //
Q_SIGNAL void udp_server_changed (QString const& udp_server) const; 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 udp_server_port_changed (port_type server_port) const;
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
// signal updates to decode highlighting // signal updates to decode highlighting
Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const; Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const;

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>546</width> <width>546</width>
<height>536</height> <height>553</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -301,7 +301,14 @@
<string>Behavior</string> <string>Behavior</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_8"> <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"> <layout class="QHBoxLayout" name="horizontalLayout_12">
<item> <item>
<spacer name="horizontalSpacer_7"> <spacer name="horizontalSpacer_7">
@ -347,10 +354,27 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="1"> <item row="0" column="1">
<widget class="QCheckBox" name="decode_at_52s_check_box"> <widget class="QCheckBox" name="enable_VHF_features_check_box">
<property name="text"> <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> </property>
</widget> </widget>
</item> </item>
@ -367,31 +391,35 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="1" column="0">
<widget class="QCheckBox" name="single_decode_check_box"> <widget class="QCheckBox" name="monitor_last_used_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">
<property name="toolTip"> <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>
<property name="text"> <property name="text">
<string>Allow Tx frequency changes while transmitting</string> <string>Monitor returns to last used frequency</string>
</property> </property>
</widget> </widget>
</item> </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"> <layout class="QHBoxLayout" name="horizontalLayout_9">
<item> <item>
<widget class="QCheckBox" name="CW_id_after_73_check_box"> <widget class="QCheckBox" name="CW_id_after_73_check_box">
@ -444,34 +472,6 @@ quiet period when decoding is done.</string>
</item> </item>
</layout> </layout>
</item> </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"> <item row="2" column="0">
<widget class="QCheckBox" name="quick_call_check_box"> <widget class="QCheckBox" name="quick_call_check_box">
<property name="toolTip"> <property name="toolTip">
@ -482,6 +482,13 @@ text message.</string>
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -1912,7 +1919,7 @@ for assessing propagation and system performance.</string>
<item> <item>
<widget class="QGroupBox" name="n1mm_group_box"> <widget class="QGroupBox" name="n1mm_group_box">
<property name="title"> <property name="title">
<string>N1MM Logger+ Broadcasts</string> <string>Secondary UDP Server (deprecated)</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_15"> <layout class="QFormLayout" name="formLayout_15">
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
@ -1928,7 +1935,7 @@ for assessing propagation and system performance.</string>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="n1mm_server_name_label"> <widget class="QLabel" name="n1mm_server_name_label">
<property name="text"> <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>
<property name="buddy"> <property name="buddy">
<cstring>n1mm_server_name_line_edit</cstring> <cstring>n1mm_server_name_line_edit</cstring>
@ -1945,7 +1952,7 @@ for assessing propagation and system performance.</string>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="n1mm_server_port_label"> <widget class="QLabel" name="n1mm_server_port_label">
<property name="text"> <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>
<property name="buddy"> <property name="buddy">
<cstring>n1mm_server_port_spin_box</cstring> <cstring>n1mm_server_port_spin_box</cstring>
@ -2287,6 +2294,23 @@ Right click for insert and delete options.</string>
</item> </item>
</layout> </layout>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -2299,35 +2323,6 @@ Right click for insert and delete options.</string>
<string>Logbook of the World User Validation</string> <string>Logbook of the World User Validation</string>
</property> </property>
<layout class="QFormLayout" name="formLayout_18"> <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"> <item row="1" column="0">
<widget class="QLabel" name="label_15"> <widget class="QLabel" name="label_15">
<property name="text"> <property name="text">
@ -2362,6 +2357,35 @@ Right click for insert and delete options.</string>
</item> </item>
</layout> </layout>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -2469,7 +2493,7 @@ Right click for insert and delete options.</string>
<item row="1" column="0" colspan="2"> <item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="gbSpecialOpActivity"> <widget class="QGroupBox" name="gbSpecialOpActivity">
<property name="title"> <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>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <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> <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>
<property name="text"> <property name="text">
<string>ARRL RTTY Roundup</string> <string>RTTY Roundup messages</string>
</property> </property>
<attribute name="buttonGroup"> <attribute name="buttonGroup">
<string notr="true">special_op_activity_button_group</string> <string notr="true">special_op_activity_button_group</string>
@ -2821,6 +2845,48 @@ Right click for insert and delete options.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox_7">
<property name="minimumSize">
<size>
<width>0</width>
<height>50</height>
</size>
</property>
<property name="title">
<string>Waterfall spectra</string>
</property>
<widget class="QRadioButton" name="rbLowSidelobes">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>91</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Low sidelobes</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QRadioButton" name="rbMaxSensitivity">
<property name="geometry">
<rect>
<x>120</x>
<y>20</y>
<width>92</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>Most sensitive</string>
</property>
</widget>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -3036,13 +3102,13 @@ Right click for insert and delete options.</string>
</connection> </connection>
</connections> </connections>
<buttongroups> <buttongroups>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/> <buttongroup name="special_op_activity_button_group"/>
<buttongroup name="PTT_method_button_group"/> <buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="split_mode_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_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/> <buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
</buttongroups> </buttongroups>
</ui> </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}; throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
} }
poll (); do_poll ();
return resolution; return resolution;
} }
@ -247,7 +247,7 @@ void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
update_mode (m); update_mode (m);
} }
void DXLabSuiteCommanderTransceiver::poll () void DXLabSuiteCommanderTransceiver::do_poll ()
{ {
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS #if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
bool quiet {false}; bool quiet {false};

View File

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

View File

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

View File

@ -2,6 +2,7 @@
#include <QDateTime> #include <QDateTime>
#include <QtAlgorithms> #include <QtAlgorithms>
#include <QDebug> #include <QDebug>
#include <math.h>
#include "commons.h" #include "commons.h"
#include "moc_Detector.cpp" #include "moc_Detector.cpp"
@ -10,7 +11,7 @@ extern "C" {
void fil4_(qint16*, qint32*, qint16*, qint32*); void fil4_(qint16*, qint32*, qint16*, qint32*);
} }
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds, Detector::Detector (unsigned frameRate, double periodLengthInSeconds,
unsigned downSampleFactor, QObject * parent) unsigned downSampleFactor, QObject * parent)
: AudioDevice (parent) : AudioDevice (parent)
, m_frameRate (frameRate) , m_frameRate (frameRate)
@ -54,12 +55,14 @@ void Detector::clear ()
qint64 Detector::writeData (char const * data, qint64 maxSize) qint64 Detector::writeData (char const * data, qint64 maxSize)
{ {
int ns=secondInPeriod(); static unsigned mstr0=999999;
if(ns < m_ns) { // When ns has wrapped around to zero, restart the buffers 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; dec_data.params.kin = 0;
m_bufferPos = 0; m_bufferPos = 0;
} }
m_ns=ns; mstr0=mstr;
// no torn frames // no torn frames
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ()))); 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 ())) { if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
<< " frames of data on the floor!" << " frames of data on the floor!"
<< dec_data.params.kin << ns; << dec_data.params.kin << mstr;
} }
for (unsigned remaining = framesAccepted; remaining; ) { 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 return maxSize; // we drop any data past the end of the buffer on
// the floor until the next period starts // 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 // 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; bool reset () override;
Q_SIGNAL void framesWritten (qint64) const; Q_SIGNAL void framesWritten (qint64) const;
@ -40,10 +41,9 @@ protected:
private: private:
void clear (); // discard buffer contents void clear (); // discard buffer contents
unsigned secondInPeriod () const;
unsigned m_frameRate; unsigned m_frameRate;
unsigned m_period; double m_period;
unsigned m_downSampleFactor; unsigned m_downSampleFactor;
qint32 m_samplesPerFFT; // after any down sampling qint32 m_samplesPerFFT; // after any down sampling
qint32 m_ns; qint32 m_ns;

View File

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

View File

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

View File

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

View File

@ -27,6 +27,8 @@ class QByteArray;
class HRDTransceiver final class HRDTransceiver final
: public PollingTransceiver : public PollingTransceiver
{ {
Q_OBJECT
public: public:
static void register_transceivers (TransceiverFactory::Transceivers *, int id); static void register_transceivers (TransceiverFactory::Transceivers *, int id);
@ -48,7 +50,7 @@ protected:
void do_ptt (bool on) override; void do_ptt (bool on) override;
// Implement the PollingTransceiver interface. // Implement the PollingTransceiver interface.
void poll () override; void do_poll () override;
private: private:
QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false); 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 resolution = -1; // best guess
} }
poll (); do_poll ();
TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution); TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution);
return resolution; return resolution;
@ -898,7 +898,7 @@ void HamlibTransceiver::do_mode (MODE mode)
update_mode (mode); update_mode (mode);
} }
void HamlibTransceiver::poll () void HamlibTransceiver::do_poll ()
{ {
#if !WSJT_TRACE_CAT_POLLS #if !WSJT_TRACE_CAT_POLLS
#if defined (NDEBUG) #if defined (NDEBUG)

View File

@ -21,9 +21,9 @@ extern "C"
class HamlibTransceiver final class HamlibTransceiver final
: public PollingTransceiver : public PollingTransceiver
{ {
Q_OBJECT; // for translation context Q_OBJECT // for translation context
public: public:
static void register_transceivers (TransceiverFactory::Transceivers *); static void register_transceivers (TransceiverFactory::Transceivers *);
static void unregister_transceivers (); static void unregister_transceivers ();
@ -40,7 +40,7 @@ class HamlibTransceiver final
void do_mode (MODE) override; void do_mode (MODE) override;
void do_ptt (bool) override; void do_ptt (bool) override;
void poll () override; void do_poll () override;
void error_check (int ret_code, QString const& doing) const; void error_check (int ret_code, QString const& doing) const;
void set_conf (char const * item, char const * value); 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, preferably v5.5 or later is required to build WSJT-X.
Qt v5 multimedia support and serial port is necessary as well as the Qt v5 multimedia support, serial port, and Linguist is necessary as
core Qt v5 components, normally installing the Qt multimedia well as the core Qt v5 components, normally installing the Qt
development package and Qt serialport development package are multimedia development, Qt serialport development packages, and the Qt
sufficient to pull in all the required Qt components and dependants as Linguist packages are sufficient to pull in all the required Qt
a single transaction. On some systems the Qt multimedia plugin components and dependants as a single transaction. On some systems
component is separate in the distribution repository an it may also the Qt multimedia plugin component is separate in the distribution
need installing. repository an it may also need installing.
The single precision FFTW v3 library libfftw3f is required along with The single precision FFTW v3 library libfftw3f is required along with
the libfftw library development package. Normally installing the 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`. ~/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 CMake
----- -----
Although CMake is available via MacPorts I prefer to use the binary 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. 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 WSJT-X
------ ------
First fetch the source from the repository: First fetch the source from the repository:
@ -411,4 +377,4 @@ $ cmake --build . --target install
73 73
Bill Bill
G4WJS. G4WJS.

View File

@ -42,11 +42,12 @@ public:
{ {
} }
void load (QString const& url, bool forced_fetch) void load (QString const& url, bool fetch, bool forced_fetch)
{ {
auto csv_file_name = csv_file_.fileName ();
abort (); // abort any active download abort (); // abort any active download
if (!QFileInfo::exists (csv_file_name) || forced_fetch) auto csv_file_name = csv_file_.fileName ();
auto exists = QFileInfo::exists (csv_file_name);
if (fetch && (!exists || forced_fetch))
{ {
current_url_.setUrl (url); current_url_.setUrl (url);
if (current_url_.isValid () && !QSslSocket::supportsSsl ()) if (current_url_.isValid () && !QSslSocket::supportsSsl ())
@ -58,8 +59,11 @@ public:
} }
else else
{ {
// load the database asynchronously if (exists)
future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name); {
// load the database asynchronously
future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name);
}
} }
} }
@ -254,9 +258,9 @@ void LotWUsers::set_local_file_path (QString const& path)
m_->csv_file_.setFileName (path); m_->csv_file_.setFileName (path);
} }
void LotWUsers::load (QString const& url, bool force_download) void LotWUsers::load (QString const& url, bool fetch, bool force_download)
{ {
m_->load (url, force_download); m_->load (url, fetch, force_download);
} }
void LotWUsers::set_age_constraint (qint64 uploaded_since_days) void LotWUsers::set_age_constraint (qint64 uploaded_since_days)

View File

@ -23,7 +23,7 @@ public:
void set_local_file_path (QString const&); void set_local_file_path (QString const&);
Q_SLOT void load (QString const& url, bool force_download = false); Q_SLOT void load (QString const& url, bool fetch = true, bool force_download = false);
Q_SLOT void set_age_constraint (qint64 uploaded_since_days); Q_SLOT void set_age_constraint (qint64 uploaded_since_days);
// returns true if the specified call sign 'call' has uploaded their // returns true if the specified call sign 'call' has uploaded their

View File

@ -3,6 +3,7 @@
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <limits>
#include <QUdpSocket> #include <QUdpSocket>
#include <QHostInfo> #include <QHostInfo>
@ -35,6 +36,7 @@ public:
impl (QString const& id, QString const& version, QString const& revision, impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self) port_type server_port, MessageClient * self)
: self_ {self} : self_ {self}
, enabled_ {false}
, id_ {id} , id_ {id}
, version_ {version} , version_ {version}
, revision_ {revision} , revision_ {revision}
@ -79,6 +81,7 @@ public:
Q_SLOT void host_info_results (QHostInfo); Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_; MessageClient * self_;
bool enabled_;
QString id_; QString id_;
QString version_; QString version_;
QString revision_; QString revision_;
@ -152,7 +155,7 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
// message format is described in NetworkMessage.hpp // message format is described in NetworkMessage.hpp
// //
NetworkMessage::Reader in {msg}; NetworkMessage::Reader in {msg};
if (OK == check_status (in) && id_ == in.id ()) // OK and for us if (OK == check_status (in))
{ {
if (schema_ < in.schema ()) // one time record of server's if (schema_ < in.schema ()) // one time record of server's
// negotiated schema // negotiated schema
@ -160,6 +163,12 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
schema_ = in.schema (); schema_ = in.schema ();
} }
if (!enabled_)
{
TRACE_UDP ("message processing disabled for id:" << in.id ());
return;
}
// //
// message format is described in NetworkMessage.hpp // message format is described in NetworkMessage.hpp
// //
@ -200,6 +209,15 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
} }
break; break;
case NetworkMessage::Close:
TRACE_UDP ("Close");
if (check_status (in) != Fail)
{
last_message_.clear ();
Q_EMIT self_->close ();
}
break;
case NetworkMessage::Replay: case NetworkMessage::Replay:
TRACE_UDP ("Replay"); TRACE_UDP ("Replay");
if (check_status (in) != Fail) if (check_status (in) != Fail)
@ -261,6 +279,42 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
} }
break; 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: default:
// Ignore // 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 void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode , QString const& report, QString const& tx_mode
, bool tx_enabled, bool transmitting, bool decoding , 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 , QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode , 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 ()) 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 () out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
<< tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 () << tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
<< de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 () << de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
<< fast_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); 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); m_->send_message (out, message);
} }
} }
@ -527,7 +588,7 @@ void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_}; 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; out << ADIF;
TRACE_UDP ("ADIF:" << ADIF); TRACE_UDP ("ADIF:" << ADIF);
m_->send_message (out, message); m_->send_message (out, message);

View File

@ -47,12 +47,16 @@ public:
// change the server port messages are sent to // change the server port messages are sent to
Q_SLOT void set_server_port (port_type server_port = 0u); Q_SLOT void set_server_port (port_type server_port = 0u);
// enable incoming messages
Q_SLOT void enable (bool);
// outgoing messages // outgoing messages
Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report 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 , 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 , 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 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 , QString const& mode, QString const& message, bool low_confidence
, bool off_air); , bool off_air);
@ -89,6 +93,10 @@ public:
Q_SIGNAL void reply (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode 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); , 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 // this signal is emitted if the server has requested a replay of
// all decodes // all decodes
Q_SIGNAL void replay (); Q_SIGNAL void replay ();
@ -105,6 +113,16 @@ public:
// callsign request for the specified call // callsign request for the specified call
Q_SIGNAL void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only); 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 // this signal is emitted when network errors occur or if a host
// lookup fails // lookup fails
Q_SIGNAL void error (QString const&) const; Q_SIGNAL void error (QString const&) const;

View File

@ -1,6 +1,7 @@
#include "MessageServer.hpp" #include "MessageServer.hpp"
#include <stdexcept> #include <stdexcept>
#include <limits>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QUdpSocket> #include <QUdpSocket>
@ -16,6 +17,11 @@
#include "moc_MessageServer.cpp" #include "moc_MessageServer.cpp"
namespace
{
auto quint32_max = std::numeric_limits<quint32>::max ();
}
class MessageServer::impl class MessageServer::impl
: public QUdpSocket : public QUdpSocket
{ {
@ -238,8 +244,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
bool tx_enabled {false}; bool tx_enabled {false};
bool transmitting {false}; bool transmitting {false};
bool decoding {false}; bool decoding {false};
qint32 rx_df {-1}; quint32 rx_df {quint32_max};
qint32 tx_df {-1}; quint32 tx_df {quint32_max};
QByteArray de_call; QByteArray de_call;
QByteArray de_grid; QByteArray de_grid;
QByteArray dx_grid; QByteArray dx_grid;
@ -247,9 +253,12 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
QByteArray sub_mode; QByteArray sub_mode;
bool fast_mode {false}; bool fast_mode {false};
quint8 special_op_mode {0}; 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 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 >> 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) if (check_status (in) != Fail)
{ {
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call) 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 (de_call), QString::fromUtf8 (de_grid)
, QString::fromUtf8 (dx_grid), watchdog_timeout , QString::fromUtf8 (dx_grid), watchdog_timeout
, QString::fromUtf8 (sub_mode), fast_mode , QString::fromUtf8 (sub_mode), fast_mode
, special_op_mode); , special_op_mode, frequency_tolerance, tr_period
, QString::fromUtf8 (configuration_name));
} }
} }
break; 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) void MessageServer::halt_tx (QString const& id, bool auto_only)
{ {
auto iter = m_->clients_.find (id); 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_); 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 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); , 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 // ask the client with identification 'id' to replay all decodes
Q_SLOT void replay (QString const& id); Q_SLOT void replay (QString const& id);
@ -72,15 +75,25 @@ public:
, QColor const& bg = QColor {}, QColor const& fg = QColor {} , QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false); , 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 // the following signals are emitted when a client broadcasts the
// matching message // matching message
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); 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 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 , 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 , QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode , 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 client_closed (QString const& id);
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time 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 , quint32 delta_frequency, QString const& mode, QString const& message

View File

@ -14,16 +14,35 @@
#include "WFPalette.hpp" #include "WFPalette.hpp"
#include "models/IARURegions.hpp" #include "models/IARURegions.hpp"
#include "models/DecodeHighlightingModel.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; class ItemEditorFactory final
return our_item_editor_factory; : 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 () 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 // types in Radio.hpp are registered in their own translation unit
// as they are needed in the wsjtx_udp shared library too // 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 // used as signal/slot connection arguments since the new Qt 5.5
// Q_ENUM macro only seems to register the unqualified name // Q_ENUM macro only seems to register the unqualified name
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::Frequency> (), new QStandardItemEditorCreator<FrequencyLineEdit> ()); item_editor_factory->registerEditor (qMetaTypeId<QDateTime> (), new QStandardItemEditorCreator<DateTimeEdit> ());
//auto frequency_delta_type_id = qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::FrequencyDelta> (), new QStandardItemEditorCreator<FrequencyDeltaLineEdit> ());
// Frequency list model // Frequency list model
qRegisterMetaTypeStreamOperators<FrequencyList_v2::Item> ("Item_v2"); qRegisterMetaTypeStreamOperators<FrequencyList_v2::Item> ("Item_v2");

View File

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

View File

@ -25,15 +25,15 @@ double constexpr Modulator::m_twoPi;
// unsigned m_nspd=1.2*48000.0/wpm; // unsigned m_nspd=1.2*48000.0/wpm;
// m_nspd=3072; //18.75 WPM // m_nspd=3072; //18.75 WPM
Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
QObject * parent) QObject * parent)
: AudioDevice {parent} : AudioDevice {parent}
, m_quickClose {false} , m_quickClose {false}
, m_phi {0.0} , m_phi {0.0}
, m_toneSpacing {0.0} , m_toneSpacing {0.0}
, m_fSpread {0.0} , m_fSpread {0.0}
, m_frameRate {frameRate}
, m_period {periodLengthInSeconds} , m_period {periodLengthInSeconds}
, m_frameRate {frameRate}
, m_state {Idle} , m_state {Idle}
, m_tuning {false} , m_tuning {false}
, m_cwLevel {false} , m_cwLevel {false}
@ -45,19 +45,15 @@ Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
void Modulator::start (unsigned symbolsLength, double framesPerSymbol, void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
double frequency, double toneSpacing, double frequency, double toneSpacing,
SoundOutput * stream, Channel channel, SoundOutput * stream, Channel channel,
bool synchronize, bool fastMode, double dBSNR, int TRperiod) bool synchronize, bool fastMode, double dBSNR, double TRperiod)
{ {
Q_ASSERT (stream); Q_ASSERT (stream);
// Time according to this computer which becomes our base time // Time according to this computer which becomes our base time
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000; qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
if (m_state != Idle) if(m_state != Idle) stop();
{
stop ();
}
m_quickClose = false; m_quickClose = false;
m_symbolsLength = symbolsLength; m_symbolsLength = symbolsLength;
m_isym0 = std::numeric_limits<unsigned>::max (); // big number m_isym0 = std::numeric_limits<unsigned>::max (); // big number
m_frequency0 = 0.; m_frequency0 = 0.;
@ -69,30 +65,34 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
m_toneSpacing = toneSpacing; m_toneSpacing = toneSpacing;
m_bFastMode=fastMode; m_bFastMode=fastMode;
m_TRperiod=TRperiod; 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) { if (m_addNoise) {
m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0)); m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0));
m_fac = 3000.0; m_fac = 3000.0;
if (m_snr > 1.0) m_fac = 3000.0 / m_snr; 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; m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000;
if(m_bFastMode) m_ic=0; if(m_bFastMode) m_ic=0;
m_silentFrames = 0; m_silentFrames = 0;
// calculate number of silent frames to send, so that audio will start at // calculate number of silent frames to send, so that audio will start at
// the nominal time "delay_ms" into the Tx sequence. // the nominal time "delay_ms" into the Tx sequence.
if (synchronize && !m_tuning && !m_bFastMode) { if (synchronize && !m_tuning && !m_bFastMode) {
m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000)); m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000));
} }
// 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); initialize (QIODevice::ReadOnly, channel);
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ? Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
Synchronizing : Active)); Synchronizing : Active));
@ -149,6 +149,8 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16))); qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
qint64 framesGenerated (0); qint64 framesGenerated (0);
// if(m_ic==0) qDebug() << "Modulator::readData" << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
switch (m_state) switch (m_state)
{ {
case Synchronizing: case Synchronizing:
@ -170,17 +172,19 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
case Active: case Active:
{ {
unsigned int isym=0; unsigned int isym=0;
// qDebug() << "Mod A" << m_toneSpacing << m_ic;
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000 if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode); 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; bool fastCwId=false;
static bool bCwId=false; static bool bCwId=false;
qint64 ms = QDateTime::currentMSecsSinceEpoch(); qint64 ms = QDateTime::currentMSecsSinceEpoch();
float tsec=0.001*(ms % (1000*m_TRperiod)); 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 and (icw[0]>0) and (tsec > (m_TRperiod-5.0))) fastCwId=true;
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
// qDebug() << "Mod A" << m_ic << isym << tsec;
if(slowCwId or fastCwId) { // Transmit CW ID? if(slowCwId or fastCwId) { // Transmit CW ID?
m_dphi = m_twoPi*m_frequency/m_frameRate; m_dphi = m_twoPi*m_frequency/m_frameRate;
if(m_bFastMode and !bCwId) { if(m_bFastMode and !bCwId) {
@ -247,15 +251,15 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
i1= m_symbolsLength * 4.0 * m_nsps; i1= m_symbolsLength * 4.0 * m_nsps;
} }
if(m_bFastMode and !m_tuning) { if(m_bFastMode and !m_tuning) {
i1=m_TRperiod*48000 - 24000; i1=m_TRperiod*48000.0 - 24000.0;
i0=i1-816; i0=i1-816;
} }
qint16 sample; qint16 sample;
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) { for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
isym=0; isym=0;
if(!m_tuning and m_TRperiod!=3) isym=m_ic / (4.0 * m_nsps); //Actual if(!m_tuning and m_TRperiod!=3.0) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
//fsample=48000
if(m_bFastMode) isym=isym%m_symbolsLength; if(m_bFastMode) isym=isym%m_symbolsLength;
if (isym != m_isym0 || m_frequency != m_frequency0) { if (isym != m_isym0 || m_frequency != m_frequency0) {
if(itone[0]>=100) { if(itone[0]>=100) {
@ -267,8 +271,6 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
m_toneFrequency0=m_frequency + itone[isym]*m_toneSpacing; m_toneFrequency0=m_frequency + itone[isym]*m_toneSpacing;
} }
} }
// qDebug() << "Mod B" << m_bFastMode << m_ic << numFrames << isym << itone[isym]
// << m_toneFrequency0 << m_nsps;
m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate; m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate;
m_isym0 = isym; m_isym0 = isym;
m_frequency0 = m_frequency; //??? m_frequency0 = m_frequency; //???
@ -289,9 +291,12 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
if (m_ic > i1) m_amp = 0.0; if (m_ic > i1) m_amp = 0.0;
sample=qRound(m_amp*qSin(m_phi)); sample=qRound(m_amp*qSin(m_phi));
if(m_toneSpacing < 0) sample=qRound(m_amp*foxcom_.wave[m_ic]);
// if(m_ic < 100) qDebug() << "Mod C" << m_ic << m_amp << foxcom_.wave[m_ic] << sample; //Here's where we transmit from a precomputed wave[] array:
if(!m_tuning and (m_toneSpacing < 0)) {
m_amp=32767.0;
sample=qRound(m_amp*foxcom_.wave[m_ic]);
}
samples = load(postProcessSample(sample), samples); samples = load(postProcessSample(sample), samples);
++framesGenerated; ++framesGenerated;
@ -308,7 +313,15 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
} }
m_frequency0 = m_frequency; m_frequency0 = m_frequency;
// done for this chunk - continue on next call // done for this chunk - continue on next call
// qDebug() << "Mod B" << m_ic << i1 << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
while (samples != end) // pad block with silence
{
samples = load (0, samples);
++framesGenerated;
}
return framesGenerated * bytesPerFrame (); return framesGenerated * bytesPerFrame ();
} }
// fall through // fall through

View File

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

View File

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

126
NEWS
View File

@ -13,9 +13,31 @@
Copyright 2001 - 2019 by Joe Taylor, K1JT. Copyright 2001 - 2019 by Joe Taylor, K1JT.
Release: WSJT-X 2.0.1 Release: WSJT-X 2.1
February 25, 2019 July 15, 2019
--------------------- -------------------
WSJT-X 2.1 is a major update that introduces FT4, a new protocol
targeted at HF contesting. Other improvements have been made in the
following areas:
- FT8 waveform generated with GMSK, fully backward compatible
- user options for waterfall and spectrum display
- contest logging
- rig control
- user interface
- UDP messaging for inter-program communication
- accessibility
There are numerous minor enhancements and bug fixes.
We now provide a separate installation package for 64-bit Windows 7
and later, with significant improvements in decoding speed.
Release: WSJT-X 2.0.1
February 25, 2019
---------------------
WSJT-X 2.0.1 is a bug fix release including the following defect WSJT-X 2.0.1 is a bug fix release including the following defect
repairs reported since the v2.0.0 GA release. repairs reported since the v2.0.0 GA release.
@ -67,9 +89,9 @@ repairs reported since the v2.0.0 GA release.
than one Hound in the same Tx sequence. than one Hound in the same Tx sequence.
Release: WSJT-X 2.0 Release: WSJT-X 2.0
December 10, 2018 December 10, 2018
----------------------- -----------------------
WSJT-X 2.0 is a major update that introduces new protocols for FT8 and WSJT-X 2.0 is a major update that introduces new protocols for FT8 and
MSK144. The new protocols become the world-wide standards on December MSK144. The new protocols become the world-wide standards on December
@ -125,9 +147,9 @@ Some details of changes since WSJT-X-rc5 include the following:
- Update the WSJT-X User Guide to v2.0 (more to come...) - Update the WSJT-X User Guide to v2.0 (more to come...)
- Update cty.dat - Update cty.dat
Release: WSJT-X 2.0-rc5 Release: WSJT-X 2.0-rc5
November 26, 2018 November 26, 2018
----------------------- -----------------------
Release Candidate 5 ("RC5") is stable, works well, and fixes the known Release Candidate 5 ("RC5") is stable, works well, and fixes the known
problems in RC4. It is likely that the General Availability (GA) problems in RC4. It is likely that the General Availability (GA)
@ -158,9 +180,9 @@ not be realized until most signals on a crowded band are using the
new FT8 protocol. new FT8 protocol.
Release: WSJT-X 2.0-rc4 Release: WSJT-X 2.0-rc4
November 12, 2018 November 12, 2018
----------------------- -----------------------
Changes from WSJT-X Version 2.0.0-rc3 include the following: Changes from WSJT-X Version 2.0.0-rc3 include the following:
@ -184,9 +206,9 @@ Changes from WSJT-X Version 2.0.0-rc3 include the following:
- New facilities for Contest and Fox-mode logging - New facilities for Contest and Fox-mode logging
Release: WSJT-X 2.0-rc3 Release: WSJT-X 2.0-rc3
October 15, 2018 October 15, 2018
----------------------- -----------------------
Changes from WSJT-X Version 2.0.0-rc2 include the following: Changes from WSJT-X Version 2.0.0-rc2 include the following:
@ -210,9 +232,9 @@ Changes from WSJT-X Version 2.0.0-rc2 include the following:
- Auto update of LoTW info, and faster program startup - Auto update of LoTW info, and faster program startup
Release: WSJT-X 2.0-rc2 Release: WSJT-X 2.0-rc2
September 25, 2018 September 25, 2018
----------------------- -----------------------
Changes from WSJT-X Version 2.0.0-rc1 include the following: Changes from WSJT-X Version 2.0.0-rc1 include the following:
- Corrected a flaw that encoded a message's first callsign as - Corrected a flaw that encoded a message's first callsign as
@ -238,9 +260,9 @@ Changes from WSJT-X Version 2.0.0-rc1 include the following:
- Suppressed the display of certain illogical false decodes. - Suppressed the display of certain illogical false decodes.
Release: WSJT-X 2.0-rc1 Release: WSJT-X 2.0-rc1
September 17, 2018 September 17, 2018
----------------------- -----------------------
This is the first candidate release on WSJT-X 2.0, intended for This is the first candidate release on WSJT-X 2.0, intended for
beta-level testing. For details see: beta-level testing. For details see:
@ -250,18 +272,18 @@ http://physics.princeton.edu/pulsar/k1jt/Quick_Start_WSJT-X_2.0.pdf
http://physics.princeton.edu/pulsar/k1jt/77bit.txt http://physics.princeton.edu/pulsar/k1jt/77bit.txt
Release: WSJT-X Version 1.9.1 Release: WSJT-X Version 1.9.1
June 1, 2018 June 1, 2018
----------------------------- -----------------------------
This critical bug fix release repairs an unintended restriction in the FT8 This critical bug fix release repairs an unintended restriction in the FT8
DXpedition mode. It supersedes v1.9.0 and must be used for DXpedition Fox DXpedition mode. It supersedes v1.9.0 and must be used for DXpedition Fox
operators. operators.
Release: WSJT-X Version 1.9.0 Release: WSJT-X Version 1.9.0
May 28, 2018 May 28, 2018
----------------------------- -----------------------------
Changes from WSJT-X Version 1.9.0-rc4 include the following: Changes from WSJT-X Version 1.9.0-rc4 include the following:
- Display in the right text window of MSK144 messages addressed to - Display in the right text window of MSK144 messages addressed to
@ -289,9 +311,9 @@ Changes from WSJT-X Version 1.9.0-rc4 include the following:
- Hamlib, improved support for flrig. - Hamlib, improved support for flrig.
Release: WSJT-X Version 1.9.0-rc4 Release: WSJT-X Version 1.9.0-rc4
April 30, 2018 April 30, 2018
--------------------------------- ---------------------------------
Changes from WSJT-X Version 1.9.0-rc3 include the following: Changes from WSJT-X Version 1.9.0-rc3 include the following:
@ -321,9 +343,9 @@ Changes from WSJT-X Version 1.9.0-rc3 include the following:
- Updated copy of cty.dat - Updated copy of cty.dat
Release: WSJT-X Version 1.9.0-rc3 Release: WSJT-X Version 1.9.0-rc3
March 18, 2018 March 18, 2018
--------------------------------- ---------------------------------
Changes from WSJT-X Version 1.9.0-rc2 include the following: Changes from WSJT-X Version 1.9.0-rc2 include the following:
@ -357,9 +379,9 @@ Changes from WSJT-X Version 1.9.0-rc2 include the following:
Release: WSJT-X Version 1.9.0-rc2 Release: WSJT-X Version 1.9.0-rc2
February 26, 2018 February 26, 2018
--------------------------------- ---------------------------------
Changes from WSJT-X Version 1.8.0 include the following: Changes from WSJT-X Version 1.8.0 include the following:
@ -401,9 +423,9 @@ wsjt-devel@lists.sourceforge.net. You must be a subscriber in order
to post there. to post there.
Release: WSJT-X Version 1.8.0 Release: WSJT-X Version 1.8.0
October 27, 2017 October 27, 2017
----------------------------- -----------------------------
This is the full General Availability release of WSJT-X Version 1.8.0. This is the full General Availability release of WSJT-X Version 1.8.0.
@ -424,9 +446,9 @@ reset of the default list of suggested operating frequencies. Go to
*Reset*. *Reset*.
Release: WSJT-X Version 1.8.0-rc3 Release: WSJT-X Version 1.8.0-rc3
October 16, 2017 October 16, 2017
--------------------------------- ---------------------------------
Most (but not all) changes since Version 1.8.0-rc2 involve user Most (but not all) changes since Version 1.8.0-rc2 involve user
control of the increasingly popular FT8 mode. The "RC3" release also control of the increasingly popular FT8 mode. The "RC3" release also
@ -528,9 +550,9 @@ message from populating the Tx message boxes.
Release: WSJT-X Version 1.8.0-rc2 Release: WSJT-X Version 1.8.0-rc2
September 2, 2017 September 2, 2017
--------------------------------- ---------------------------------
Implementation of FT8 and its auto-sequencing feature is now more Implementation of FT8 and its auto-sequencing feature is now more
capable and more polished. The decoder is faster and better: it now capable and more polished. The decoder is faster and better: it now
@ -552,8 +574,8 @@ frequencies. Go to *File->Settings->Frequencies*, right click on
the table and select *Reset*. the table and select *Reset*.
Release: WSJT-X Version 1.8.0 Release: WSJT-X Version 1.8.0
----------------------------- -----------------------------
NEW FEATURES IN WSJT-X Version 1.8.0 NEW FEATURES IN WSJT-X Version 1.8.0
------------------------------------ ------------------------------------
@ -606,8 +628,8 @@ prescription of steps to reproduce the undesired behavior. You must
be a subscriber to post to either of these lists. be a subscriber to post to either of these lists.
Brief Description of the FT8 Protocol Brief Description of the FT8 Protocol
------------------------------------- -------------------------------------
WSJT-X Version 1.8.0 includes a new mode called FT8, developed by K9AN WSJT-X Version 1.8.0 includes a new mode called FT8, developed by K9AN
and K1JT. The mode name "FT8" stands for "Franke and Taylor, 8-FSK and K1JT. The mode name "FT8" stands for "Franke and Taylor, 8-FSK
@ -641,8 +663,8 @@ activated in v1.8.0.
We haven't yet finalized what the three extra bits in the message We haven't yet finalized what the three extra bits in the message
payload will be used for. Suggestions are welcome! payload will be used for. Suggestions are welcome!
-- Joe, K1JT, for the WSJT Development Team -- Joe, K1JT, for the WSJT Development Team
WSJT-X v1.6.0 Release Notice WSJT-X v1.6.0 Release Notice
============================ ============================
@ -923,7 +945,7 @@ defect is resolved in the next release (v1.5).
WSJT-X ChangeLog WSJT-X ChangeLog
------------------------------------------------------------------ ------------------------------------------------------------------
October 7, 2013: Version 1.2.1, r3590 October 7, 2013: Version 1.2.1, r3590

View File

@ -116,15 +116,18 @@
* Tx Enabled bool * Tx Enabled bool
* Transmitting bool * Transmitting bool
* Decoding bool * Decoding bool
* Rx DF qint32 * Rx DF quint32
* Tx DF qint32 * Tx DF quint32
* DE call utf8 * DE call utf8
* DE grid utf8 * DE grid utf8
* DX grid utf8 * DX grid utf8
* Tx Watchdog bool * Tx Watchdog bool
* Sub-mode utf8 * Sub-mode utf8
* Fast mode bool * 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 * WSJT-X sends this status message when various internal state
* changes to allow the server to track the relevant state of each * changes to allow the server to track the relevant state of each
@ -133,19 +136,22 @@
* *
* Application start up, * Application start up,
* "Enable Tx" button status changes, * "Enable Tx" button status changes,
* Dial frequency changes, * dial frequency changes,
* Changes to the "DX Call" field, * changes to the "DX Call" field,
* Operating mode, sub-mode or fast mode changes, * operating mode, sub-mode or fast mode changes,
* Transmit mode changed (in dual JT9+JT65 mode), * transmit mode changed (in dual JT9+JT65 mode),
* Changes to the "Rpt" spinner, * changes to the "Rpt" spinner,
* After an old decodes replay sequence (see Replay below), * after an old decodes replay sequence (see Replay below),
* When switching between Tx and Rx mode, * when switching between Tx and Rx mode,
* At the start and end of decoding, * at the start and end of decoding,
* When the Rx DF changes, * when the Rx DF changes,
* When the Tx DF changes, * when the Tx DF changes,
* When settings are exited, * when settings are exited,
* When the DX call or grid changes, * when the DX call or grid changes,
* When the Tx watchdog is set or reset. * 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 * The Special operation mode is an enumeration that indicates the
* setting selected in the WSJT-X "Settings->Advanced->Special * setting selected in the WSJT-X "Settings->Advanced->Special
@ -159,6 +165,10 @@
* 5 -> FOX * 5 -> FOX
* 6 -> HOUND * 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 * Decode Out 2 quint32
* Id (unique key) utf8 * Id (unique key) utf8
@ -271,11 +281,12 @@
* button. * button.
* *
* *
* Close Out 6 quint32 * Close Out/In 6 quint32
* Id (unique key) utf8 * Id (unique key) utf8
* *
* Close is sent by a client immediately prior to it shutting * Close is sent by a client immediately prior to it shutting
* down gracefully. * down gracefully. When sent by a server it requests the target
* client to close down gracefully.
* *
* *
* Replay In 7 quint32 * Replay In 7 quint32
@ -414,6 +425,35 @@
* the last instance only instead of all instances of the * the last instance only instead of all instances of the
* specified call be highlighted or have it's highlighting * specified call be highlighted or have it's highlighting
* cleared. * 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> #include <QDataStream>
@ -443,6 +483,8 @@ namespace NetworkMessage
Location, Location,
LoggedADIF, LoggedADIF,
HighlightCallsign, HighlightCallsign,
SwitchConfiguration,
Configure,
maximum_message_type_ // ONLY add new message types maximum_message_type_ // ONLY add new message types
// immediately before here // immediately before here
}; };

View File

@ -4,6 +4,7 @@
#include <QDebug> #include <QDebug>
#include <objbase.h> #include <objbase.h>
#include <QThread> #include <QThread>
#include <QEventLoop>
#include "qt_helpers.hpP" #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 () int OmniRigTransceiver::do_start ()
{ {
TRACE_CAT ("OmniRigTransceiver", "starting"); TRACE_CAT ("OmniRigTransceiver", "starting");
@ -182,101 +192,109 @@ int OmniRigTransceiver::do_start ()
.arg (readable_params_, 8, 16, QChar ('0')) .arg (readable_params_, 8, 16, QChar ('0'))
.arg (writable_params_, 8, 16, QChar ('0')) .arg (writable_params_, 8, 16, QChar ('0'))
.arg (rig_number_).toLocal8Bit ()); .arg (rig_number_).toLocal8Bit ());
for (int i = 0; i < 5; ++i)
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)
{ {
QThread::msleep (100); // wait until OmniRig polls the rig if (OmniRig::ST_ONLINE == rig_->Status ())
auto f = rig_->GetRxFrequency ();
int resolution {0};
if (f)
{ {
if (OmniRig::PM_UNKNOWN == rig_->Vfo () break;
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB)) }
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB)) await_notification_with_timeout (1000);
{ }
// start with VFO A (probably MAIN) on rigs that we if (OmniRig::ST_ONLINE != rig_->Status ())
// can't query VFO but can set explicitly {
rig_->SetVfo (OmniRig::PM_VFOA); throw_qstring ("OmniRig: " + rig_->StatusStr ());
} }
if (f % 10) return resolution; // 1Hz resolution auto f = rig_->GetRxFrequency ();
auto test_frequency = f - f % 100 + 55; for (int i = 0; (f == 0) && (i < 5); ++i)
if (OmniRig::PM_FREQ & writable_params_) {
{ await_notification_with_timeout (1000);
rig_->SetFreq (test_frequency); f = rig_->GetRxFrequency ();
} }
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_)) update_rx_frequency (f);
{ int resolution {0};
rig_->SetFreqB (test_frequency); if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
} && (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_)) == (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
{ {
rig_->SetFreqA (test_frequency); // start with VFO A (probably MAIN) on rigs that we
} // can't query VFO but can set explicitly
else rig_->SetVfo (OmniRig::PM_VFOA);
{ }
throw_qstring (tr ("OmniRig: don't know how to set rig frequency")); f = state ().frequency ();
} if (f % 10) return resolution; // 1Hz resolution
switch (rig_->GetRxFrequency () - test_frequency) auto test_frequency = f - f % 100 + 55;
{ if (OmniRig::PM_FREQ & writable_params_)
case -5: resolution = -1; break; // 10Hz truncated {
case 5: resolution = 1; break; // 10Hz rounded rig_->SetFreq (test_frequency);
case -15: resolution = -2; break; // 20Hz truncated }
case -55: resolution = -2; break; // 100Hz truncated else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
case 45: resolution = 2; break; // 100Hz rounded {
} rig_->SetFreqB (test_frequency);
if (1 == resolution) // may be 20Hz rounded }
{ else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
test_frequency = f - f % 100 + 51; {
if (OmniRig::PM_FREQ & writable_params_) rig_->SetFreqA (test_frequency);
{ }
rig_->SetFreq (test_frequency); else
} {
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_)) throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
{ }
rig_->SetFreqB (test_frequency); if (!await_notification_with_timeout (1000))
} {
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_)) TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
{ throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
rig_->SetFreqA (test_frequency); }
} switch (rig_->GetRxFrequency () - test_frequency)
if (9 == rig_->GetRxFrequency () - test_frequency) {
{ case -5: resolution = -1; break; // 10Hz truncated
resolution = 2; // 20Hz rounded case 5: resolution = 1; break; // 10Hz rounded
} case -15: resolution = -2; break; // 20Hz truncated
} case -55: resolution = -2; break; // 100Hz truncated
if (OmniRig::PM_FREQ & writable_params_) case 45: resolution = 2; break; // 100Hz rounded
{ }
rig_->SetFreq (f); if (1 == resolution) // may be 20Hz rounded
} {
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_)) test_frequency = f - f % 100 + 51;
{ if (OmniRig::PM_FREQ & writable_params_)
rig_->SetFreqB (f); {
} rig_->SetFreq (test_frequency);
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_)) }
{ else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
rig_->SetFreqA (f); {
} rig_->SetFreqB (test_frequency);
update_rx_frequency (f); }
return resolution; 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")); if (OmniRig::PM_FREQ & writable_params_)
return 0; // keep compiler happy {
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 () void OmniRigTransceiver::do_stop ()
{ {
if (offline_timer_)
{
offline_timer_->stop ();
offline_timer_.reset ();
}
QThread::msleep (200); // leave some time for pending QThread::msleep (200); // leave some time for pending
// commands at the server end // commands at the server end
if (port_) if (port_)
@ -300,17 +318,6 @@ void OmniRigTransceiver::do_stop ()
TRACE_CAT ("OmniRigTransceiver", "stopped"); 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) void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
{ {
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + 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 () void OmniRigTransceiver::handle_visible_change ()
{ {
if (!omni_rig_ || omni_rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ()); TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
} }
void OmniRigTransceiver::handle_rig_type_change (int rig_number) 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_number_ == rig_number)
{ {
if (!rig_ || rig_->isNull ()) return;
readable_params_ = rig_->ReadableParams (); readable_params_ = rig_->ReadableParams ();
writable_params_ = rig_->WriteableParams (); 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 (rig_->RigType ())
.arg (readable_params_, 8, 16, QChar ('0')) .arg (readable_params_, 8, 16, QChar ('0'))
.arg (writable_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) 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_number_ == rig_number)
{ {
if (!rig_ || rig_->isNull ()) return;
auto const& status = rig_->StatusStr ().toLocal8Bit (); 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 (OmniRig::ST_ONLINE != rig_->Status ())
{ {
if (!offline_timer_->isActive ()) offline ("Rig went offline");
{
offline_timer_->start (); // give OmniRig time to recover
}
} }
else else
{ {
offline_timer_->stop (); Q_EMIT notified ();
update_rx_frequency (rig_->GetRxFrequency ());
update_complete ();
TRACE_CAT ("OmniRigTransceiver", "OmniRig frequency:" << state ().frequency ());
} }
// 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) void OmniRigTransceiver::handle_params_change (int rig_number, int params)
{ {
if (rig_number_ == rig_number) if (!omni_rig_ || omni_rig_->isNull ()) return;
{ TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig params change: params = 0x%1 for rig %2"}
.arg (params, 8, 16, QChar ('0')) .arg (params, 8, 16, QChar ('0'))
.arg (rig_number).toLocal8Bit () .arg (rig_number).toLocal8Bit ()
<< "state before:" << state ()); << "state before:" << state ());
if (rig_number_ == rig_number)
{
if (!rig_ || rig_->isNull ()) return;
// starting_ = false; // starting_ = false;
TransceiverState old_state {state ()}; TransceiverState old_state {state ()};
auto need_frequency = false; 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) if (params & OmniRig::PM_VFOAA)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
update_split (false); update_split (false);
reversed_ = false; reversed_ = false;
update_rx_frequency (rig_->FreqA ()); update_rx_frequency (rig_->FreqA ());
@ -387,6 +397,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
} }
if (params & OmniRig::PM_VFOAB) if (params & OmniRig::PM_VFOAB)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
update_split (true); update_split (true);
reversed_ = false; reversed_ = false;
update_rx_frequency (rig_->FreqA ()); update_rx_frequency (rig_->FreqA ());
@ -394,6 +405,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
} }
if (params & OmniRig::PM_VFOBA) if (params & OmniRig::PM_VFOBA)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
update_split (true); update_split (true);
reversed_ = true; reversed_ = true;
update_other_frequency (rig_->FreqA ()); update_other_frequency (rig_->FreqA ());
@ -401,6 +413,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
} }
if (params & OmniRig::PM_VFOBB) if (params & OmniRig::PM_VFOBB)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
update_split (false); update_split (false);
reversed_ = true; reversed_ = true;
update_other_frequency (rig_->FreqA ()); update_other_frequency (rig_->FreqA ());
@ -408,154 +421,195 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
} }
if (params & OmniRig::PM_VFOA) if (params & OmniRig::PM_VFOA)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOA");
reversed_ = false; reversed_ = false;
need_frequency = true; need_frequency = true;
} }
if (params & OmniRig::PM_VFOB) if (params & OmniRig::PM_VFOB)
{ {
TRACE_CAT ("OmniRigTransceiver", "VFOB");
reversed_ = true; reversed_ = true;
need_frequency = true; need_frequency = true;
} }
if (params & OmniRig::PM_FREQ) if (params & OmniRig::PM_FREQ)
{ {
TRACE_CAT ("OmniRigTransceiver", "FREQ");
need_frequency = true; need_frequency = true;
} }
if (params & OmniRig::PM_FREQA) if (params & OmniRig::PM_FREQA)
{ {
auto f = rig_->FreqA ();
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
if (reversed_) if (reversed_)
{ {
update_other_frequency (rig_->FreqA ()); update_other_frequency (f);
} }
else else
{ {
update_rx_frequency (rig_->FreqA ()); update_rx_frequency (f);
} }
} }
if (params & OmniRig::PM_FREQB) if (params & OmniRig::PM_FREQB)
{ {
auto f = rig_->FreqB ();
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
if (reversed_) if (reversed_)
{ {
update_rx_frequency (rig_->FreqB ()); update_rx_frequency (f);
} }
else else
{ {
update_other_frequency (rig_->FreqB ()); update_other_frequency (f);
} }
} }
if (need_frequency) if (need_frequency)
{ {
if (readable_params_ & OmniRig::PM_FREQA) 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 (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) if (params & OmniRig::PM_PITCH)
{ {
TRACE_CAT ("OmniRigTransceiver", "PITCH");
} }
if (params & OmniRig::PM_RITOFFSET) if (params & OmniRig::PM_RITOFFSET)
{ {
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
} }
if (params & OmniRig::PM_RIT0) if (params & OmniRig::PM_RIT0)
{ {
TRACE_CAT ("OmniRigTransceiver", "RIT0");
} }
if (params & OmniRig::PM_VFOEQUAL) if (params & OmniRig::PM_VFOEQUAL)
{ {
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq (); 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_rx_frequency (f);
update_other_frequency (f); update_other_frequency (f);
update_mode (map_mode (rig_->Mode ())); update_mode (m);
} }
if (params & OmniRig::PM_VFOSWAP) 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_other_frequency (state ().frequency ());
update_rx_frequency (temp); update_rx_frequency (f);
update_mode (map_mode (rig_->Mode ())); update_mode (map_mode (rig_->Mode ()));
} }
if (params & OmniRig::PM_SPLITON) if (params & OmniRig::PM_SPLITON)
{ {
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
update_split (true); update_split (true);
} }
if (params & OmniRig::PM_SPLITOFF) if (params & OmniRig::PM_SPLITOFF)
{ {
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
update_split (false); update_split (false);
} }
if (params & OmniRig::PM_RITON) if (params & OmniRig::PM_RITON)
{ {
TRACE_CAT ("OmniRigTransceiver", "RITON");
} }
if (params & OmniRig::PM_RITOFF) if (params & OmniRig::PM_RITOFF)
{ {
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
} }
if (params & OmniRig::PM_XITON) if (params & OmniRig::PM_XITON)
{ {
TRACE_CAT ("OmniRigTransceiver", "XITON");
} }
if (params & OmniRig::PM_XITOFF) if (params & OmniRig::PM_XITOFF)
{ {
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
} }
if (params & OmniRig::PM_RX) if (params & OmniRig::PM_RX)
{ {
TRACE_CAT ("OmniRigTransceiver", "RX");
update_PTT (false); update_PTT (false);
} }
if (params & OmniRig::PM_TX) if (params & OmniRig::PM_TX)
{ {
TRACE_CAT ("OmniRigTransceiver", "TX");
update_PTT (); update_PTT ();
} }
if (params & OmniRig::PM_CW_U) if (params & OmniRig::PM_CW_U)
{ {
TRACE_CAT ("OmniRigTransceiver", "CW-R");
update_mode (CW_R); update_mode (CW_R);
} }
if (params & OmniRig::PM_CW_L) if (params & OmniRig::PM_CW_L)
{ {
TRACE_CAT ("OmniRigTransceiver", "CW");
update_mode (CW); update_mode (CW);
} }
if (params & OmniRig::PM_SSB_U) if (params & OmniRig::PM_SSB_U)
{ {
TRACE_CAT ("OmniRigTransceiver", "USB");
update_mode (USB); update_mode (USB);
} }
if (params & OmniRig::PM_SSB_L) if (params & OmniRig::PM_SSB_L)
{ {
TRACE_CAT ("OmniRigTransceiver", "LSB");
update_mode (LSB); update_mode (LSB);
} }
if (params & OmniRig::PM_DIG_U) if (params & OmniRig::PM_DIG_U)
{ {
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
update_mode (DIG_U); update_mode (DIG_U);
} }
if (params & OmniRig::PM_DIG_L) if (params & OmniRig::PM_DIG_L)
{ {
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
update_mode (DIG_L); update_mode (DIG_L);
} }
if (params & OmniRig::PM_AM) if (params & OmniRig::PM_AM)
{ {
TRACE_CAT ("OmniRigTransceiver", "AM");
update_mode (AM); update_mode (AM);
} }
if (params & OmniRig::PM_FM) if (params & OmniRig::PM_FM)
{ {
TRACE_CAT ("OmniRigTransceiver", "FM");
update_mode (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 ()); 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) 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)command;
(void)reply; (void)reply;
if (!omni_rig_ || omni_rig_->isNull ()) return;
if (rig_number_ == rig_number) if (rig_number_ == rig_number)
{ {
if (!rig_ || rig_->isNull ()) return;
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit () TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
<< "with reply" << reply.toString ().toLocal8Bit () << "with reply" << reply.toString ().toLocal8Bit ()
<< QString ("for rig %1").arg (rig_number).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_tx_frequency (Frequency, MODE, bool no_ignore) override;
void do_mode (MODE) override; void do_mode (MODE) override;
void do_ptt (bool on) override; void do_ptt (bool on) override;
void do_sync (bool force_signal, bool no_poll) override;
private: 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_COM_exception (int, QString, QString, QString);
Q_SLOT void handle_visible_change (); Q_SLOT void handle_visible_change ();
Q_SLOT void handle_rig_type_change (int rig_number); Q_SLOT void handle_rig_type_change (int rig_number);
@ -62,7 +63,7 @@ private:
QString rig_type_; QString rig_type_;
int readable_params_; int readable_params_;
int writable_params_; int writable_params_;
QScopedPointer<QTimer> offline_timer_; // QScopedPointer<QTimer> offline_timer_;
bool send_update_signal_; bool send_update_signal_;
bool reversed_; // some rigs can reverse VFOs bool reversed_; // some rigs can reverse VFOs
}; };

View File

@ -129,52 +129,46 @@ bool PollingTransceiver::do_pre_update ()
return true; 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 () void PollingTransceiver::handle_timeout ()
{ {
QString message; QString message;
bool force_signal {false};
// we must catch all exceptions here since we are called by Qt and // we must catch all exceptions here since we are called by Qt and
// inform our parent of the failure via the offline() message // inform our parent of the failure via the offline() message
try 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) catch (std::exception const& e)
{ {

View File

@ -39,11 +39,9 @@ protected:
QObject * parent); QObject * parent);
protected: 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 // Sub-classes implement this and fetch what they can from the rig
// in a non-intrusive manner. // in a non-intrusive manner.
virtual void poll () = 0; virtual void do_poll () = 0;
void do_post_start () override final; void do_post_start () override final;
void do_post_stop () 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) QString pretty_frequency_MHz_string (Frequency f, QLocale const& locale)

View File

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

View File

@ -12,6 +12,178 @@
Copyright 2001 - 2019 by Joe Taylor, K1JT. Copyright 2001 - 2019 by Joe Taylor, K1JT.
Release: WSJT-X 2.1
July 15, 2019
-------------------
WSJT-X 2.1 is a major update that introduces FT4, a new protocol
targeted at HF contesting. Other improvements have been made in the
following areas:
- FT8 waveform generated with GMSK, fully backward compatible
- user options for waterfall and spectrum display
- contest logging
- rig control
- user interface
- UDP messaging for inter-program communication
- accessibility
There are numerous minor enhancements and bug fixes.
We now provide a separate installation package for 64-bit Windows 7
and later, with significant improvements in decoding speed.
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 Release: WSJT-X 2.0.1
February 25, 2019 February 25, 2019
--------------------- ---------------------

View File

@ -3,6 +3,8 @@
#include <stdexcept> #include <stdexcept>
#include <QDebug> #include <QDebug>
#include <QMutex>
#include <QMutexLocker>
#include <QString> #include <QString>
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
@ -28,10 +30,11 @@ private:
QTextStream * original_stream_; QTextStream * original_stream_;
QtMessageHandler original_handler_; QtMessageHandler original_handler_;
static QTextStream * current_stream_; static QTextStream * current_stream_;
static QMutex mutex_;
}; };
QTextStream * TraceFile::impl::current_stream_; QTextStream * TraceFile::impl::current_stream_;
QMutex TraceFile::impl::mutex_;
// delegate to implementation class // delegate to implementation class
TraceFile::TraceFile (QString const& trace_file_path) TraceFile::TraceFile (QString const& trace_file_path)
@ -73,7 +76,10 @@ TraceFile::impl::~impl ()
void TraceFile::impl::message_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg) void TraceFile::impl::message_handler (QtMsgType type, QMessageLogContext const& context, QString const& msg)
{ {
Q_ASSERT_X (current_stream_, "TraceFile:message_handler", "no stream to write to"); Q_ASSERT_X (current_stream_, "TraceFile:message_handler", "no stream to write to");
*current_stream_ << qFormatLogMessage (type, context, msg) << endl; {
QMutexLocker lock {&mutex_}; // thread safety - serialize writes to the trace file
*current_stream_ << qFormatLogMessage (type, context, msg) << endl;
}
if (QtFatalMsg == type) if (QtFatalMsg == type)
{ {

View File

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

View File

@ -112,8 +112,6 @@ protected:
virtual void do_ptt (bool = true) = 0; virtual void do_ptt (bool = true) = 0;
virtual void do_post_ptt (bool = true) {} 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;} virtual bool do_pre_update () {return true;}
// sub classes report rig state changes with these methods // sub classes report rig state changes with these methods

View File

@ -1,8 +1,11 @@
#include "ClientWidget.hpp" #include "ClientWidget.hpp"
#include <limits>
#include <QRegExp> #include <QRegExp>
#include <QColor> #include <QColor>
#include <QtWidgets>
#include <QAction> #include <QAction>
#include <QDebug>
#include "validators/MaidenheadLocatorValidator.hpp" #include "validators/MaidenheadLocatorValidator.hpp"
@ -11,6 +14,9 @@ namespace
//QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"}; //QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"}; QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
QRegularExpression cq_re {"(CQ|CQDX|QRZ)[^A-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) void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
{ {
@ -21,9 +27,10 @@ namespace
} }
} }
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id) ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
: client_id_ {client_id} : QSortFilterProxyModel {parent}
, rx_df_ (-1) , client_id_ {client_id}
, rx_df_ (quint32_max)
{ {
} }
@ -49,7 +56,7 @@ QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int
break; break;
case 4: // DF 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}; 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_) if (df != rx_df_)
{ {
@ -119,26 +126,48 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
, QListWidget const * calls_of_interest, QWidget * parent) , QListWidget const * calls_of_interest, QWidget * parent)
: QDockWidget {make_title (id, version, revision), parent} : QDockWidget {make_title (id, version, revision), parent}
, id_ {id} , id_ {id}
, done_ {false}
, calls_of_interest_ {calls_of_interest} , 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_action_ {new QAction {tr ("&Erase Band Activity"), this}}
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}} , erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}} , erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
, decodes_table_view_ {new QTableView} , decodes_table_view_ {new QTableView {this}}
, beacons_table_view_ {new QTableView} , beacons_table_view_ {new QTableView {this}}
, message_line_edit_ {new QLineEdit} , message_line_edit_ {new QLineEdit {this}}
, grid_line_edit_ {new QLineEdit} , 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} , 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} , columns_resized_ {false}
{ {
// set up widgets // 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_rx_frequency_action_);
decodes_table_view_->insertAction (nullptr, erase_both_action_); decodes_table_view_->insertAction (nullptr, erase_both_action_);
auto form_layout = new QFormLayout; message_line_edit_->setValidator (&message_validator);
form_layout->addRow (tr ("Free text:"), message_line_edit_); grid_line_edit_->setValidator (&locator_validator);
form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_); dx_grid_line_edit_->setValidator (&locator_validator);
message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this}); tr_period_spin_box_->setRange (5, 30);
grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this}); 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) { connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
Q_EMIT do_free_text (id_, text, false); 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] () { connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT location (id_, grid_line_edit_->text ()); 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; decodes_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
auto decodes_layout = new QVBoxLayout {decodes_page}; decodes_layout_->addWidget (decodes_table_view_);
decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2}); decodes_layout_->addLayout (form_layout_);
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_proxy_model->setSourceModel (beacons_model); beacons_table_view_->setModel (&beacons_proxy_model_);
beacons_table_view_->setModel (beacons_proxy_model);
beacons_table_view_->verticalHeader ()->hide (); beacons_table_view_->verticalHeader ()->hide ();
beacons_table_view_->hideColumn (0); beacons_table_view_->hideColumn (0);
beacons_table_view_->horizontalHeader ()->setStretchLastSection (true); beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
beacons_table_view_->setContextMenuPolicy (Qt::ActionsContextMenu); beacons_table_view_->setContextMenuPolicy (Qt::ActionsContextMenu);
beacons_table_view_->insertAction (nullptr, erase_action_); beacons_table_view_->insertAction (nullptr, erase_action_);
auto beacons_page = new QWidget; beacons_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
auto beacons_layout = new QVBoxLayout {beacons_page}; 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 (decodes_page_);
decodes_stack_->addWidget (beacons_page); decodes_stack_->addWidget (beacons_page_);
// stack alternative views // stack alternative views
auto content_layout = new QVBoxLayout; content_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
content_layout->setContentsMargins (QMargins {2, 2, 2, 2}); content_layout_->addLayout (decodes_stack_);
content_layout->addLayout (decodes_stack_);
// set up controls // set up controls
auto control_button_box = new QDialogButtonBox; auto_off_button_ = control_button_box_->addButton (tr ("&Auto Off"), QDialogButtonBox::ActionRole);
control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole); halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
control_button_box->addButton (halt_tx_button_, 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 */) { connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, true); Q_EMIT do_halt_tx (id_, true);
}); });
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, false); Q_EMIT do_halt_tx (id_, false);
}); });
content_layout->addWidget (control_button_box); content_layout_->addWidget (control_button_box_);
// set up status area // set up status area
auto status_bar = new QStatusBar; status_bar_->addPermanentWidget (de_label_);
status_bar->addPermanentWidget (de_label_); status_bar_->addPermanentWidget (tx_mode_label_);
status_bar->addPermanentWidget (mode_label_); status_bar_->addPermanentWidget (frequency_label_);
status_bar->addPermanentWidget (frequency_label_); status_bar_->addPermanentWidget (tx_df_label_);
status_bar->addPermanentWidget (dx_label_); status_bar_->addPermanentWidget (report_label_);
status_bar->addPermanentWidget (rx_df_label_); content_layout_->addWidget (status_bar_);
status_bar->addPermanentWidget (tx_df_label_); connect (this, &ClientWidget::topLevelChanged, status_bar_, &QStatusBar::setSizeGripEnabled);
status_bar->addPermanentWidget (report_label_);
content_layout->addWidget (status_bar);
connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
// set up central widget // set up central widget
auto content_widget = new QFrame; content_widget_->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken); setWidget (content_widget_);
content_widget->setLayout (content_layout);
setWidget (content_widget);
// setMinimumSize (QSize {550, 0}); // setMinimumSize (QSize {550, 0});
setFeatures (DockWidgetMovable | DockWidgetFloatable);
setAllowedAreas (Qt::BottomDockWidgetArea); setAllowedAreas (Qt::BottomDockWidgetArea);
setFloating (true); 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 () ClientWidget::~ClientWidget ()
{ {
for (int row = 0; row < calls_of_interest_->count (); ++row) 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 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 , 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 , QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode , bool watchdog_timeout, QString const& submode, bool fast_mode
, quint8 special_op_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_.de_call (de_call);
decodes_proxy_model_.rx_df (rx_df); decodes_proxy_model_.rx_df (rx_df);
QString special; 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 (de_grid.size () ? '(' + de_grid + ')' : QString {})
.arg (special) .arg (special)
: QString {}); : QString {});
mode_label_->setText (QString {"Mode: %1%2%3%4"} update_line_edit (mode_line_edit_, mode);
.arg (mode) update_spin_box (frequency_tolerance_spin_box_, frequency_tolerance
.arg (sub_mode) , quint32_max == frequency_tolerance ? QString {"n/a"} : QString {});
.arg (fast_mode && !mode.contains (QRegularExpression {R"(ISCAT|MSK144)"}) ? "fast" : "") 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 + ')')); .arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f)); frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
dx_label_->setText (dx_call.size () >= 0 ? QString {"DX: %1%2"}.arg (dx_call) update_line_edit (dx_call_line_edit_, dx_call);
.arg (dx_grid.size () ? '(' + dx_grid + ')' : QString {}) : QString {}); update_line_edit (dx_grid_line_edit_, dx_grid);
rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : ""); if (rx_df != quint32_max) update_spin_box (rx_df_spin_box_, rx_df);
tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_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); report_label_->setText ("SNR: " + report);
update_dynamic_property (frequency_label_, "transmitting", transmitting); update_dynamic_property (frequency_label_, "transmitting", transmitting);
auto_off_button_->setEnabled (tx_enabled); auto_off_button_->setEnabled (tx_enabled);
halt_tx_button_->setEnabled (transmitting); 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); update_dynamic_property (tx_df_label_, "watchdog_timeout", watchdog_timeout);
} }
} }

View File

@ -1,11 +1,11 @@
#ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ #ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__ #define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
#include <QDockWidget>
#include <QObject> #include <QObject>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QString> #include <QString>
#include <QRegularExpression> #include <QRegularExpression>
#include <QtWidgets>
#include "MessageServer.hpp" #include "MessageServer.hpp"
@ -13,6 +13,20 @@ class QAbstractItemModel;
class QModelIndex; class QModelIndex;
class QColor; class QColor;
class QAction; 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; using Frequency = MessageServer::Frequency;
@ -25,16 +39,18 @@ public:
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision , QString const& id, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent = nullptr); , QListWidget const * calls_of_interest, QWidget * parent = nullptr);
void dispose ();
~ClientWidget (); ~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 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 , 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 , QString const& de_call, QString const& de_grid, QString const& dx_grid
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode , 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 Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode , float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air); , QString const& message, bool low_confidence, bool off_air);
@ -45,6 +61,7 @@ public:
Q_SLOT void decodes_cleared (QString const& client_id); 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_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_reply (QModelIndex const&, quint8 modifier);
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only); 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); 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 Q_SIGNAL void highlight_callsign (QString const& id, QString const& call
, QColor const& bg = QColor {}, QColor const& fg = QColor {} , QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false); , 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: private:
QString id_;
QListWidget const * calls_of_interest_;
class IdFilterModel final class IdFilterModel final
: public QSortFilterProxyModel : public QSortFilterProxyModel
{ {
public: public:
IdFilterModel (QString const& client_id); IdFilterModel (QString const& client_id, QObject * = nullptr);
void de_call (QString const&); 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; QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override;
private:
protected:
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override; bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
private:
QString client_id_; QString client_id_;
QString call_; QString call_;
QRegularExpression base_call_re_; QRegularExpression base_call_re_;
int rx_df_; quint32 rx_df_;
} decodes_proxy_model_; };
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_action_;
QAction * erase_rx_frequency_action_; QAction * erase_rx_frequency_action_;
QAction * erase_both_action_; QAction * erase_both_action_;
@ -83,17 +109,39 @@ private:
QTableView * beacons_table_view_; QTableView * beacons_table_view_;
QLineEdit * message_line_edit_; QLineEdit * message_line_edit_;
QLineEdit * grid_line_edit_; QLineEdit * grid_line_edit_;
QStackedLayout * decodes_stack_; QAbstractButton * generate_messages_push_button_;
QAbstractButton * auto_off_button_; QAbstractButton * auto_off_button_;
QAbstractButton * halt_tx_button_; QAbstractButton * halt_tx_button_;
QLabel * de_label_; QLabel * de_label_;
QLabel * mode_label_;
bool fast_mode_;
QLabel * frequency_label_; QLabel * frequency_label_;
QLabel * dx_label_;
QLabel * rx_df_label_;
QLabel * tx_df_label_; QLabel * tx_df_label_;
QLabel * report_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_; 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::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
connect (server_, &MessageServer::decodes_cleared, dock, &ClientWidget::decodes_cleared); connect (server_, &MessageServer::decodes_cleared, dock, &ClientWidget::decodes_cleared);
connect (dock, &ClientWidget::do_clear_decodes, server_, &MessageServer::clear_decodes); 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_reply, decodes_model_, &DecodesModel::do_reply);
connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx); connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text); connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
connect (dock, &ClientWidget::location, server_, &MessageServer::location); connect (dock, &ClientWidget::location, server_, &MessageServer::location);
connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible); connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign); 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; dock_widgets_[id] = dock;
server_->replay (id); // request decodes and status server_->replay (id); // request decodes and status
} }
@ -265,7 +268,7 @@ void MessageAggregatorMainWindow::remove_client (QString const& id)
auto iter = dock_widgets_.find (id); auto iter = dock_widgets_.find (id);
if (iter != std::end (dock_widgets_)) if (iter != std::end (dock_widgets_))
{ {
(*iter)->close (); (*iter)->dispose ();
dock_widgets_.erase (iter); dock_widgets_.erase (iter);
} }
} }

View File

@ -51,7 +51,8 @@ public:
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/ , bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/ , QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/ , 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_) if (id == id_)
{ {

View File

@ -1,6 +1,6 @@
# Version number components # Version number components
set (WSJTX_VERSION_MAJOR 2) set (WSJTX_VERSION_MAJOR 2)
set (WSJTX_VERSION_MINOR 0) set (WSJTX_VERSION_MINOR 1)
set (WSJTX_VERSION_PATCH 1) set (WSJTX_VERSION_PATCH 0)
set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions set (WSJTX_RC 8) # release candidate number, comment out or zero for development versions
set (WSJTX_VERSION_IS_RELEASE 1) # set to 1 for final release build set (WSJTX_VERSION_IS_RELEASE 1) # set to 1 for final release build

View File

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

View File

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

1986
cty.dat

File diff suppressed because it is too large Load Diff

View File

@ -51,11 +51,12 @@ DecodedText::DecodedText (QString const& the_string)
QStringList DecodedText::messageWords () const QStringList DecodedText::messageWords () const
{ {
if (is_standard_) if(is_standard_) {
{ // extract up to the first four message words
// extract up to the first four message words QString t=message_;
return words_re.match (message_).capturedTexts (); if(t.left(4)=="TU; ") t=message_.mid(4,-1);
} return words_re.match(t).capturedTexts();
}
// simple word split for free text messages // simple word split for free text messages
auto words = message_.split (' ', QString::SkipEmptyParts); auto words = message_.split (' ', QString::SkipEmptyParts);
// add whole message as item 0 to mimic RE capture list // add whole message as item 0 to mimic RE capture list

View File

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

View File

@ -91,7 +91,8 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK] :sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK]
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice] :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] :win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages]
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_0_2q.exe[Win32 OpenSSL Lite Package] :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] :writelog: https://writelog.com/[Writelog]
:wsjt_yahoo_group: https://groups.yahoo.com/neo/groups/wsjtgroup/info[WSJT Group] :wsjt_yahoo_group: https://groups.yahoo.com/neo/groups/wsjtgroup/info[WSJT Group]
:wsjtx: http://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X] :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] :QRA64_EME: http://physics.princeton.edu/pulsar/K1JT/QRA64_EME.pdf[QRA64 for microwave EME]
:svn: http://subversion.apache.org/packages.html#windows[Subversion] :svn: http://subversion.apache.org/packages.html#windows[Subversion]
:win32: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win32.exe[wsjtx-{VERSION}-win32.exe] :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-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] :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] :wspr_code: http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe[WSPRcode.exe]

View File

@ -95,3 +95,19 @@ QT_QPA_PLATFORMTHEME set to empty (the space after the '=' character
is necessary): is necessary):
QT_QPA_PLATFORMTHEME= wsjtx QT_QPA_PLATFORMTHEME= wsjtx
I am running _WSJT-X_ on Linux using a KDE desktop. Why does *Menu->Configurations* misbehave?::
The KDE development team have added code to Qt that tries to
automatically add shortcut accelerator keys to all buttons including
pop up menu buttons, this interferes with operation of the application
(many other Qt applications have similar issues with KDE). Until this
is fixed by the KDE team you must disable this misfeature. Edit the
file ~/.config/kdeglobals and add a section containing the following:
[Development]
AutoCheckAccelerators=false
+
See https://stackoverflow.com/a/32711483 and
https://bugs.kde.org/show_bug.cgi?id=337491 for more details.

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 // Status=review
Download and execute the package file {win32}, following these Download and execute the package file {win32} (WinXP, Vista, Win 7,
instructions: 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 ` * Install _WSJT-X_ into its own directory, for example `C:\WSJTX` or `
C:\WSJT\WSJTX`, rather than the conventional location `C:\Program 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 * All program files relating to _WSJT-X_ will be stored in the chosen
installation directory and its subdirectories. installation directory and its subdirectories.
@ -29,26 +30,31 @@ TIP: Your computer may be configured so that this directory is
[[OPENSSL]] [[OPENSSL]]
* image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error, role="right"] * image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error,
From this version onward _WSJT-X_ requires the _OpenSSL_ libraries role="right"] _WSJT-X_ requires the _OpenSSL_ libraries to be
to be installed. Suitable libraries may already be installed on your installed. Suitable libraries may already be installed on your
system, if they are not you will see this error shortly after system, if they are not you will see this error shortly after
startup. To fix this you need to install the _OpenSSL_ libraries. startup. To fix this you need to install the _OpenSSL_ libraries.
** You can download a suitable _OpenSSL_ package for from ** You can download a suitable _OpenSSL_ package for from
{win_openssl_packages}, you need the latest *Win32 v1.0.2 Lite* {win_openssl_packages}, you need the latest *Windows v1.0.2 Lite*
version (Note it is the Win32 package even if you are using a version. For the 32-bit _WSJT-X_ build use the Win32 version of the
64-bit Windows operating system) which at the time of writing was _OpenSSL_ libraries, for the 64-bit _WSJT-X_ use the Win64 version
{win32_openssl}. 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 ** Install the package and accept the default options, including the
option to copy the _OpenSSL_ DLLs to the Windows system directory option to copy the _OpenSSL_ DLLs to the Windows system
(this is important). + 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 NOTE: If you still get the same network error after installing the
_OpenSSL_ libraries then you also need to install the _OpenSSL_ libraries then you also need to install the
{msvcpp_redist} component. From the download page select {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 TIP: If you cannot install the _OpenSSL_ libraries or do not have an
Internet connection on the computer used to run 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 an extended and experimental branch of the program
_WSJT_. _WSJT_.
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers nine different _WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers ten different
protocols or modes: *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*, *ISCAT*, protocols or modes: *FT4*, *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*,
*MSK144*, *WSPR*, and *Echo*. The first five are designed for making *ISCAT*, *MSK144*, *WSPR*, and *Echo*. The first six are designed for
reliable QSOs under extreme weak-signal conditions. They use nearly making reliable QSOs under weak-signal conditions. They use nearly
identical message structure and source encoding. JT65 and QRA64 were identical message structure and source encoding. JT65 and QRA64 were
designed for EME ("`moonbounce`") on the VHF/UHF bands and have also designed for EME ("`moonbounce`") on the VHF/UHF bands and have also
proven very effective for worldwide QRP communication on the HF bands. 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 so a minimal QSO takes four to six minutes — two or three
transmissions by each station, one sending in odd UTC minutes and the transmissions by each station, one sending in odd UTC minutes and the
other even. FT8 is operationally similar but four times faster 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 (15-second T/R sequences) and less sensitive by a few dB. FT4 is
bands, world-wide QSOs are possible with any of these modes using faster still (7.5 s T/R sequences) and especially well suited for
power levels of a few watts (or even milliwatts) and compromise radio contesting. On the HF bands, world-wide QSOs are possible with
antennas. On VHF bands and higher, QSOs are possible (by EME and any of these modes using power levels of a few watts (or even
other propagation types) at signal levels 10 to 15 dB below those milliwatts) and compromise antennas. On VHF bands and higher, QSOs
required for CW. 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`" *ISCAT*, *MSK144*, and optionally submodes *JT9E-H* are "`fast`"
protocols designed to take advantage of brief signal enhancements from 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 *Version Numbers:* _WSJT-X_ release numbers have major, minor, and
patch numbers separated by periods: for example, _WSJT-X_ Version 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 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 feedback. For example, version 2.1.0-rc1, 2.1.0-rc2, etc., would
be beta releases leading up to the final release of v1.9.0. be beta releases leading up to the final release of v2.1.0.
Release candidates should be used _only_ during a short testing Release candidates should be used _only_ during a short testing
period. They carry an implied obligation to provide feedback to the period. They carry an implied obligation to provide feedback to the
program development group. Candidate releases should not be used on program development group. Candidate releases should not be used on

View File

@ -1,40 +1,15 @@
=== New in Version {VERSION} === New in Version {VERSION}
For quick reference, here's a short list of features and capabilities The most important feature added to _WSJT-X_ since Version 2.0.1 is
added to _WSJT-X_ since Version 1.9.1: the new *FT4 protocol*, designed especially for radio contesting. It
has T/R sequence length 7.5 s, bandwidth 80 Hz, and threshold
- New FT8 and MSK144 protocols with 77-bit payloads permit these enhancements: sensitivity -17.5 dB. Version 2.1.0 also has improvements to FT8
waveform generation, waterfall and spectrum display, contest logging,
* Optimized contest messages for NA VHF, EU VHF, Field Day, RTTY Roundup rig control, the user interface, keyboard shortcuts, UDP messaging for
inter-program communication, and accessibility, as well as a number of
* Full support for "/R" and "/P" calls in relevant contests more minor enhancements and bug fixes. We now provide a separate
installation package for 64-bit Windows Vista and later, offering
* New logging features for contesting significant improvements in decoding speed.
* 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.
=== Documentation Conventions === 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 aim is to compress the most common messages used for minimally valid
QSOs into a fixed 72-bit length. QSOs into a fixed 72-bit length.
The information payload for FT8 and MSK144 contains 77 bits. The 5 The information payload for FT4, FT8, and MSK144 contains 77 bits.
additional bits are used to flag special message types used for FT8 The 5 new bits added to the original 72 are used to flag special
DXpedition Mode, contesting, nonstandard callsigns, and a few other message types signifying special message types used for FT8 DXpedition
special types. Mode, contesting, nonstandard callsigns, and a few other
possibilities.
A standard amateur callsign consists of a one- or two-character 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 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]]
=== 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]] [[FT8PRO]]
==== FT8 ==== FT8
Forward error correction (FEC) in FT8 uses a low-density parity check FT8 uses the same LDPC (174,91) code as FT4. Modulation is 8-tone
(LDPC) code with 77 information bits, a 14-bit cyclic redundancy check frequency-shift keying (8-GFSK) at 12000/1920 = 6.25 baud.
(CRC), and 83 parity bits making a 174-bit codeword. It is thus Synchronization uses 7×7 Costas arrays at the beginning, middle, and
called an LDPC (174,91) code. Synchronization uses 7×7 Costas arrays end of each transmission. Transmitted symbols carry three bits, so
at the beginning, middle, and end of each transmission. Modulation is the total number of channel symbols is 174/3 + 21 = 79. The total
8-tone frequency-shift keying (8-FSK) at 12000/1920 = 6.25 baud. Each occupied bandwidth is 8 × 6.25 = 50 Hz.
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.
[[JT4PRO]] [[JT4PRO]]
==== JT4 ==== 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) |Mode |FEC Type |(n,k) | Q|Modulation type|Keying rate (Baud)|Bandwidth (Hz)
|Sync Energy|Tx Duration (s)|S/N Threshold (dB) |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 |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 |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 |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"] [width="50%",cols="h,3*^",frame=topbot,options="header"]
|===================================== |=====================================
|Mode |Tone Spacing |BW (Hz)|S/N (dB) |Mode |Tone Spacing |BW (Hz)|S/N (dB)
|FT4 |20.8333 | 83.3 |-17.5
|FT8 |6.25 | 50.0 |-21 |FT8 |6.25 | 50.0 |-21
|JT4A |4.375| 17.5 |-23 |JT4A |4.375| 17.5 |-23
|JT4B |8.75 | 30.6 |-22 |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 === FT8
include::tutorial-example3.adoc[] include::tutorial-example3.adoc[]
[[TUT_EX4]]
=== FT4
include::tutorial-example4.adoc[]
[[MAKE_QSOS]] [[MAKE_QSOS]]
== Making QSOs == Making QSOs
include::make-qso.adoc[] 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 += \ SOURCES += \
item_delegates/ForeignKeyDelegate.cpp \ item_delegates/ForeignKeyDelegate.cpp \
item_delegates/FrequencyItemDelegate.cpp \ item_delegates/FrequencyDelegate.cpp \
item_delegates/FrequencyDeltaDelegate.cpp \
item_delegates/CallsignDelegate.cpp \ item_delegates/CallsignDelegate.cpp \
item_delegates/MaidenheadLocatorItemDelegate.cpp item_delegates/MaidenheadLocatorItemDelegate.cpp
HEADERS += \ HEADERS += \
item_delegates/ForeignKeyDelegate.hpp \ item_delegates/ForeignKeyDelegate.hpp \
item_delegates/FrequencyItemDelegate.hpp \ item_delegates/FrequencyDelegate.hpp \
item_delegates/FrequencyDeltaDelegate.hpp \
item_delegates/CallsignDelegate.hpp \ item_delegates/CallsignDelegate.hpp \
item_delegates/MaidenheadLocatorDelegate.hpp \ item_delegates/MaidenheadLocatorDelegate.hpp
item_delegates/DateTimeAsSecsSinceEpochDelegate.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 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 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 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 6 ... tbd
7 ... 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. 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 W9XYZ <YW18FIFA> R-09
YW18FIFA <W9XYZ> RRR YW18FIFA <W9XYZ> RRR
<W9XYZ> YW18FIFA 73 <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 TNX BOB 73 GL
CQ YW18FIFA CQ YW18FIFA

View File

@ -172,6 +172,10 @@ subroutine pack77(msg0,i3,n3,c77)
call pack77_4(nwords,w,i3,n3,c77) call pack77_4(nwords,w,i3,n3,c77)
if(i3.ge.0) go to 900 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 ! It defaults to free text
800 i3=0 800 i3=0
n3=0 n3=0
@ -204,6 +208,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
character*6 cexch,grid6 character*6 cexch,grid6
character*4 grid4,cserial character*4 grid4,cserial
character*3 csec(NSEC) character*3 csec(NSEC)
character*2 cfield
character*38 c character*38 c
integer hashmy10,hashmy12,hashmy22,hashdx10,hashdx12,hashdx22 integer hashmy10,hashmy12,hashmy22,hashdx10,hashdx12,hashdx22
logical unpk28_success,unpk77_success logical unpk28_success,unpk77_success
@ -491,8 +496,31 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
else else
msg='CQ '//trim(call_2) msg='CQ '//trim(call_2)
endif 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 endif
if(msg(1:4).eq.'CQ <') unpk77_success=.false. ! if(msg(1:4).eq.'CQ <') unpk77_success=.false.
return return
end subroutine unpack77 end subroutine unpack77
@ -1040,12 +1068,11 @@ subroutine pack77_3(nwords,w,i3,n3,c77)
call chkcall(w(i1+1),bcall_2,ok2) call chkcall(w(i1+1),bcall_2,ok2)
if(.not.ok1 .or. .not.ok2) go to 900 if(.not.ok1 .or. .not.ok2) go to 900
crpt=w(nwords-1)(1:3) 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. & 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 crpt(3:3).eq.'9') then
nserial=0 nserial=0
read(w(nwords),*,err=1) nserial read(w(nwords),*,err=1) nserial
!1 i3=3
! n3=0
endif endif
1 mult=' ' 1 mult=' '
imult=-1 imult=-1
@ -1150,6 +1177,60 @@ subroutine pack77_4(nwords,w,i3,n3,c77)
900 return 900 return
end subroutine pack77_4 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) subroutine packtext77(c13,c71)
character*13 c13,w character*13 c13,w

View File

@ -12,18 +12,22 @@ subroutine addit(itone,nfsample,nsym,nsps,ifreq,sig,dat)
dphi=0. dphi=0.
iters=1 iters=1
if(nsym.eq.79) iters=2 if(nsym.eq.79) iters=2 !FT8
if(nsym.eq.103) iters=5 !FT4
do iter=1,iters do iter=1,iters
f=ifreq f=ifreq
phi=0. phi=0.
ntot=nsym*tsym/dt ntot=nsym*tsym/dt
k=12000 !Start audio at t = 1.0 s k=12000 !Start audio at t = 1.0 s
t=0. t=0.
if(nsym.eq.79) k=12000 + (iter-1)*12000*30 !Special case for FT8 if(nsym.eq.79) k=12000 + (iter-1)*12000*30 !Special case for FT8
if(nsym.eq.103) k=12000 + (iter-1)*12000*10 !Special case for FT4
isym0=-1 isym0=-1
do i=1,ntot do i=1,ntot
t=t+dt t=t+dt
isym=nint(t/tsym) + 1 isym=nint(t/tsym) + 1
if(isym.gt.nsym) exit
if(isym.ne.isym0) then if(isym.ne.isym0) then
freq=f + itone(isym)*baud freq=f + itone(isym)*baud
dphi=twopi*freq*dt dphi=twopi*freq*dt
@ -59,7 +63,7 @@ subroutine addcw(icw,ncw,ifreq,sig,dat)
phi=0. phi=0.
k=12000 !Start audio at t = 1.0 s k=12000 !Start audio at t = 1.0 s
t=0. t=0.
npts=60*12000 npts=59*12000
x=0. x=0.
do i=1,npts do i=1,npts
t=t+dt t=t+dt

View File

@ -1,6 +1,6 @@
program allsim program allsim
! Generate simulated data for WSJT-X slow modes: JT4, JT9, JT65, QRA64, ! Generate simulated data for WSJT-X modes: JT4, JT9, JT65, FT8, FT4, QRA64,
! and WSPR. Also unmodulated carrier and 20 WPM CW. ! and WSPR. Also unmodulated carrier and 20 WPM CW.
@ -15,6 +15,7 @@ program allsim
logical*1 bcontest logical*1 bcontest
real*4 dat(NMAX) real*4 dat(NMAX)
character message*22,msgsent*22,arg*8,mygrid*6 character message*22,msgsent*22,arg*8,mygrid*6
character*37 msg37,msgsent37
nargs=iargc() nargs=iargc()
if(nargs.ne.1) then if(nargs.ne.1) then
@ -60,15 +61,20 @@ program allsim
call gen4(message,0,msgsent,itone,itype) call gen4(message,0,msgsent,itone,itype)
call addit(itone,11025,206,2520,1200,sig,dat) !JT4 call addit(itone,11025,206,2520,1200,sig,dat) !JT4
i3bit=0 ! ### TEMPORARY ??? ### i3=-1
call genft8(message,mygrid,bcontest,i3bit,msgsent,msgbits,itone) n3=-1
call genft8(message,i3,n3,msgsent,msgbits,itone)
call addit(itone,12000,79,1920,1400,sig,dat) !FT8 call addit(itone,12000,79,1920,1400,sig,dat) !FT8
msg37=message//' '
call genft4(msg37,0,msgsent37,itone)
call addit(itone,12000,103,512,1600,sig,dat) !FT4
call genqra64(message,0,msgsent,itone,itype) call genqra64(message,0,msgsent,itone,itype)
call addit(itone,12000,84,6912,1600,sig,dat) !QRA64 call addit(itone,12000,84,6912,1800,sig,dat) !QRA64
call gen65(message,0,msgsent,itone,itype) call gen65(message,0,msgsent,itone,itype)
call addit(itone,11025,126,4096,1800,sig,dat) !JT65 call addit(itone,11025,126,4096,2000,sig,dat) !JT65
iwave(1:npts)=nint(rms*dat(1:npts)) iwave(1:npts)=nint(rms*dat(1:npts))

View File

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

View File

@ -7,6 +7,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
use jt65_decode use jt65_decode
use jt9_decode use jt9_decode
use ft8_decode use ft8_decode
use ft4_decode
include 'jt9com.f90' include 'jt9com.f90'
include 'timer_common.inc' include 'timer_common.inc'
@ -27,6 +28,10 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
integer :: decoded integer :: decoded
end type counting_ft8_decoder end type counting_ft8_decoder
type, extends(ft4_decoder) :: counting_ft4_decoder
integer :: decoded
end type counting_ft4_decoder
real ss(184,NSMAX) real ss(184,NSMAX)
logical baddata,newdat65,newdat9,single_decode,bVHF,bad0,newdat,ex logical baddata,newdat65,newdat9,single_decode,bVHF,bad0,newdat,ex
integer*2 id2(NTMAX*12000) integer*2 id2(NTMAX*12000)
@ -40,6 +45,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
type(counting_jt65_decoder) :: my_jt65 type(counting_jt65_decoder) :: my_jt65
type(counting_jt9_decoder) :: my_jt9 type(counting_jt9_decoder) :: my_jt9
type(counting_ft8_decoder) :: my_ft8 type(counting_ft8_decoder) :: my_ft8
type(counting_ft4_decoder) :: my_ft4
!cast C character arrays to Fortran character strings !cast C character arrays to Fortran character strings
datetime=transfer(params%datetime, datetime) datetime=transfer(params%datetime, datetime)
@ -53,6 +59,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
my_jt65%decoded = 0 my_jt65%decoded = 0
my_jt9%decoded = 0 my_jt9%decoded = 0
my_ft8%decoded = 0 my_ft8%decoded = 0
my_ft4%decoded = 0
single_decode=iand(params%nexp_decode,32).ne.0 single_decode=iand(params%nexp_decode,32).ne.0
bVHF=iand(params%nexp_decode,64).ne.0 bVHF=iand(params%nexp_decode,64).ne.0
@ -126,8 +133,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
n30fox(j)=n n30fox(j)=n
m=n30max-n m=n30max-n
if(len(trim(g2fox(j))).eq.4) then if(len(trim(g2fox(j))).eq.4) then
call azdist(mygrid,g2fox(j),0.d0,nAz,nEl,nDmiles,nDkm, & call azdist(mygrid,g2fox(j)//' ',0.d0,nAz,nEl,nDmiles, &
nHotAz,nHotABetter) nDkm,nHotAz,nHotABetter)
else else
nDkm=9999 nDkm=9999
endif endif
@ -142,6 +149,15 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
go to 800 go to 800
endif endif
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, &
logical(params%lapcqonly),ncontest,mycall,hiscall)
call timer('decft4 ',1)
go to 800
endif
rms=sqrt(dot_product(float(id2(300000:310000)), & rms=sqrt(dot_product(float(id2(300000:310000)), &
float(id2(300000:310000)))/10000.0) float(id2(300000:310000)))/10000.0)
if(rms.lt.2.0) go to 800 if(rms.lt.2.0) go to 800
@ -258,7 +274,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
!$omp end parallel sections !$omp end parallel sections
! JT65 is not yet producing info for nsynced, ndecoded. ! JT65 is not yet producing info for nsynced, ndecoded.
800 ndecoded = my_jt4%decoded + my_jt65%decoded + my_jt9%decoded + my_ft8%decoded 800 ndecoded = my_jt4%decoded + my_jt65%decoded + my_jt9%decoded + &
my_ft8%decoded + my_ft4%decoded
write(*,1010) nsynced,ndecoded write(*,1010) nsynced,ndecoded
1010 format('<DecodeFinished>',2i4) 1010 format('<DecodeFinished>',2i4)
call flush(6) call flush(6)
@ -500,7 +517,7 @@ contains
decoded0=decoded decoded0=decoded
annot=' ' annot=' '
if(ncontest.eq.0 .and. nap.ne.0) then if(nap.ne.0) then
write(annot,'(a1,i1)') 'a',nap write(annot,'(a1,i1)') 'a',nap
if(qual.lt.0.17) decoded0(37:37)='?' if(qual.lt.0.17) decoded0(37:37)='?'
endif endif
@ -561,4 +578,44 @@ contains
return return
end subroutine ft8_decoded end subroutine ft8_decoded
subroutine ft4_decoded (this,sync,snr,dt,freq,decoded,nap,qual)
use ft4_decode
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
character c1*12,c2*12,g2*4,w*4
integer i0,i1,i2,i3,i4,i5,n30,nwrap
integer, intent(in) :: nap
real, intent(in) :: qual
character*2 annot
character*37 decoded0
decoded0=decoded
annot=' '
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)
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,' FT4')
call flush(6)
call flush(13)
select type(this)
type is (counting_ft4_decoder)
this%decoded = this%decoded + 1
end select
return
end subroutine ft4_decoded
end subroutine multimode_decoder end subroutine multimode_decoder

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

View File

@ -19,6 +19,7 @@ subroutine four2a(a,nfft,ndim,isign,iform)
! This version of four2a makes calls to the FFTW library to do the ! This version of four2a makes calls to the FFTW library to do the
! actual computations. ! actual computations.
use fftw3
parameter (NPMAX=2100) !Max numberf of stored plans parameter (NPMAX=2100) !Max numberf of stored plans
parameter (NSMALL=16384) !Max size of "small" FFTs parameter (NSMALL=16384) !Max size of "small" FFTs
complex a(nfft) !Array to be transformed complex a(nfft) !Array to be transformed
@ -29,7 +30,6 @@ subroutine four2a(a,nfft,ndim,isign,iform)
logical found_plan logical found_plan
data nplan/0/ !Number of stored plans data nplan/0/ !Number of stored plans
common/patience/npatience,nthreads !Patience and threads for FFTW plans common/patience/npatience,nthreads !Patience and threads for FFTW plans
include 'fftw3.f90' !FFTW definitions
save plan,nplan,nn,ns,nf,nl save plan,nplan,nn,ns,nf,nl
if(nfft.lt.0) go to 999 if(nfft.lt.0) go to 999
@ -107,7 +107,7 @@ subroutine four2a(a,nfft,ndim,isign,iform)
!$omp end critical(fftw) !$omp end critical(fftw)
end if end if
enddo enddo
call fftwf_cleanup()
nplan=0 nplan=0
!$omp end critical(four2a) !$omp end critical(four2a)

View File

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

12
lib/fsk4hf/ft2_params.f90 Normal file
View File

@ -0,0 +1,12 @@
! LDPC (128,90) code
parameter (KK=90) !Information bits (77 + CRC13)
parameter (ND=128) !Data symbols
parameter (NS=16) !Sync symbols (2x8)
parameter (NN=NS+ND) !Total channel symbols (144)
parameter (NSPS=160) !Samples per symbol at 12000 S/s
parameter (NZ=NSPS*NN) !Samples in full 1.92 s waveform (23040)
parameter (NMAX=2.5*12000) !Samples in iwave (36,000)
parameter (NFFT1=400, NH1=NFFT1/2) !Length of FFTs for symbol spectra
parameter (NSTEP=NSPS/4) !Rough time-sync step size
parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps)
parameter (NDOWN=16) !Downsample factor

335
lib/fsk4hf/ft2d.f90 Normal file
View File

@ -0,0 +1,335 @@
program ft2d
use crc
use packjt77
include 'ft2_params.f90'
character arg*8,message*37,c77*77,infile*80,fname*16,datetime*11
character*37 decodes(100)
character*120 data_dir
character*90 dmsg
complex c2(0:NMAX/16-1) !Complex waveform
complex cb(0:NMAX/16-1)
complex cd(0:144*10-1) !Complex waveform
complex c1(0:9),c0(0:9)
complex ccor(0:1,144)
complex csum,cterm,cc0,cc1,csync1,csync2
complex csync(16),csl(0:159)
real*8 fMHz
real a(5)
real rxdata(128),llr(128) !Soft symbols
real llr2(128)
real sbits(144),sbits1(144),sbits3(144)
real ps(0:8191),psbest(0:8191)
real candidates(100,2)
real savg(NH1),sbase(NH1)
integer ihdr(11)
integer*2 iwave(NMAX) !Generated full-length waveform
integer*1 message77(77),apmask(128),cw(128)
integer*1 hbits(144),hbits1(144),hbits3(144)
integer*1 s16(16),s45(45)
logical unpk77_success
data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/
data s45/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0,1,1,0,1,0,0,0,1,1,1,0,0/
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)
twopi=8.0*atan(1.0)
h=0.800 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading)
dphi=twopi/2*baud*h*dt*16 ! dt*16 is samp interval after downsample
dphi0=-1*dphi
dphi1=+1*dphi
phi0=0.0
phi1=0.0
do i=0,9
c1(i)=cmplx(cos(phi1),sin(phi1))
c0(i)=cmplx(cos(phi0),sin(phi0))
phi1=mod(phi1+dphi1,twopi)
phi0=mod(phi0+dphi0,twopi)
enddo
the=twopi*h/2.0
cc1=cmplx(cos(the),-sin(the))
cc0=cmplx(cos(the),sin(the))
k=0
do j=1,16
dphi1=(2*s16(j)-1)*dphi
phi1=0.0
do i=0,9
csl(k)=cmplx(cos(phi1),sin(phi1))
phi1=mod(phi1+dphi1,twopi)
k=k+1
enddo
enddo
nargs=iargc()
if(nargs.lt.1) then
print*,'Usage: ft2d [-a <data_dir>] [-f fMHz] 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
ncoh=1
do ifile=iarg,nargs
call getarg(ifile,infile)
j2=index(infile,'.wav')
open(10,file=infile,status='old',access='stream')
read(10,end=999) ihdr,iwave
read(infile(j2-4:j2-1),*) nutc
datetime=infile(j2-11:j2-1)
close(10)
candidates=0.0
ncand=0
call getcandidates2(iwave,375.0,3000.0,0.2,2200.0,100,savg,candidates,ncand,sbase)
ndecodes=0
do icand=1,ncand
f0=candidates(icand,1)
xsnr=1.0
if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle
call ft2_downsample(iwave,f0,c2) ! downsample from 160s/Symbol to 10s/Symbol
!c2=c2/sqrt(sum(abs(c2(0:NMAX/16-1))))
!ishift=-1
!rccbest=-99.
!do is=0,435
!rcc=0.0
! do id=10,10
! rcc=rcc+abs(sum(conjg(c2(is:is+159-id))*c2(is+id:is+159)*csl(0:159-id)*conjg(csl(id:159))))
! enddo
! if(rcc.gt.rccbest) then
! rccbest=rcc
! ishift=is
! endif
!write(21,*) is,rcc
!enddo
! 750 samples/second here
ibest=-1
sybest=-99.
dfbest=-1.
do if=-30,+30
df=if
a=0.
a(1)=-df
call twkfreq1(c2,NMAX/16,fs,a,cb)
do is=0,374
csync1=0.
cterm=1
do ib=1,16
! do ib=1,45
i1=(ib-1)*10+is
if(s16(ib).eq.1) then
! if(s45(ib).eq.1) then
csync1=csync1+sum(cb(i1:i1+9)*conjg(c1(0:9)))*cterm
cterm=cterm*cc1
else
csync1=csync1+sum(cb(i1:i1+9)*conjg(c0(0:9)))*cterm
cterm=cterm*cc0
endif
enddo
if(abs(csync1).gt.sybest) then
ibest=is
sybest=abs(csync1)
dfbest=df
endif
enddo
enddo
a=0.
!dfbest=1500.0-f0
a(1)=-dfbest
call twkfreq1(c2,NMAX/16,fs,a,cb)
!ibest=197
ib=ibest
cd=cb(ib:ib+144*10-1)
s2=sum(cd*conjg(cd))/(10*144)
cd=cd/sqrt(s2)
do nseq=1,4
if( nseq.eq.1 ) then ! noncoherent single-symbol detection
sbits1=0.0
do ibit=1,144
ib=(ibit-1)*10
ccor(1,ibit)=sum(cd(ib:ib+9)*conjg(c1(0:9)))
ccor(0,ibit)=sum(cd(ib:ib+9)*conjg(c0(0:9)))
sbits1(ibit)=abs(ccor(1,ibit))-abs(ccor(0,ibit))
hbits1(ibit)=0
if(sbits1(ibit).gt.0) hbits1(ibit)=1
enddo
sbits=sbits1
hbits=hbits1
sbits3=sbits1
hbits3=hbits1
elseif( nseq.ge.2 ) then
nbit=2*nseq-1
numseq=2**(nbit)
ps=0
do ibit=nbit/2+1,144-nbit/2
ps=0.0
pmax=0.0
do iseq=0,numseq-1
csum=0.0
cterm=1.0
k=1
do i=nbit-1,0,-1
ibb=iand(iseq/(2**i),1)
csum=csum+ccor(ibb,ibit-(nbit/2+1)+k)*cterm
if(ibb.eq.0) cterm=cterm*cc0
if(ibb.eq.1) cterm=cterm*cc1
k=k+1
enddo
ps(iseq)=abs(csum)
if( ps(iseq) .gt. pmax ) then
pmax=ps(iseq)
ibflag=1
endif
enddo
if( ibflag .eq. 1 ) then
psbest=ps
ibflag=0
endif
call getbitmetric(2**(nbit/2),psbest,numseq,sbits3(ibit))
hbits3(ibit)=0
if(sbits3(ibit).gt.0) hbits3(ibit)=1
enddo
sbits=sbits3
hbits=hbits3
endif
nsync_qual=count(hbits(1:16).eq.s16)
! if(nsync_qual.lt.10) exit
rxdata=sbits(17:144)
rxav=sum(rxdata(1:128))/128.0
rx2av=sum(rxdata(1:128)*rxdata(1:128))/128.0
rxsig=sqrt(rx2av-rxav*rxav)
rxdata=rxdata/rxsig
sigma=0.80
llr(1:128)=2*rxdata/(sigma*sigma)
!xllrmax=maxval(abs(llr))
!write(*,*) ifile,icand,nseq,nsync_qual
apmask=0
!apmask(1:29)=1
!llr(1:29)=xllrmax*(2*s45(17:45)-1)
max_iterations=40
do ibias=0,0
llr2=llr
if(ibias.eq.1) llr2=llr+0.4
if(ibias.eq.2) llr2=llr-0.4
call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations)
if(nharderror.ge.0) exit
enddo
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
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(idupe.eq.1) goto 888
ndecodes=ndecodes+1
decodes(ndecodes)=message
nsnr=nint(xsnr)
freq=f0+dfbest
1210 format(a11,2i4,f6.2,f12.7,2x,a22,i3)
write(*,1212) datetime(8:11),nsnr,ibest/750.0,freq,message,'*',nseq,nharderror,nsync_qual
1212 format(a4,i4,2x,f5.3,f11.1,2x,a22,a1,i5,i5,i5)
goto 888
endif
enddo ! nseq
888 continue
enddo !candidate list
enddo !files
write(*,1120)
1120 format("<DecodeFinished>")
999 end program ft2d
subroutine getbitmetric(ib,ps,ns,xmet)
real ps(0:ns-1)
xm1=0
xm0=0
do i=0,ns-1
if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i)
if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i)
enddo
xmet=xm1-xm0
return
end subroutine getbitmetric
subroutine downsample2(ci,f0,co)
parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10
complex ci(0:NI-1),ct(0:NI-1)
complex co(0:NO-1)
fs=12000.0
df=fs/NI
ct=ci
call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain
i0=nint(f0/df)
ct=cshift(ct,i0)
co=0.0
co(0)=ct(0)
b=8.0
do i=1,NO/2
arg=(i*df/b)**2
filt=exp(-arg)
co(i)=ct(i)*filt
co(NO-i)=ct(NI-i)*filt
enddo
co=co/NO
call four2a(co,NO,1,1,1) !c2c FFT back to time domain
return
end subroutine downsample2
subroutine ft2_downsample(iwave,f0,c)
! Input: i*2 data in iwave() at sample rate 12000 Hz
! Output: Complex data in c(), sampled at 1200 Hz
include 'ft2_params.f90'
parameter (NFFT2=NMAX/16)
integer*2 iwave(NMAX)
complex c(0:NMAX/16-1)
complex c1(0:NFFT2-1)
complex cx(0:NMAX/2)
real x(NMAX)
equivalence (x,cx)
BW=4.0*75
df=12000.0/NMAX
x=iwave
call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain
ibw=nint(BW/df)
i0=nint(f0/df)
c1=0.
c1(0)=cx(i0)
do i=1,NFFT2/2
arg=(i-1)*df/bw
win=exp(-arg*arg)
c1(i)=cx(i0+i)*win
c1(NFFT2-i)=cx(i0-i)*win
enddo
c1=c1/NFFT2
call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain
c=c1(0:NMAX/16-1)
return
end subroutine ft2_downsample

154
lib/fsk4hf/ft2sim.f90 Normal file
View File

@ -0,0 +1,154 @@
program ft2sim
! Generate simulated signals for experimental "FT2" mode
use wavhdr
use packjt77
include 'ft2_params.f90' !Set various constants
parameter (NWAVE=NN*NSPS)
type(hdr) h !Header for .wav file
character arg*12,fname*17
character msg37*37,msgsent37*37
character c77*77
complex c0(0:NMAX-1)
complex c(0:NMAX-1)
real wave(NMAX)
real dphi(0:NMAX-1)
real pulse(480)
integer itone(NN)
integer*1 msgbits(77)
integer*2 iwave(NMAX) !Generated full-length waveform
! Get command-line argument(s)
nargs=iargc()
if(nargs.ne.8) then
print*,'Usage: ft2sim "message" f0 DT fdop del width nfiles snr'
print*,'Examples: ft2sim "K1ABC W9XYZ EN37" 1500.0 0.0 0.1 1.0 0 10 -18'
print*,' ft2sim "WA9XYZ/R KA1ABC/R FN42" 1500.0 0.0 0.1 1.0 0 10 -18'
print*,' ft2sim "K1ABC RR73; W9XYZ <KH1/KH7Z> -11" 300 0 0 0 25 1 -10'
go to 999
endif
call getarg(1,msg37) !Message to be transmitted
call getarg(2,arg)
read(arg,*) f0 !Frequency (only used for single-signal)
call getarg(3,arg)
read(arg,*) xdt !Time offset from nominal (s)
call getarg(4,arg)
read(arg,*) fspread !Watterson frequency spread (Hz)
call getarg(5,arg)
read(arg,*) delay !Watterson delay (ms)
call getarg(6,arg)
read(arg,*) width !Filter transition width (Hz)
call getarg(7,arg)
read(arg,*) nfiles !Number of files
call getarg(8,arg)
read(arg,*) snrdb !SNR_2500
nfiles=abs(nfiles)
twopi=8.0*atan(1.0)
fs=12000.0 !Sample rate (Hz)
dt=1.0/fs !Sample interval (s)
hmod=0.800 !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)
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 genft2(msg37,0,msgsent37,itone,itype)
write(*,*)
write(*,'(a9,a37,3x,a7,i1,a1,i1)') 'Message: ',msgsent37,'i3.n3: ',i3,'.',n3
write(*,1000) f0,xdt,txt,snrdb
1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1)
write(*,*)
if(i3.eq.1) then
write(*,*) ' mycall hiscall hisgrid'
write(*,'(28i1,1x,i1,1x,28i1,1x,i1,1x,i1,1x,15i1,1x,3i1)') msgbits(1:77)
else
write(*,'(a14)') 'Message bits: '
write(*,'(77i1)') msgbits
endif
write(*,*)
write(*,'(a17)') 'Channel symbols: '
write(*,'(79i1)') itone
write(*,*)
call sgran()
! The filtered frequency pulse
do i=1,480
tt=(i-240.5)/160.0
pulse(i)=gfsk_pulse(1.0,tt)
enddo
! Define the instantaneous frequency waveform
dphi_peak=twopi*(hmod/2.0)/real(NSPS)
dphi=0.0
do j=1,NN
ib=(j-1)*160
ie=ib+480-1
dphi(ib:ie)=dphi(ib:ie)+dphi_peak*pulse*(2*itone(j)-1)
enddo
phi=0.0
c0=0.0
dphi=dphi+twopi*f0*dt
do j=0,NMAX-1
c0(j)=cmplx(cos(phi),sin(phi))
phi=mod(phi+dphi(j),twopi)
enddo
c0(0:159)=c0(0:159)*(1.0-cos(twopi*(/(i,i=0,159)/)/320.0) )/2.0
c0(145*160:145*160+159)=c0(145*160:145*160+159)*(1.0+cos(twopi*(/(i,i=0,159)/)/320.0 ))/2.0
c0(146*160:)=0.
k=nint((xdt+0.25)/dt)
c0=cshift(c0,-k)
ia=k
do ifile=1,nfiles
c=c0
if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c,NMAX,NWAVE,fs,delay,fspread)
c=sig*c
ib=k
wave=real(c)
peak=maxval(abs(wave(ia:ib)))
nslots=1
if(width.gt.0.0) call filt8(f0,nslots,width,wave)
if(snrdb.lt.90) then
do i=1,NMAX !Add gaussian noise at specified SNR
xnoise=gran()
wave(i)=wave(i) + xnoise
enddo
endif
gain=100.0
if(snrdb.lt.90.0) then
wave=gain*wave
else
datpk=maxval(abs(wave))
fac=32766.9/datpk
wave=fac*wave
endif
if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped."
iwave=nint(wave)
h=default_header(12000,NMAX)
write(fname,1102) ifile
1102 format('000000_',i6.6,'.wav')
open(10,file=fname,status='unknown',access='stream')
write(10) h,iwave !Save to *.wav file
close(10)
write(*,1110) ifile,xdt,f0,snrdb,fname
1110 format(i4,f7.2,f8.2,f7.1,2x,a17)
enddo
999 end program ft2sim

329
lib/fsk4hf/ft4d.f90 Normal file
View File

@ -0,0 +1,329 @@
program ft4d
use crc
use packjt77
include 'ft4_params.f90'
character arg*8,message*37,c77*77,infile*80,fname*16,datetime*11
character*37 decodes(100)
character*120 data_dir
character*90 dmsg
complex cd2(0:NMAX/16-1) !Complex waveform
complex cb(0:NMAX/16-1)
complex cd(0:76*20-1) !Complex waveform
complex csum,cterm
complex ctwk(80),ctwk2(80)
complex csymb(20)
complex cs(0:3,NN)
real s4(0:3,NN)
real*8 fMHz
real ps(0:8191),psbest(0:8191)
real bmeta(152),bmetb(152),bmetc(152)
real s(NH1,NHSYM)
real a(5)
real llr(128),llr2(128),llra(128),llrb(128),llrc(128)
real s2(0:255)
real candidate(3,100)
real savg(NH1),sbase(NH1)
integer ihdr(11)
integer icos4(0:3)
integer*2 iwave(NMAX) !Generated full-length waveform
integer*1 message77(77),apmask(128),cw(128)
integer*1 hbits(152),hbits1(152),hbits3(152)
integer*1 s12(12)
integer graymap(0:3)
integer ip(1)
logical unpk77_success
logical one(0:511,0:7) ! 256 4-symbol sequences, 8 bits
data s12/1,1,1,2,2,2,2,2,2,1,1,1/
data icos4/0,1,3,2/
data graymap/0,1,3,2/
save one
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)
twopi=8.0*atan(1.0)
h=1.0 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading)
one=.false.
do i=0,255
do j=0,7
if(iand(i,2**j).ne.0) one(i,j)=.true.
enddo
enddo
nargs=iargc()
if(nargs.lt.1) then
print*,'Usage: ft4d [-a <data_dir>] [-f fMHz] 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
ncoh=1
do ifile=iarg,nargs
call getarg(ifile,infile)
j2=index(infile,'.wav')
open(10,file=infile,status='old',access='stream')
read(10,end=999) ihdr,iwave
read(infile(j2-4:j2-1),*) nutc
datetime=infile(j2-11:j2-1)
close(10)
candidate=0.0
ncand=0
nfqso=1500
nfa=500
nfb=2700
syncmin=1.0
maxcand=100
! call syncft4(iwave,nfa,nfb,syncmin,nfqso,maxcand,s,candidate,ncand,sbase)
call getcandidates4(iwave,375.0,3000.0,0.2,2200.0,100,savg,candidate,ncand,sbase)
ndecodes=0
do icand=1,ncand
f0=candidate(1,icand)-1.5*37.5
xsnr=1.0
if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle
call ft4_downsample(iwave,f0,cd2) ! downsample from 320 Sa/Symbol to 20 Sa/Symbol
sum2=sum(cd2*conjg(cd2))/(20.0*76)
if(sum2.gt.0.0) cd2=cd2/sqrt(sum2)
! 750 samples/second here
ibest=-1
smax=-99.
dfbest=-1.
do idf=-90,+90,5
df=idf
a=0.
a(1)=df
ctwk=1.
call twkfreq1(ctwk,80,fs,a,ctwk2)
do istart=0,315
call sync4d(cd2,istart,ctwk2,1,sync)
if(sync.gt.smax) then
smax=sync
ibest=istart
dfbest=df
endif
enddo
enddo
f0=f0+dfbest
!f0=1443.75
call ft4_downsample(iwave,f0,cb) ! downsample from 320s/Symbol to 20s/Symbol
sum2=sum(abs(cb)**2)/(20.0*76)
if(sum2.gt.0.0) cb=cb/sqrt(sum2)
!ibest=208
cd=cb(ibest:ibest+76*20-1)
do k=1,NN
i1=(k-1)*20
csymb=cd(i1:i1+19)
call four2a(csymb,20,1,-1,1)
cs(0:3,k)=csymb(1:4)/1e2
s4(0:3,k)=abs(csymb(1:4))
enddo
! sync quality check
is1=0
is2=0
is3=0
do k=1,4
ip=maxloc(s4(:,k))
if(icos4(k-1).eq.(ip(1)-1)) is1=is1+1
ip=maxloc(s4(:,k+36))
if(icos4(k-1).eq.(ip(1)-1)) is2=is2+1
ip=maxloc(s4(:,k+72))
if(icos4(k-1).eq.(ip(1)-1)) is3=is3+1
enddo
! hard sync sum - max is 12
nsync=is1+is2+is3
do nseq=1,3
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,76,nsym
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.152) 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 normalizebmet(bmeta,152)
call normalizebmet(bmetb,152)
call normalizebmet(bmetc,152)
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( 73: 80).eq.(/0,0,0,1,1,0,1,1/))
ns3=count(hbits(145:152).eq.(/0,0,0,1,1,0,1,1/))
nsync_qual=ns1+ns2+ns3
sigma=0.7
llra(1:64)=bmeta(9:72)
llra(65:128)=bmeta(81:144)
llra=2*llra/sigma**2
llrb(1:64)=bmetb(9:72)
llrb(65:128)=bmetb(81:144)
llrb=2*llrb/sigma**2
llrc(1:64)=bmetc(9:72)
llrc(65:128)=bmetc(81:144)
llrc=2*llrc/sigma**2
do isd=1,3
if(isd.eq.1) llr=llra
if(isd.eq.2) llr=llrb
if(isd.eq.3) llr=llrc
apmask=0
max_iterations=40
do ibias=0,0
llr2=llr
if(ibias.eq.1) llr2=llr+0.4
if(ibias.eq.2) llr2=llr-0.4
call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations)
if(nharderror.ge.0) exit
enddo
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
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(idupe.eq.1) cycle
ndecodes=ndecodes+1
decodes(ndecodes)=message
nsnr=nint(xsnr)
write(*,1212) datetime(8:11),nsnr,ibest/750.0,f0,message,'*',nharderror,nsync_qual,isd,niterations
1212 format(a4,i4,2x,f5.3,f11.1,2x,a22,a1,i5,i5,i5,i5)
endif
enddo ! sequence estimation
enddo !candidate list
enddo !files
write(*,1120)
1120 format("<DecodeFinished>")
999 end program ft4d
subroutine getbitmetric(ib,ps,ns,xmet)
real ps(0:ns-1)
xm1=0
xm0=0
do i=0,ns-1
if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i)
if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i)
enddo
xmet=xm1-xm0
return
end subroutine getbitmetric
subroutine downsample4(ci,f0,co)
parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10
complex ci(0:NI-1),ct(0:NI-1)
complex co(0:NO-1)
fs=12000.0
df=fs/NI
ct=ci
call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain
i0=nint(f0/df)
ct=cshift(ct,i0)
co=0.0
co(0)=ct(0)
b=8.0
do i=1,NO/2
arg=(i*df/b)**2
filt=exp(-arg)
co(i)=ct(i)*filt
co(NO-i)=ct(NI-i)*filt
enddo
co=co/NO
call four2a(co,NO,1,1,1) !c2c FFT back to time domain
return
end subroutine downsample4
subroutine ft4_downsample(iwave,f0,c)
! Input: i*2 data in iwave() at sample rate 12000 Hz
! Output: Complex data in c(), sampled at 1200 Hz
include 'ft4_params.f90'
parameter (NFFT2=NMAX/16)
integer*2 iwave(NMAX)
complex c(0:NMAX/16-1)
complex c1(0:NFFT2-1)
complex cx(0:NMAX/2)
real x(NMAX)
equivalence (x,cx)
BW=6.0*75
df=12000.0/NMAX
x=iwave
call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain
ibw=nint(BW/df)
i0=nint(f0/df)
c1=0.
c1(0)=cx(i0)
do i=1,NFFT2/2
arg=(i-1)*df/bw
win=exp(-arg*arg)
c1(i)=cx(i0+i)*win
c1(NFFT2-i)=cx(i0-i)*win
enddo
c1=c1/NFFT2
call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain
c=c1(0:NMAX/16-1)
return
end subroutine ft4_downsample

86
lib/fsk4hf/genft2.f90 Normal file
View File

@ -0,0 +1,86 @@
subroutine genft2(msg0,ichk,msgsent,i4tone,itype)
! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration)
!
! Encode an MSK144 message
! Input:
! - msg0 requested message to be transmitted
! - ichk if ichk=1, return only msgsent
! if ichk.ge.10000, set imsg=ichk-10000 for short msg
! - msgsent message as it will be decoded
! - i4tone array of audio tone values, 0 or 1
! - itype message type
! 1 = 77 bit message
! 7 = 16 bit message "<Call_1 Call2> Rpt"
use iso_c_binding, only: c_loc,c_size_t
use packjt77
character*37 msg0
character*37 message !Message to be generated
character*37 msgsent !Message as it will be received
character*77 c77
integer*4 i4tone(144)
integer*1 codeword(128)
integer*1 msgbits(77)
integer*1 bitseq(144) !Tone #s, data and sync (values 0-1)
integer*1 s16(16)
real*8 xi(864),xq(864),pi,twopi
data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/
equivalence (ihash,i1hash)
logical unpk77_success
nsym=128
pi=4.0*atan(1.0)
twopi=8.*atan(1.0)
message(1:37)=' '
itype=1
if(msg0(1:1).eq.'@') then !Generate a fixed tone
read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency
go to 2
1 nfreq=1000
2 i4tone(1)=nfreq
else
message=msg0
do i=1, 37
if(ichar(message(i:i)).eq.0) then
message(i:37)=' '
exit
endif
enddo
do i=1,37 !Strip leading blanks
if(message(1:1).ne.' ') exit
message=message(i+1:)
enddo
if(message(1:1).eq.'<') then
i2=index(message,'>')
i1=0
if(i2.gt.0) i1=index(message(1:i2),' ')
if(i1.gt.0) then
call genmsk40(message,msgsent,ichk,i4tone,itype)
if(itype.lt.0) go to 999
i4tone(41)=-40
go to 999
endif
endif
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent
if(ichk.eq.1) go to 999
read(c77,"(77i1)") msgbits
call encode_128_90(msgbits,codeword)
!Create 144-bit channel vector:
bitseq=0
bitseq(1:16)=s16
bitseq(17:144)=codeword
i4tone=bitseq
endif
999 return
end subroutine genft2

View File

@ -0,0 +1,63 @@
subroutine getcandidates2(id,fa,fb,syncmin,nfqso,maxcand,savg,candidate, &
ncand,sbase)
! For now, hardwired to find the largest peak in the average spectrum
include 'ft2_params.f90'
real s(NH1,NHSYM)
real savg(NH1),savsm(NH1)
real sbase(NH1)
real x(NFFT1)
complex cx(0:NH1)
real candidate(3,maxcand)
integer*2 id(NMAX)
integer*1 s8(8)
integer indx(NH1)
data s8/0,1,1,1,0,0,1,0/
equivalence (x,cx)
! Compute symbol spectra, stepping by NSTEP steps.
savg=0.
tstep=NSTEP/12000.0
df=12000.0/NFFT1 !3.125 Hz
fac=1.0/300.0
do j=1,NHSYM
ia=(j-1)*NSTEP + 1
ib=ia+NSPS-1
x(1:NSPS)=fac*id(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
savsm=0.
do i=2,NH1-1
savsm(i)=sum(savg(i-1:i+1))/3.
enddo
nfa=fa/df
nfb=fb/df
np=nfb-nfa+1
indx=0
call indexx(savsm(nfa:nfb),np,indx)
xn=savsm(nfa+indx(nint(0.3*np)))
savsm=savsm/xn
imax=-1
xmax=-99.
do i=2,NH1-1
if(savsm(i).gt.savsm(i-1).and. &
savsm(i).gt.savsm(i+1).and. &
savsm(i).gt.xmax) then
xmax=savsm(i)
imax=i
endif
enddo
f0=imax*df
if(xmax.gt.1.2) then
ncand=ncand+1
candidate(1,ncand)=f0
endif
return
end subroutine getcandidates2

View File

@ -1,194 +0,0 @@
program msksim
! Simulate characteristics of a potential "MSK10" mode using LDPC (168,84)
! code, OQPDK modulation, and 30 s T/R sequences.
! Reception and Demodulation algorithm:
! 1. Compute coarse spectrum; find fc1 = approx carrier freq
! 2. Mix from fc1 to 0; LPF at +/- 0.75*R
! 3. Square, FFT; find peaks near -R/2 and +R/2 to get fc2
! 4. Mix from fc2 to 0
! 5. Fit cb13 (central part of csync) to c -> lag, phase
! 6. Fit complex ploynomial for channel equalization
! 7. Get soft bits from equalized data
parameter (KK=84) !Information bits (72 + CRC12)
parameter (ND=168) !Data symbols: LDPC (168,84), r=1/2
parameter (NS=65) !Sync symbols (2 x 26 + Barker 13)
parameter (NR=3) !Ramp up/down
parameter (NN=NR+NS+ND) !Total symbols (236)
parameter (NSPS=1152/72) !Samples per MSK symbol (16)
parameter (N2=2*NSPS) !Samples per OQPSK symbol (32)
parameter (N13=13*N2) !Samples in central sync vector (416)
parameter (NZ=NSPS*NN) !Samples in baseband waveform (3776)
parameter (NFFT1=4*NSPS,NH1=NFFT1/2)
character*8 arg
complex cbb(0:NZ-1) !Complex baseband waveform
complex csync(0:NZ-1) !Sync symbols only, from cbb
complex cb13(0:N13-1) !Barker 13 waveform
complex c(0:NZ-1) !Complex waveform
complex c0(0:NZ-1) !Complex waveform
complex zz(NS+ND) !Complex symbol values (intermediate)
complex z
real xnoise(0:NZ-1) !Generated random noise
real ynoise(0:NZ-1) !Generated random noise
real rxdata(ND),llr(ND) !Soft symbols
real pp(2*NSPS) !Shaped pulse for OQPSK
real a(5) !For twkfreq1
real aa(20),bb(20) !Fitted polyco's
integer id(NS+ND) !NRZ values (+/-1) for Sync and Data
integer ierror(NS+ND)
integer icw(NN)
integer*1 msgbits(KK),decoded(KK),apmask(ND),cw(ND)
! integer*1 codeword(ND)
data msgbits/0,0,1,0,0,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,1, &
1,1,1,0,1,1,1,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,0,1,1,0,1,1, &
1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0/
nargs=iargc()
if(nargs.ne.6) then
print*,'Usage: mskhfsim f0(Hz) delay(ms) fspread(Hz) maxn iters snr(dB)'
print*,'Example: mskhfsim 0 0 0 5 10 -20'
print*,'Set snr=0 to cycle through a range'
go to 999
endif
call getarg(1,arg)
read(arg,*) f0 !Generated carrier frequency
call getarg(2,arg)
read(arg,*) delay !Delta_t (ms) for Watterson model
call getarg(3,arg)
read(arg,*) fspread !Fspread (Hz) for Watterson model
call getarg(4,arg)
read(arg,*) maxn !Max nterms for polyfit
call getarg(5,arg)
read(arg,*) iters !Iterations at each SNR
call getarg(6,arg)
read(arg,*) snrdb !Specified SNR_2500
twopi=8.0*atan(1.0)
fs=12000.0/72.0 !Sample rate = 166.6666667 Hz
dt=1.0/fs !Sample interval (s)
tt=NSPS*dt !Duration of "itone" symbols (s)
ts=2*NSPS*dt !Duration of OQPSK symbols (s)
baud=1.0/tt !Keying rate for "itone" symbols (baud)
txt=NZ*dt !Transmission length (s)
bandwidth_ratio=2500.0/(fs/2.0)
write(*,1000) f0,delay,fspread,maxn,iters,baud,3*baud,txt
1000 format('f0:',f5.1,' Delay:',f4.1,' fSpread:',f5.2,' maxn:',i3, &
' Iters:',i6/'Baud:',f7.3,' BW:',f5.1,' TxT:',f5.1,f5.2/)
write(*,1004)
1004 format(/' SNR err ber fer fsigma'/37('-'))
do i=1,N2 !Half-sine pulse shape
pp(i)=sin(0.5*(i-1)*twopi/(2*NSPS))
enddo
call genmskhf(msgbits,id,icw,cbb,csync)!Generate baseband waveform and csync
cb13=csync(1680:2095) !Copy the Barker 13 waveform
a=0.
a(1)=f0
call twkfreq1(cbb,NZ,fs,a,cbb) !Mix to specified frequency
isna=-10
isnb=-30
if(snrdb.ne.0.0) then
isna=nint(snrdb)
isnb=isna
endif
do isnr=isna,isnb,-1 !Loop over SNR range
snrdb=isnr
sig=sqrt(bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
nhard=0
nhardsync=0
nfe=0
sqf=0.
do iter=1,iters !Loop over requested iterations
c=cbb
if(delay.ne.0.0 .or. fspread.ne.0.0) then
call watterson(c,NZ,fs,delay,fspread)
endif
c=sig*c !Scale to requested SNR
if(snrdb.lt.90) then
do i=0,NZ-1 !Generate gaussian noise
xnoise(i)=gran()
ynoise(i)=gran()
enddo
c=c + cmplx(xnoise,ynoise) !Add AWGN noise
endif
call getfc1(c,fc1) !First approx for freq
call getfc2(c,csync,fc1,fc2,fc3) !Refined freq
sqf=sqf + (fc1+fc2-f0)**2
!NB: Measured performance is about equally good using fc2 or fc3 here:
a(1)=-(fc1+fc2)
a(2:5)=0.
call twkfreq1(c,NZ,fs,a,c) !Mix c down by fc1+fc2
! The following may not be necessary?
! z=sum(c(1680:2095)*cb13)/208.0 !Get phase from Barker 13 vector
! z0=z/abs(z)
! c=c*conjg(z0)
!---------------------------------------------------------------- DT
! Not presently used:
amax=0.
jpk=0
do j=-20*NSPS,20*NSPS !Get jpk
z=sum(c(1680+j:2095+j)*cb13)/208.0
if(abs(z).gt.amax) then
amax=abs(z)
jpk=j
endif
enddo
xdt=jpk/fs
nterms=maxn
c0=c
do itry=1,10
idf=itry/2
if(mod(itry,2).eq.0) idf=-idf
nhard0=0
nhardsync0=0
ifer=1
a(1)=idf*0.01
a(2:5)=0.
call twkfreq1(c0,NZ,fs,a,c) !Mix c0 into c
call cpolyfit(c,pp,id,maxn,aa,bb,zz,nhs)
call msksoftsym(zz,aa,bb,id,nterms,ierror,rxdata,nhard0,nhardsync0)
if(nhardsync0.gt.12) cycle
rxav=sum(rxdata)/ND
rx2av=sum(rxdata*rxdata)/ND
rxsig=sqrt(rx2av-rxav*rxav)
rxdata=rxdata/rxsig
ss=0.84
llr=2.0*rxdata/(ss*ss)
apmask=0
max_iterations=40
ifer=0
call bpdecode168(llr,apmask,max_iterations,decoded,niterations,cw)
nbadcrc=0
if(niterations.ge.0) call chkcrc12(decoded,nbadcrc)
if(niterations.lt.0 .or. count(msgbits.ne.decoded).gt.0 .or. &
nbadcrc.ne.0) ifer=1
! if(ifer.eq.0) write(67,1301) snrdb,itry,idf,niterations, &
! nhardsync0,nhard0
!1301 format(f6.1,5i6)
if(ifer.eq.0) exit
enddo !Freq dither loop
nhard=nhard+nhard0
nhardsync=nharsdync+nhardsync0
nfe=nfe+ifer
enddo
fsigma=sqrt(sqf/iters)
ber=float(nhard)/((NS+ND)*iters)
fer=float(nfe)/iters
write(*,1050) snrdb,nhard,ber,fer,fsigma
! write(60,1050) snrdb,nhard,ber,fer,fsigma
1050 format(f6.1,i7,f8.4,f7.3,f8.2)
enddo
999 end program msksim

View File

@ -70,14 +70,14 @@ endfunction
# M-ary PSK Block Coded Modulation," Igal Sason and Gil Weichman, # M-ary PSK Block Coded Modulation," Igal Sason and Gil Weichman,
# doi: 10.1109/EEEI.2006.321097 # doi: 10.1109/EEEI.2006.321097
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
N=174 N=128
K=75 K=90
R=K/N R=K/N
delta=0.01; delta=0.01;
[ths,fval,info,output]=fzero(@f1,[delta,pi/2-delta], optimset ("jacobian", "off")); [ths,fval,info,output]=fzero(@f1,[delta,pi/2-delta], optimset ("jacobian", "off"));
for ebnodb=-6:0.5:4 for ebnodb=-3:0.5:4
ebno=10^(ebnodb/10.0); ebno=10^(ebnodb/10.0);
esno=ebno*R; esno=ebno*R;
A=sqrt(2*esno); A=sqrt(2*esno);

19
lib/fsk4hf/spb_128_90.dat Normal file
View File

@ -0,0 +1,19 @@
N = 128
K = 90
R = 0.70312
-3.000000 0.000341
-2.500000 0.001513
-2.000000 0.006049
-1.500000 0.021280
-1.000000 0.064283
-0.500000 0.162755
0.000000 0.338430
0.500000 0.571867
1.000000 0.791634
1.500000 0.930284
2.000000 0.985385
2.500000 0.998258
3.000000 0.999893
3.500000 0.999997
4.000000 1.000000

6
lib/ft2/cdatetime.f90 Normal file
View File

@ -0,0 +1,6 @@
character*17 function cdatetime()
character cdate*8,ctime*10
call date_and_time(cdate,ctime)
cdatetime=cdate(3:8)//'_'//ctime
return
end function cdatetime

279
lib/ft2/ft2.f90 Normal file
View File

@ -0,0 +1,279 @@
program ft2
use packjt77
include 'gcom1.f90'
integer ft2audio,ptt
logical allok
character*20 pttport
character*8 arg
character*80 fname
integer*2 id2(30000)
open(12,file='all_ft2.txt',status='unknown',position='append')
nargs=iargc()
if(nargs.eq.1) then
call getarg(1,fname)
open(10,file=fname,status='old',access='stream')
read(10) id2(1:22) !Read (and ignore) the header
read(10) id2 !Read the Rx data
close(10)
call ft2_decode(fname(1:17),nfqso,id2,ndecodes,mycall,hiscall,nrx)
go to 999
endif
allok=.true.
! Get home-station details
open(10,file='ft2.ini',status='old',err=1)
go to 2
1 print*,'Cannot open ft2.ini'
allok=.false.
2 read(10,*,err=3) mycall,mygrid,ndevin,ndevout,pttport,exch
go to 4
3 print*,'Error reading ft2.ini'
allok=.false.
4 if(index(pttport,'/').lt.1) read(pttport,*) nport
hiscall=' '
hiscall_next=' '
idevin=ndevin
idevout=ndevout
call padevsub(idevin,idevout)
if(idevin.ne.ndevin .or. idevout.ne.ndevout) allok=.false.
i1=0
i1=ptt(nport,1,1,iptt)
i1=ptt(nport,1,0,iptt)
if(i1.lt.0 .and. nport.ne.0) allok=.false.
if(.not.allok) then
write(*,"('Please fix setup error(s) and restart.')")
go to 999
endif
nright=1
iwrite=0
iwave=0
nwave=NTZ
nfsample=12000
ngo=1
npabuf=1152
ntxok=0
ntransmitting=0
tx_once=.false.
snrdb=99.0
txmsg='CQ K1JT FN20'
ltx=.false.
lrx=.false.
autoseq=.false.
QSO_in_progress=.false.
ntxed=0
if(nargs.eq.3) then
call getarg(1,txmsg)
call getarg(2,arg)
read(arg,*) f0
call getarg(3,arg)
read(arg,*) snrdb
tx_once=.true.
ftx=1500.0
call transmit(-1,ftx,iptt)
snrdb=99.0
endif
! Start the audio streams
ierr=ft2audio(idevin,idevout,npabuf,nright,y1,y2,NRING,iwrite,itx, &
iwave,nwave+3*1152,nfsample,nTxOK,nTransmitting,ngo)
if(ierr.ne.0) then
print*,'Error',ierr,' starting audio input and/or output.'
endif
999 end program ft2
subroutine update(total_time,ic1,ic2)
use wavhdr
type(hdr) h
real*8 total_time
integer*8 count0,count1,clkfreq
integer ptt
integer*2 id(30000)
logical transmitted,level,ok
character*70 line
character cdatetime*17,fname*17,mode*8,band*6
include 'gcom1.f90'
data nt0/-1/,transmitted/.false./,snr/-99.0/
data level/.false./
save nt0,transmitted,level,snr,iptt
if(ic1.ne.0 .or. ic2.ne.0) then
if(ic1.eq.27 .and. ic2.eq.0) ngo=0 !ESC
if(nTxOK.eq.0 .and. ntransmitting.eq.0) then
nfunc=0
if(ic1.eq.0 .and. ic2.eq.59) nfunc=1 !F1
if(ic1.eq.0 .and. ic2.eq.60) nfunc=2 !F2
if(ic1.eq.0 .and. ic2.eq.61) nfunc=3 !F3
if(ic1.eq.0 .and. ic2.eq.62) nfunc=4 !F4
if(ic1.eq.0 .and. ic2.eq.63) nfunc=5 !F5
if(nfunc.eq.1 .or. (nfunc.ge.2 .and. hiscall.ne.' ')) then
ftx=1500.0
call transmit(nfunc,ftx,iptt)
endif
endif
if(ic1.eq.13 .and. ic2.eq.0) hiscall=hiscall_next
if((ic1.eq.97 .or. ic1.eq.65) .and. ic2.eq.0) autoseq=.not.autoseq
if((ic1.eq.108 .or. ic1.eq.76) .and. ic2.eq.0) level=.not.level
endif
if(ntransmitting.eq.1) transmitted=.true.
if(transmitted .and. ntransmitting.eq.0) then
i1=0
if(iptt.eq.1 .and. nport.gt.0) i1=ptt(nport,0,1,iptt)
if(tx_once .and. transmitted) stop
transmitted=.false.
endif
nt=2*total_time
if(nt.gt.nt0 .or. ic1.ne.0 .or. ic2.ne.0) then
if(level) then
! Measure and display the average level of signal plus noise in past 0.5 s
k=iwrite-6000
if(k.lt.1) k=k+NRING
sq=0.
do i=1,6000
k=k+1
if(k.gt.NRING) k=k-NRING
x=y1(k)
sq=sq + x*x
enddo
sigdb=0.
if(sq.gt.0.0) sigdb=db(sq/6000.0)
n=sigdb
if(n.lt.1) n=1
if(n.gt.70) n=70
line=' '
line(n:n)='*'
write(*,1030) sigdb,ntxed,autoseq,QSO_in_progress,(line(i:i),i=1,n)
1030 format(f4.1,i3,2L2,1x,70a1)
! write(*,1020) nt,total_time,iwrite,itx,ntxok,ntransmitting,ndecodes, &
! snr,sigdb,line
!1020 format(i6,f9.3,i10,i6,3i3,f6.0,f6.1,1x,a30)
endif
k=iwrite-30000
if(k.lt.1) k=k+NRING
do i=1,30000
k=k+1
if(k.gt.NRING) k=k-NRING
id(i)=y1(k)
enddo
nutc=0
nfqso=1500
ndecodes=0
if(maxval(abs(id)).gt.0) then
call system_clock(count0,clkfreq)
nrx=-1
call ft2_decode(cdatetime(),nfqso,id,ndecodes,mycall,hiscall,nrx)
call system_clock(count1,clkfreq)
! tdecode=float(count1-count0)/float(clkfreq)
if(ndecodes.ge.1) then
fMHz=7.074
mode='FT2'
nsubmode=1
ntrperiod=0
h=default_header(12000,30000)
k=0
do i=1,250
sq=0
do n=1,120
k=k+1
x=id(k)
sq=sq + x*x
enddo
write(43,3043) i,0.01*i,1.e-4*sq
3043 format(i7,f12.6,f12.3)
enddo
call set_wsjtx_wav_params(fMHz,mode,nsubmode,ntrperiod,id)
band=""
mode=""
nsubmode=-1
ntrperiod=-1
call get_wsjtx_wav_params(id,band,mode,nsubmode,ntrperiod,ok)
! write(*,1010) band,ntrperiod,mode,char(ichar('A')-1+id(3))
!1010 format('Band: ',a6,' T/R period:',i4,' Mode: ',a8,1x,a1)
fname=cdatetime()
fname(14:17)='.wav'
open(13,file=fname,status='unknown',access='stream')
write(13) h,id
close(13)
endif
if(autoseq .and.nrx.eq.2) QSO_in_progress=.true.
if(autoseq .and. QSO_in_progress .and. nrx.ge.1 .and. nrx.le.4) then
lrx(nrx)=.true.
ftx=1500.0
if(ntxed.eq.1) then
if(nrx.eq.2) then
call transmit(3,ftx,iptt)
else
call transmit(1,ftx,iptt)
endif
endif
if(ntxed.eq.2) then
if(nrx.eq.3) then
call transmit(4,ftx,iptt)
QSO_in_progress=.false.
write(*,1032)
1032 format('QSO complete: S+P side')
else
call transmit(2,ftx,iptt)
endif
endif
if(ntxed.eq.3) then
if(nrx.eq.4) then
QSO_in_progress=.false.
write(*,1034)
1034 format('QSO complete: CQ side')
else
call transmit(3,ftx,iptt)
endif
endif
endif
endif
nt0=nt
endif
return
end subroutine update
character*17 function cdatetime()
character cdate*8,ctime*10
call date_and_time(cdate,ctime)
cdatetime=cdate(3:8)//'_'//ctime
return
end function cdatetime
subroutine transmit(nfunc,ftx,iptt)
include 'gcom1.f90'
character*17 cdatetime
integer ptt
if(nTxOK.eq.1) return
if(nfunc.eq.1) txmsg='CQ '//trim(mycall)//' '//mygrid
if(nfunc.eq.2) txmsg=trim(hiscall)//' '//trim(mycall)// &
' 559 '//trim(exch)
if(nfunc.eq.3) txmsg=trim(hiscall)//' '//trim(mycall)// &
' R 559 '//trim(exch)
if(nfunc.eq.4) txmsg=trim(hiscall)//' '//trim(mycall)//' RR73'
if(nfunc.eq.5) txmsg='TNX 73 GL'
call ft2_iwave(txmsg,ftx,snrdb,iwave)
iwave(23041:)=0
i1=ptt(nport,1,1,iptt)
ntxok=1
n=len(trim(txmsg))
write(*,1010) cdatetime(),0,0.0,nint(ftx),(txmsg(i:i),i=1,n)
write(12,1010) cdatetime(),0,0.0,nint(ftx),(txmsg(i:i),i=1,n)
1010 format(a17,i4,f6.2,i5,' Tx ',37a1)
if(nfunc.ge.1 .and. nfunc.le.4) ntxed=nfunc
if(nfunc.ge.1 .and. nfunc.le.5) ltx(nfunc)=.true.
if(nfunc.eq.2 .or. nfunc.eq.3) QSO_in_progress=.true.
return
end subroutine transmit

2
lib/ft2/ft2.ini Normal file
View File

@ -0,0 +1,2 @@
K1JT FN20 1 5 0 NJ
MyCall MyGrid AudioIn AudioOut PTTport Exch

298
lib/ft2/ft2_decode.f90 Normal file
View File

@ -0,0 +1,298 @@
subroutine ft2_decode(cdatetime0,nfqso,iwave,ndecodes,mycall,hiscall,nrx,line)
use crc
use packjt77
include 'ft2_params.f90'
character message*37,c77*77
character*61 line
character*37 decodes(100)
character*120 data_dir
character*17 cdatetime0,cdatetime
character*6 mycall,hiscall,hhmmss
complex c2(0:NMAX/16-1) !Complex waveform
complex cb(0:NMAX/16-1)
complex cd(0:144*10-1) !Complex waveform
complex c1(0:9),c0(0:9)
complex ccor(0:1,144)
complex csum,cterm,cc0,cc1,csync1
real*8 fMHz
real a(5)
real rxdata(128),llr(128) !Soft symbols
real llr2(128)
real sbits(144),sbits1(144),sbits3(144)
real ps(0:8191),psbest(0:8191)
real candidate(3,100)
real savg(NH1)
integer*2 iwave(NMAX) !Generated full-length waveform
integer*1 message77(77),apmask(128),cw(128)
integer*1 hbits(144),hbits1(144),hbits3(144)
integer*1 s16(16)
logical unpk77_success
data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/
hhmmss=cdatetime0(8:13)
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)
twopi=8.0*atan(1.0)
h=0.8 !h=0.8 seems to be optimum for AWGN sensitivity (not for fading)
dphi=twopi/2*baud*h*dt*16 ! dt*16 is samp interval after downsample
dphi0=-1*dphi
dphi1=+1*dphi
phi0=0.0
phi1=0.0
do i=0,9
c1(i)=cmplx(cos(phi1),sin(phi1))
c0(i)=cmplx(cos(phi0),sin(phi0))
phi1=mod(phi1+dphi1,twopi)
phi0=mod(phi0+dphi0,twopi)
enddo
the=twopi*h/2.0
cc1=cmplx(cos(the),-sin(the))
cc0=cmplx(cos(the),sin(the))
data_dir="."
fMHz=7.074
ncoh=1
candidate=0.0
ncand=0
fa=375.0
fb=3000.0
syncmin=0.2
maxcand=100
nfqso=-1
call getcandidates2a(iwave,fa,fb,maxcand,savg,candidate,ncand)
ndecodes=0
do icand=1,ncand
f0=candidate(1,icand)
if( f0.le.375.0 .or. f0.ge.(5000.0-375.0) ) cycle
call ft2_downsample(iwave,f0,c2) ! downsample from 160s/Symbol to 10s/Symbol
! 750 samples/second here
ibest=-1
sybest=-99.
dfbest=-1.
!### do if=-15,+15
do if=-30,30
df=if
a=0.
a(1)=-df
call twkfreq1(c2,NMAX/16,fs,a,cb)
do is=0,374 !DT search range is 0 - 0.5 s
csync1=0.
cterm=1
do ib=1,16
i1=(ib-1)*10+is
i2=i1+136*10
if(s16(ib).eq.1) then
csync1=csync1+sum(cb(i1:i1+9)*conjg(c1(0:9)))*cterm
cterm=cterm*cc1
else
csync1=csync1+sum(cb(i1:i1+9)*conjg(c0(0:9)))*cterm
cterm=cterm*cc0
endif
enddo
if(abs(csync1).gt.sybest) then
ibest=is
sybest=abs(csync1)
dfbest=df
endif
enddo
enddo
a=0.
a(1)=-dfbest
call twkfreq1(c2,NMAX/16,fs,a,cb)
ib=ibest
cd=cb(ib:ib+144*10-1)
s2=sum(real(cd*conjg(cd)))/(10*144)
cd=cd/sqrt(s2)
do nseq=1,5
if( nseq.eq.1 ) then ! noncoherent single-symbol detection
sbits1=0.0
do ibit=1,144
ib=(ibit-1)*10
ccor(1,ibit)=sum(cd(ib:ib+9)*conjg(c1(0:9)))
ccor(0,ibit)=sum(cd(ib:ib+9)*conjg(c0(0:9)))
sbits1(ibit)=abs(ccor(1,ibit))-abs(ccor(0,ibit))
hbits1(ibit)=0
if(sbits1(ibit).gt.0) hbits1(ibit)=1
enddo
sbits=sbits1
hbits=hbits1
sbits3=sbits1
hbits3=hbits1
elseif( nseq.ge.2 ) then
nbit=2*nseq-1
numseq=2**(nbit)
ps=0
do ibit=nbit/2+1,144-nbit/2
ps=0.0
pmax=0.0
do iseq=0,numseq-1
csum=0.0
cterm=1.0
k=1
do i=nbit-1,0,-1
ibb=iand(iseq/(2**i),1)
csum=csum+ccor(ibb,ibit-(nbit/2+1)+k)*cterm
if(ibb.eq.0) cterm=cterm*cc0
if(ibb.eq.1) cterm=cterm*cc1
k=k+1
enddo
ps(iseq)=abs(csum)
if( ps(iseq) .gt. pmax ) then
pmax=ps(iseq)
ibflag=1
endif
enddo
if( ibflag .eq. 1 ) then
psbest=ps
ibflag=0
endif
call getbitmetric(2**(nbit/2),psbest,numseq,sbits3(ibit))
hbits3(ibit)=0
if(sbits3(ibit).gt.0) hbits3(ibit)=1
enddo
sbits=sbits3
hbits=hbits3
endif
nsync_qual=count(hbits(1:16).eq.s16)
if(nsync_qual.lt.10) exit
rxdata=sbits(17:144)
rxav=sum(rxdata(1:128))/128.0
rx2av=sum(rxdata(1:128)*rxdata(1:128))/128.0
rxsig=sqrt(rx2av-rxav*rxav)
rxdata=rxdata/rxsig
sigma=0.80
llr(1:128)=2*rxdata/(sigma*sigma)
apmask=0
max_iterations=40
do ibias=0,0
llr2=llr
if(ibias.eq.1) llr2=llr+0.4
if(ibias.eq.2) llr2=llr-0.4
call bpdecode128_90(llr2,apmask,max_iterations,message77,cw,nharderror,niterations)
if(nharderror.ge.0) exit
enddo
nhardmin=-1
if(sum(message77).eq.0) cycle
if( nharderror.ge.0 ) then
write(c77,'(77i1)') message77(1:77)
call unpack77(c77,nrx,message,unpk77_success)
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
xsnr=db(sybest*sybest) - 115.0 !### Rough estimate of S/N ###
nsnr=nint(xsnr)
freq=f0+dfbest
write(line,1000) hhmmss,nsnr,ibest/750.0,nint(freq),message
1000 format(a6,i4,f5.2,i5,' + ',1x,a37)
open(24,file='all_ft2.txt',status='unknown',position='append')
write(24,1002) cdatetime0,nsnr,ibest/750.0,nint(freq),message, &
nseq,nharderror,nhardmin
if(hhmmss.eq.' ') write(*,1002) cdatetime0,nsnr, &
ibest/750.0,nint(freq),message,nseq,nharderror,nhardmin
1002 format(a17,i4,f6.2,i5,' Rx ',a37,3i5)
close(24)
!### Temporary: assume most recent decoded message conveys "hiscall".
i0=index(message,' ')
if(i0.ge.3 .and. i0.le.7) then
hiscall=message(i0+1:i0+6)
i1=index(hiscall,' ')
if(i1.gt.0) hiscall=hiscall(1:i1)
endif
nrx=-1
if(index(message,'CQ ').eq.1) nrx=1
if((index(message,trim(mycall)//' ').eq.1) .and. &
(index(message,' '//trim(hiscall)//' ').ge.4)) then
if(index(message,' 559 ').gt.8) nrx=2
if(index(message,' R 559 ').gt.8) nrx=3
if(index(message,' RR73 ').gt.8) nrx=4
endif
!###
exit
endif
enddo ! nseq
enddo !candidate list
return
end subroutine ft2_decode
subroutine getbitmetric(ib,ps,ns,xmet)
real ps(0:ns-1)
xm1=0
xm0=0
do i=0,ns-1
if( iand(i/ib,1) .eq. 1 .and. ps(i) .gt. xm1 ) xm1=ps(i)
if( iand(i/ib,1) .eq. 0 .and. ps(i) .gt. xm0 ) xm0=ps(i)
enddo
xmet=xm1-xm0
return
end subroutine getbitmetric
subroutine downsample2(ci,f0,co)
parameter(NI=144*160,NH=NI/2,NO=NI/16) ! downsample from 200 samples per symbol to 10
complex ci(0:NI-1),ct(0:NI-1)
complex co(0:NO-1)
fs=12000.0
df=fs/NI
ct=ci
call four2a(ct,NI,1,-1,1) !c2c FFT to freq domain
i0=nint(f0/df)
ct=cshift(ct,i0)
co=0.0
co(0)=ct(0)
b=8.0
do i=1,NO/2
arg=(i*df/b)**2
filt=exp(-arg)
co(i)=ct(i)*filt
co(NO-i)=ct(NI-i)*filt
enddo
co=co/NO
call four2a(co,NO,1,1,1) !c2c FFT back to time domain
return
end subroutine downsample2
subroutine ft2_downsample(iwave,f0,c)
! Input: i*2 data in iwave() at sample rate 12000 Hz
! Output: Complex data in c(), sampled at 1200 Hz
include 'ft2_params.f90'
parameter (NFFT2=NMAX/16)
integer*2 iwave(NMAX)
complex c(0:NMAX/16-1)
complex c1(0:NFFT2-1)
complex cx(0:NMAX/2)
real x(NMAX)
equivalence (x,cx)
BW=4.0*75
df=12000.0/NMAX
x=iwave
call four2a(x,NMAX,1,-1,0) !r2c FFT to freq domain
ibw=nint(BW/df)
i0=nint(f0/df)
c1=0.
c1(0)=cx(i0)
do i=1,NFFT2/2
arg=(i-1)*df/bw
win=exp(-arg*arg)
c1(i)=cx(i0+i)*win
c1(NFFT2-i)=cx(i0-i)*win
enddo
c1=c1/NFFT2
call four2a(c1,NFFT2,1,1,1) !c2c FFT back to time domain
c=c1(0:NMAX/16-1)
return
end subroutine ft2_downsample

View File

@ -0,0 +1,88 @@
subroutine ft2_gfsk_iwave(msg37,f0,snrdb,iwave)
! Generate waveform for experimental "FT2" mode
use packjt77
include 'ft2_params.f90' !Set various constants
parameter (NWAVE=(NN+2)*NSPS)
character msg37*37,msgsent37*37
real wave(NWAVE),xnoise(NWAVE)
real dphi(NWAVE)
real pulse(480)
integer itone(NN)
integer*2 iwave(NWAVE) !Generated full-length waveform
logical first
data first/.true./
save pulse
twopi=8.0*atan(1.0)
fs=12000.0 !Sample rate (Hz)
dt=1.0/fs !Sample interval (s)
hmod=0.8 !Modulation index (MSK=0.5, FSK=1.0)
tt=NSPS*dt !Duration of symbols (s)
baud=1.0/tt !Keying rate (baud)
bw=1.5*baud !Occupied bandwidth (Hz)
txt=NZ*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
if(first) then
! The filtered frequency pulse
do i=1,480
tt=(i-240.5)/160.0
pulse(i)=gfsk_pulse(1.0,tt)
enddo
dphi_peak=twopi*(hmod/2.0)/real(NSPS)
first=.false.
endif
! Source-encode, then get itone():
itype=1
call genft2(msg37,0,msgsent37,itone,itype)
! Create the instantaneous frequency waveform
dphi=0.0
do j=1,NN
ib=(j-1)*160+1
ie=ib+480-1
dphi(ib:ie)=dphi(ib:ie)+dphi_peak*pulse*(2*itone(j)-1)
enddo
phi=0.0
wave=0.0
sqrt2=sqrt(2.)
dphi=dphi+twopi*f0*dt
do j=1,NWAVE
wave(j)=sqrt2*sin(phi)
sqsig=sqsig + wave(j)**2
phi=mod(phi+dphi(j),twopi)
enddo
wave(1:160)=wave(1:160)*(1.0-cos(twopi*(/(i,i=0,159)/)/320.0) )/2.0
wave(145*160+1:146*160)=wave(145*160+1:146*160)*(1.0+cos(twopi*(/(i,i=0,159)/)/320.0 ))/2.0
wave(146*160+1:)=0.
if(snrdb.gt.90.0) then
iwave=nint((32767.0/sqrt(2.0))*wave)
return
endif
sqnoise=1.e-30
if(snrdb.lt.90) then
do i=1,NWAVE !Add gaussian noise at specified SNR
xnoise(i)=gran() !Noise has rms = 1.0
enddo
endif
xnoise=xnoise*sqrt(0.5*fs/2500.0)
fac=30.0
snr_amplitude=10.0**(0.05*snrdb)
wave=fac*(snr_amplitude*wave + xnoise)
datpk=maxval(abs(wave))
print*,'A',snr_amplitude,datpk
iwave=nint((30000.0/datpk)*wave)
return
end subroutine ft2_gfsk_iwave

64
lib/ft2/ft2_iwave.f90 Normal file
View File

@ -0,0 +1,64 @@
subroutine ft2_iwave(msg37,f0,snrdb,iwave)
! Generate waveform for experimental "FT2" mode
use packjt77
include 'ft2_params.f90' !Set various constants
parameter (NWAVE=NN*NSPS)
character msg37*37,msgsent37*37
real wave(NWAVE),xnoise(NWAVE)
integer itone(NN)
integer*2 iwave(NWAVE) !Generated full-length waveform
twopi=8.0*atan(1.0)
fs=12000.0 !Sample rate (Hz)
dt=1.0/fs !Sample interval (s)
hmod=0.8 !Modulation index (MSK=0.5, FSK=1.0)
tt=NSPS*dt !Duration of symbols (s)
baud=1.0/tt !Keying rate (baud)
bw=1.5*baud !Occupied bandwidth (Hz)
txt=NZ*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():
itype=1
call genft2(msg37,0,msgsent37,itone,itype)
k=0
phi=0.0
sqsig=0.
do j=1,NN !Generate real waveform
dphi=twopi*(f0*dt+(hmod/2.0)*(2*itone(j)-1)/real(NSPS))
do i=1,NSPS
k=k+1
wave(k)=sqrt(2.0)*sin(phi) !Signal has rms = 1.0
sqsig=sqsig + wave(k)**2
phi=mod(phi+dphi,twopi)
enddo
enddo
if(snrdb.gt.90.0) then
iwave=nint((32767.0/sqrt(2.0))*wave)
return
endif
sqnoise=1.e-30
if(snrdb.lt.90) then
do i=1,NWAVE !Add gaussian noise at specified SNR
xnoise(i)=gran() !Noise has rms = 1.0
enddo
endif
xnoise=xnoise*sqrt(0.5*fs/2500.0)
fac=30.0
snr_amplitude=10.0**(0.05*snrdb)
wave=fac*(snr_amplitude*wave + xnoise)
datpk=maxval(abs(wave))
print*,'A',snr_amplitude,datpk
iwave=nint((30000.0/datpk)*wave)
return
end subroutine ft2_iwave

12
lib/ft2/ft2_params.f90 Normal file
View File

@ -0,0 +1,12 @@
! LDPC (128,90) code
parameter (KK=90) !Information bits (77 + CRC13)
parameter (ND=128) !Data symbols
parameter (NS=16) !Sync symbols (2x8)
parameter (NN=NS+ND) !Total channel symbols (144)
parameter (NSPS=160) !Samples per symbol at 12000 S/s
parameter (NZ=NSPS*NN) !Samples in full 1.92 s waveform (23040)
parameter (NMAX=30000) !Samples in iwave (2.5*12000)
parameter (NFFT1=400, NH1=NFFT1/2) !Length of FFTs for symbol spectra
parameter (NSTEP=NSPS/4) !Rough time-sync step size
parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps)
parameter (NDOWN=16) !Downsample factor

347
lib/ft2/ft2audio.c Normal file
View File

@ -0,0 +1,347 @@
#include <stdio.h>
#include "portaudio.h"
#include <string.h>
#include <time.h>
int iaa;
int icc;
double total_time=0.0;
// Definition of structure pointing to the audio data
typedef struct
{
int *iwrite;
int *itx;
int *TxOK;
int *Transmitting;
int *nwave;
int *nright;
int nring;
int nfs;
short *y1;
short *y2;
short *iwave;
} paTestData;
// Input callback routine:
static int
SoundIn( void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
paTestData *data = (paTestData*)userData;
short *in = (short*)inputBuffer;
unsigned int i;
static int ia=0;
if(*data->Transmitting) return 0;
if(statusFlags!=0) printf("Status flags %d\n",(int)statusFlags);
if((statusFlags&1) == 0) {
//increment buffer pointers only if data available
ia=*data->iwrite;
if(*data->nright==0) { //Use left channel for input
for(i=0; i<framesPerBuffer; i++) {
data->y1[ia] = (*in++);
data->y2[ia] = (*in++);
ia++;
}
} else { //Use right channel
for(i=0; i<framesPerBuffer; i++) {
data->y2[ia] = (*in++);
data->y1[ia] = (*in++);
ia++;
}
}
}
if(ia >= data->nring) ia=0; //Wrap buffer pointer if necessary
*data->iwrite = ia; //Save buffer pointer
iaa=ia;
total_time += (double)framesPerBuffer/12000.0;
// printf("iwrite: %d\n",*data->iwrite);
return 0;
}
// Output callback routine:
static int
SoundOut( void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
paTestData *data = (paTestData*)userData;
short *wptr = (short*)outputBuffer;
unsigned int i,n;
static short int n2;
static int ic=0;
static int TxOKz=0;
static clock_t tstart=-1;
static clock_t tend=-1;
static int nsent=0;
// printf("txOK: %d %d\n",TxOKz,*data->TxOK);
if(*data->TxOK && (!TxOKz)) ic=0; //Reset buffer pointer to start Tx
*data->Transmitting=*data->TxOK; //Set the "transmitting" flag
if(*data->TxOK) {
if(!TxOKz) {
// Start of a transmission
tstart=clock();
nsent=0;
// printf("Start Tx\n");
}
TxOKz=*data->TxOK;
for(i=0 ; i < framesPerBuffer; i++ ) {
n2=data->iwave[ic];
*wptr++ = n2; //left
*wptr++ = n2; //right
ic++;
if(ic > *data->nwave) {
*data->TxOK = 0;
*data->Transmitting = 0;
*data->iwrite = 0; //Reset Rx buffer pointer to 0
ic=0;
tend=clock();
double TxT=((double)(tend-tstart))/CLOCKS_PER_SEC;
// printf("End Tx, TxT = %f nSent = %d\n",TxT,nsent);
break;
}
}
nsent += framesPerBuffer;
} else {
memset((void*)outputBuffer, 0, 2*sizeof(short)*framesPerBuffer);
}
*data->itx = icc; //Save buffer pointer
icc=ic;
return 0;
}
/*******************************************************************/
int ft2audio_(int *ndevin, int *ndevout, int *npabuf, int *nright,
short y1[], short y2[], int *nring, int *iwrite,
int *itx, short iwave[], int *nwave, int *nfsample,
int *TxOK, int *Transmitting, int *ngo)
{
paTestData data;
PaStream *instream, *outstream;
PaStreamParameters inputParameters, outputParameters;
// PaStreamInfo *streamInfo;
int nfpb = *npabuf;
int nSampleRate = *nfsample;
int ndevice_in = *ndevin;
int ndevice_out = *ndevout;
double dSampleRate = (double) *nfsample;
PaError err_init, err_open_in, err_open_out, err_start_in, err_start_out;
PaError err = 0;
data.iwrite = iwrite;
data.itx = itx;
data.TxOK = TxOK;
data.Transmitting = Transmitting;
data.y1 = y1;
data.y2 = y2;
data.nring = *nring;
data.nright = nright;
data.nwave = nwave;
data.iwave = iwave;
data.nfs = nSampleRate;
err_init = Pa_Initialize(); // Initialize PortAudio
if(err_init) {
printf("Error initializing PortAudio.\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_init),
err_init);
Pa_Terminate(); // I don't think we need this but...
return(-1);
}
// printf("Opening device %d for input, %d for output...\n",
// ndevice_in,ndevice_out);
inputParameters.device = ndevice_in;
inputParameters.channelCount = 2;
inputParameters.sampleFormat = paInt16;
inputParameters.suggestedLatency = 0.2;
inputParameters.hostApiSpecificStreamInfo = NULL;
// Test if this configuration actually works, so we do not run into an
// ugly assertion
err_open_in = Pa_IsFormatSupported(&inputParameters, NULL, dSampleRate);
if (err_open_in == 0) {
err_open_in = Pa_OpenStream(
&instream, //address of stream
&inputParameters,
NULL,
dSampleRate, //Sample rate
nfpb, //Frames per buffer
paNoFlag,
(PaStreamCallback *)SoundIn, //Callback routine
(void *)&data); //address of data structure
if(err_open_in) { // We should have no error here usually
printf("Error opening input audio stream:\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_in),
err_open_in);
err = 1;
} else {
// printf("Successfully opened audio input.\n");
}
} else {
printf("Error opening input audio stream.\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_in),
err_open_in);
err = 1;
}
outputParameters.device = ndevice_out;
outputParameters.channelCount = 2;
outputParameters.sampleFormat = paInt16;
outputParameters.suggestedLatency = 0.2;
outputParameters.hostApiSpecificStreamInfo = NULL;
// Test if this configuration actually works, so we do not run into an
// ugly assertion.
err_open_out = Pa_IsFormatSupported(NULL, &outputParameters, dSampleRate);
if (err_open_out == 0) {
err_open_out = Pa_OpenStream(
&outstream, //address of stream
NULL,
&outputParameters,
dSampleRate, //Sample rate
nfpb, //Frames per buffer
paNoFlag,
(PaStreamCallback *)SoundOut, //Callback routine
(void *)&data); //address of data structure
if(err_open_out) { // We should have no error here usually
printf("Error opening output audio stream!\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_out),
err_open_out);
err += 2;
} else {
// printf("Successfully opened audio output.\n");
}
} else {
printf("Error opening output audio stream.\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_open_out),
err_open_out);
err += 2;
}
// if there was no error in opening both streams start them
if (err == 0) {
err_start_in = Pa_StartStream(instream); //Start input stream
if(err_start_in) {
printf("Error starting input audio stream!\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_start_in),
err_start_in);
err += 4;
}
err_start_out = Pa_StartStream(outstream); //Start output stream
if(err_start_out) {
printf("Error starting output audio stream!\n");
printf("\tErrortext: %s\n\tNumber: %d\n",Pa_GetErrorText(err_start_out),
err_start_out);
err += 8;
}
}
if (err == 0) printf("Audio streams running normally.\n******************************************************************\n");
while( Pa_IsStreamActive(instream) && (*ngo != 0) && (err == 0) ) {
int ic1=0;
int ic2=0;
if(_kbhit()) ic1 = _getch();
if(_kbhit()) ic2 = _getch();
// if(ic1!=0 || ic2!=0) printf("%d %d %d\n",iaa,ic1,ic2);
update_(&total_time,&ic1,&ic2);
Pa_Sleep(100);
}
Pa_AbortStream(instream); // Abort stream
Pa_CloseStream(instream); // Close stream, we're done.
Pa_AbortStream(outstream); // Abort stream
Pa_CloseStream(outstream); // Close stream, we're done.
Pa_Terminate();
return(err);
}
int padevsub_(int *idevin, int *idevout)
{
int numdev,ndefin,ndefout;
int nchin[101], nchout[101];
int i, devIdx;
int numDevices;
const PaDeviceInfo *pdi;
PaError err;
Pa_Initialize();
numDevices = Pa_GetDeviceCount();
numdev = numDevices;
if( numDevices < 0 ) {
err = numDevices;
Pa_Terminate();
return err;
}
if ((devIdx = Pa_GetDefaultInputDevice()) > 0) {
ndefin = devIdx;
} else {
ndefin = 0;
}
if ((devIdx = Pa_GetDefaultOutputDevice()) > 0) {
ndefout = devIdx;
} else {
ndefout = 0;
}
printf("\nAudio Input Output Device Name\n");
printf("Device Channels Channels\n");
printf("------------------------------------------------------------------\n");
for( i=0; i < numDevices; i++ ) {
pdi = Pa_GetDeviceInfo(i);
// if(i == Pa_GetDefaultInputDevice()) ndefin = i;
// if(i == Pa_GetDefaultOutputDevice()) ndefout = i;
nchin[i]=pdi->maxInputChannels;
nchout[i]=pdi->maxOutputChannels;
printf(" %2d %2d %2d %s\n",i,nchin[i],nchout[i],
pdi->name);
}
printf("\nUser requested devices: Input = %2d Output = %2d\n",
*idevin,*idevout);
printf("Default devices: Input = %2d Output = %2d\n",
ndefin,ndefout);
if((*idevin<0) || (*idevin>=numdev)) *idevin=ndefin;
if((*idevout<0) || (*idevout>=numdev)) *idevout=ndefout;
if((*idevin==0) && (*idevout==0)) {
*idevin=ndefin;
*idevout=ndefout;
}
printf("Will open devices: Input = %2d Output = %2d\n",
*idevin,*idevout);
Pa_Terminate();
return 0;
}

7
lib/ft2/g4.cmd Normal file
View File

@ -0,0 +1,7 @@
gcc -c ft2audio.c
gcc -c ptt.c
gfortran -c ../77bit/packjt77.f90
gfortran -c ../wavhdr.f90
gfortran -c ../crc.f90
gfortran -o ft2 -fbounds-check -fno-second-underscore -ffpe-trap=invalid,zero -Wall -Wno-conversion -Wno-character-truncation ft2.f90 ft2_iwave.f90 ft2_decode.f90 getcandidates2.f90 ft2audio.o ptt.o /JTSDK/wsjtx-output/qt55/2.1.0/Release/build/libwsjt_fort.a /JTSDK/wsjtx-output/qt55/2.1.0/Release/build/libwsjt_cxx.a libportaudio.a ../libfftw3f_win.a -lwinmm
rm *.o *.mod

34
lib/ft2/gcom1.f90 Normal file
View File

@ -0,0 +1,34 @@
! Variable Purpose
!---------------------------------------------------------------------------
integer NRING !Length of Rx ring buffer
integer NTZ !Length of Tx waveform in samples
parameter(NRING=230400) !Ring buffer at 12000 samples/sec
parameter(NTZ=23040) !144*160
parameter(NMAX=30000) !2.5*12000
real snrdb
integer ndevin !Device# for audio input
integer ndevout !Device# for audio output
integer iwrite !Pointer to Rx ring buffer
integer itx !Pointer to Tx buffer
integer ngo !Set to 0 to terminate audio streams
integer nTransmitting !Actually transmitting?
integer nTxOK !OK to transmit?
integer nport !COM port for PTT
logical tx_once !Transmit one message, then exit
logical ltx !True if msg i has been transmitted
logical lrx !True if msg i has been received
logical autoseq
logical QSO_in_progress
integer*2 y1 !Ring buffer for audio channel 0
integer*2 y2 !Ring buffer for audio channel 1
integer*2 iwave !Data for Tx audio
character*6 mycall
character*6 hiscall
character*6 hiscall_next
character*4 mygrid
character*3 exch
character*37 txmsg
common/gcom1/snrdb,ndevin,ndevout,iwrite,itx,ngo,nTransmitting,nTxOK,nport, &
ntxed,tx_once,y1(NRING),y2(NRING),iwave(NTZ+3*1152),ltx(5),lrx(5), &
autoseq,QSO_in_progress,mycall,hiscall,hiscall_next,mygrid,exch,txmsg

86
lib/ft2/genft2.f90 Normal file
View File

@ -0,0 +1,86 @@
subroutine genft2(msg0,ichk,msgsent,i4tone,itype)
! s8 + 48bits + s8 + 80 bits = 144 bits (72ms message duration)
!
! Encode an MSK144 message
! Input:
! - msg0 requested message to be transmitted
! - ichk if ichk=1, return only msgsent
! if ichk.ge.10000, set imsg=ichk-10000 for short msg
! - msgsent message as it will be decoded
! - i4tone array of audio tone values, 0 or 1
! - itype message type
! 1 = 77 bit message
! 7 = 16 bit message "<Call_1 Call2> Rpt"
use iso_c_binding, only: c_loc,c_size_t
use packjt77
character*37 msg0
character*37 message !Message to be generated
character*37 msgsent !Message as it will be received
character*77 c77
integer*4 i4tone(144)
integer*1 codeword(128)
integer*1 msgbits(77)
integer*1 bitseq(144) !Tone #s, data and sync (values 0-1)
integer*1 s16(16)
real*8 xi(864),xq(864),pi,twopi
data s16/0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0/
equivalence (ihash,i1hash)
logical unpk77_success
nsym=128
pi=4.0*atan(1.0)
twopi=8.*atan(1.0)
message(1:37)=' '
itype=1
if(msg0(1:1).eq.'@') then !Generate a fixed tone
read(msg0(2:5),*,end=1,err=1) nfreq !at specified frequency
go to 2
1 nfreq=1000
2 i4tone(1)=nfreq
else
message=msg0
do i=1, 37
if(ichar(message(i:i)).eq.0) then
message(i:37)=' '
exit
endif
enddo
do i=1,37 !Strip leading blanks
if(message(1:1).ne.' ') exit
message=message(i+1:)
enddo
if(message(1:1).eq.'<') then
i2=index(message,'>')
i1=0
if(i2.gt.0) i1=index(message(1:i2),' ')
if(i1.gt.0) then
call genmsk40(message,msgsent,ichk,i4tone,itype)
if(itype.lt.0) go to 999
i4tone(41)=-40
go to 999
endif
endif
i3=-1
n3=-1
call pack77(message,i3,n3,c77)
call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent
if(ichk.eq.1) go to 999
read(c77,"(77i1)") msgbits
call encode_128_90(msgbits,codeword)
!Create 144-bit channel vector:
bitseq=0
bitseq(1:16)=s16
bitseq(17:144)=codeword
i4tone=bitseq
endif
999 return
end subroutine genft2

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