mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2025-05-24 02:12:37 -04:00
Merge branch 'release-2.1.0'
This commit is contained in:
commit
a6eecf3b23
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,3 +1,5 @@
|
||||
.gitattributes export-ignore
|
||||
/samples export-ignore
|
||||
/lib/fsk4hf export-ignore
|
||||
/lib/fsk4hf export-ignore
|
||||
/robots export-ignore
|
||||
|
397
Audio/tools/record_time_signal.cpp
Normal file
397
Audio/tools/record_time_signal.cpp
Normal 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;
|
||||
}
|
@ -19,9 +19,12 @@ find_path (__hamlib_pc_path NAMES hamlib.pc
|
||||
PATH_SUFFIXES lib/pkgconfig lib64/pkgconfig
|
||||
)
|
||||
if (__hamlib_pc_path)
|
||||
set (ENV{PKG_CONFIG_PATH} "${__hamlib_pc_path}" "$ENV{PKG_CONFIG_PATH}")
|
||||
unset (__hamlib_pc_path CACHE)
|
||||
set (__pc_path $ENV{PKG_CONFIG_PATH})
|
||||
list (APPEND __pc_path "${__hamlib_pc_path}")
|
||||
set (ENV{PKG_CONFIG_PATH} "${__pc_path}")
|
||||
unset (__pc_path CACHE)
|
||||
endif ()
|
||||
unset (__hamlib_pc_path CACHE)
|
||||
|
||||
# Use pkg-config to get hints about paths, libs and, flags
|
||||
unset (__pkg_config_checked_hamlib CACHE)
|
||||
|
@ -39,6 +39,10 @@ if (POLICY CMP0063)
|
||||
cmake_policy (SET CMP0063 NEW) # honour visibility properties for all library types
|
||||
endif (POLICY CMP0063)
|
||||
|
||||
if (POLICY CMP0071)
|
||||
cmake_policy (SET CMP0071 NEW) # run automoc and autouic on generated sources
|
||||
endif (POLICY CMP0071)
|
||||
|
||||
include (${PROJECT_SOURCE_DIR}/CMake/VersionCompute.cmake)
|
||||
message (STATUS "Building ${CMAKE_PROJECT_NAME}-${wsjtx_VERSION}")
|
||||
|
||||
@ -181,7 +185,11 @@ attach a debugger which will then receive the console output inside its console.
|
||||
set (PROJECT_ARCHITECTURE "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
if (NOT PROJECT_ARCHITECTURE)
|
||||
# This is supposed to happen already on Windows
|
||||
if (CMAKE_SIZEOF_VOID_P MATCHES 8)
|
||||
set (PROJECT_ARCHITECTURE "x64")
|
||||
else ()
|
||||
set (PROJECT_ARCHITECTURE "$ENV{PROCESSOR_ARCHITECTURE}")
|
||||
endif ()
|
||||
endif (NOT PROJECT_ARCHITECTURE)
|
||||
message (STATUS "******************************************************")
|
||||
message (STATUS "Building for for: ${CMAKE_SYSTEM_NAME}-${PROJECT_ARCHITECTURE}")
|
||||
@ -229,6 +237,7 @@ set (wsjt_qt_CXXSRCS
|
||||
models/FrequencyList.cpp
|
||||
models/StationList.cpp
|
||||
widgets/FrequencyLineEdit.cpp
|
||||
widgets/FrequencyDeltaLineEdit.cpp
|
||||
item_delegates/CandidateKeyFilter.cpp
|
||||
item_delegates/ForeignKeyDelegate.cpp
|
||||
validators/LiveFrequencyValidator.cpp
|
||||
@ -271,9 +280,12 @@ set (wsjt_qt_CXXSRCS
|
||||
widgets/CabrilloLogWindow.cpp
|
||||
item_delegates/CallsignDelegate.cpp
|
||||
item_delegates/MaidenheadLocatorDelegate.cpp
|
||||
item_delegates/FrequencyDelegate.cpp
|
||||
item_delegates/FrequencyDeltaDelegate.cpp
|
||||
models/CabrilloLog.cpp
|
||||
logbook/AD1CCty.cpp
|
||||
logbook/WorkedBefore.cpp
|
||||
logbook/Multiplier.cpp
|
||||
)
|
||||
|
||||
set (wsjt_qtmm_CXXSRCS
|
||||
@ -356,6 +368,7 @@ set (wsjt_FSRCS
|
||||
lib/jt65_decode.f90
|
||||
lib/jt65_mod.f90
|
||||
lib/ft8_decode.f90
|
||||
lib/ft4_decode.f90
|
||||
lib/jt9_decode.f90
|
||||
lib/options.f90
|
||||
lib/packjt.f90
|
||||
@ -381,6 +394,7 @@ set (wsjt_FSRCS
|
||||
lib/azdist.f90
|
||||
lib/badmsg.f90
|
||||
lib/ft8/baseline.f90
|
||||
lib/ft4/ft4_baseline.f90
|
||||
lib/bpdecode40.f90
|
||||
lib/bpdecode128_90.f90
|
||||
lib/ft8/bpdecode174_91.f90
|
||||
@ -394,6 +408,7 @@ set (wsjt_FSRCS
|
||||
lib/chkhist.f90
|
||||
lib/chkmsg.f90
|
||||
lib/chkss2.f90
|
||||
lib/ft4/clockit.f90
|
||||
lib/ft8/compress.f90
|
||||
lib/coord.f90
|
||||
lib/db.f90
|
||||
@ -455,6 +470,7 @@ set (wsjt_FSRCS
|
||||
lib/ft8.f90
|
||||
lib/ft8dec.f90
|
||||
lib/ft8/ft8sim.f90
|
||||
lib/ft8/ft8sim_gfsk.f90
|
||||
lib/gen4.f90
|
||||
lib/gen65.f90
|
||||
lib/gen9.f90
|
||||
@ -462,12 +478,16 @@ set (wsjt_FSRCS
|
||||
lib/ft8/genft8.f90
|
||||
lib/genmsk_128_90.f90
|
||||
lib/genmsk40.f90
|
||||
lib/ft4/genft4.f90
|
||||
lib/ft4/gen_ft4wave.f90
|
||||
lib/ft8/gen_ft8wave.f90
|
||||
lib/genqra64.f90
|
||||
lib/ft8/genft8refsig.f90
|
||||
lib/genwspr.f90
|
||||
lib/geodist.f90
|
||||
lib/getlags.f90
|
||||
lib/getmet4.f90
|
||||
lib/ft2/gfsk_pulse.f90
|
||||
lib/graycode.f90
|
||||
lib/graycode65.f90
|
||||
lib/grayline.f90
|
||||
@ -506,6 +526,10 @@ set (wsjt_FSRCS
|
||||
lib/msk144signalquality.f90
|
||||
lib/msk144sim.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/wsprd/osdwspr.f90
|
||||
lib/ft8/osd174_91.f90
|
||||
@ -539,6 +563,7 @@ set (wsjt_FSRCS
|
||||
lib/stdmsg.f90
|
||||
lib/subtract65.f90
|
||||
lib/ft8/subtractft8.f90
|
||||
lib/ft4/subtractft4.f90
|
||||
lib/sun.f90
|
||||
lib/symspec.f90
|
||||
lib/symspec2.f90
|
||||
@ -546,8 +571,11 @@ set (wsjt_FSRCS
|
||||
lib/sync4.f90
|
||||
lib/sync64.f90
|
||||
lib/sync65.f90
|
||||
lib/ft4/getcandidates4.f90
|
||||
lib/ft4/get_ft4_bitmetrics.f90
|
||||
lib/ft8/sync8.f90
|
||||
lib/ft8/sync8d.f90
|
||||
lib/ft4/sync4d.f90
|
||||
lib/sync9.f90
|
||||
lib/sync9f.f90
|
||||
lib/sync9w.f90
|
||||
@ -794,7 +822,7 @@ if (WIN32)
|
||||
endif (NOT AXSERVER)
|
||||
string (REPLACE "\"" "" AXSERVER ${AXSERVER})
|
||||
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)
|
||||
endif ()
|
||||
find_package (Boost 1.63 REQUIRED)
|
||||
if (Boost_FOUND)
|
||||
include_directories (${Boost_INCLUDE_DIRS})
|
||||
endif ()
|
||||
|
||||
#
|
||||
# OpenMP
|
||||
@ -860,10 +885,7 @@ message (STATUS "hamlib_LIBRARY_DIRS: ${hamlib_LIBRARY_DIRS}")
|
||||
#
|
||||
|
||||
# Widgets finds its own dependencies.
|
||||
find_package (Qt5Widgets 5 REQUIRED)
|
||||
find_package (Qt5Multimedia 5 REQUIRED)
|
||||
find_package (Qt5PrintSupport 5 REQUIRED)
|
||||
find_package (Qt5Sql 5 REQUIRED)
|
||||
find_package (Qt5 COMPONENTS Widgets Multimedia PrintSupport Sql LinguistTools REQUIRED)
|
||||
|
||||
if (WIN32)
|
||||
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})
|
||||
|
||||
|
||||
# Qt i18n
|
||||
set (LANGUAGES
|
||||
en_GB
|
||||
pt_PT
|
||||
)
|
||||
foreach (lang_ ${LANGUAGES})
|
||||
file (TO_NATIVE_PATH translations/wsjtx_${lang_}.ts ts_)
|
||||
list (APPEND TS_FILES ${ts_})
|
||||
endforeach ()
|
||||
if (UPDATE_TRANSLATIONS)
|
||||
message (STATUS "UPDATE_TRANSLATIONS option is set.")
|
||||
qt5_create_translation (
|
||||
QM_FILES ${wsjt_qt_UISRCS} ${wsjtx_UISRCS} ${wsjt_qt_CXXSRCS} ${wsjtx_CXXSRCS}
|
||||
${TS_FILES}
|
||||
)
|
||||
else ()
|
||||
qt5_add_translation (QM_FILES ${TS_FILES})
|
||||
endif ()
|
||||
add_custom_target (translations DEPENDS ${QM_FILES})
|
||||
set_property (DIRECTORY PROPERTY CLEAN_NO_CUSTOM TRUE)
|
||||
# do this after i18n to stop lupdate walking the boost tree which it
|
||||
# chokes on
|
||||
if (Boost_FOUND)
|
||||
include_directories (${Boost_INCLUDE_DIRS})
|
||||
endif ()
|
||||
|
||||
# embedded resources
|
||||
function (add_resources resources path)
|
||||
foreach (resource_file_ ${ARGN})
|
||||
get_filename_component (name_ ${resource_file_} NAME)
|
||||
if (IS_ABSOLUTE "${resource_file_}")
|
||||
file (TO_NATIVE_PATH ${resource_file_} source_)
|
||||
else ()
|
||||
file (TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${resource_file_} source_)
|
||||
endif ()
|
||||
file (TO_NATIVE_PATH ${path}/${name_} dest_)
|
||||
set (resources_ "${resources_}\n <file alias=\"${dest_}\">${source_}</file>")
|
||||
set (${resources} ${${resources}}${resources_} PARENT_SCOPE)
|
||||
@ -1082,6 +1134,7 @@ endfunction (add_resources resources path)
|
||||
|
||||
add_resources (wsjtx_RESOURCES "" ${TOP_LEVEL_RESOURCES})
|
||||
add_resources (wsjtx_RESOURCES /Palettes ${PALETTE_FILES})
|
||||
add_resources (wsjtx_RESOURCES /Translations ${QM_FILES})
|
||||
|
||||
configure_file (wsjtx.qrc.in wsjtx.qrc @ONLY)
|
||||
|
||||
@ -1102,7 +1155,6 @@ if (WIN32)
|
||||
wrap_ax_server (GENAXSRCS ${AXSERVERSRCS})
|
||||
endif (WIN32)
|
||||
|
||||
|
||||
#
|
||||
# targets
|
||||
#
|
||||
@ -1221,6 +1273,9 @@ target_link_libraries (jt49sim wsjt_fort wsjt_cxx)
|
||||
add_executable (allsim lib/allsim.f90 wsjtx.rc)
|
||||
target_link_libraries (allsim wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (rtty_spec lib/rtty_spec.f90 wsjtx.rc)
|
||||
target_link_libraries (rtty_spec wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (jt65code lib/jt65code.f90 wsjtx.rc)
|
||||
target_link_libraries (jt65code wsjt_fort wsjt_cxx)
|
||||
|
||||
@ -1254,9 +1309,21 @@ target_link_libraries (ft8 wsjt_fort wsjt_cxx)
|
||||
add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc)
|
||||
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)
|
||||
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)
|
||||
|
||||
# build the main application
|
||||
@ -1606,6 +1673,7 @@ if (NOT is_debug_build)
|
||||
install (
|
||||
DIRECTORY
|
||||
${QT_PLUGINS_DIR}/platforms
|
||||
${QT_PLUGINS_DIR}/styles
|
||||
${QT_PLUGINS_DIR}/accessible
|
||||
${QT_PLUGINS_DIR}/audio
|
||||
${QT_PLUGINS_DIR}/imageformats
|
||||
|
@ -167,8 +167,11 @@
|
||||
#include "MetaDataRegistry.hpp"
|
||||
#include "SettingsGroup.hpp"
|
||||
#include "widgets/FrequencyLineEdit.hpp"
|
||||
#include "widgets/FrequencyDeltaLineEdit.hpp"
|
||||
#include "item_delegates/CandidateKeyFilter.hpp"
|
||||
#include "item_delegates/ForeignKeyDelegate.hpp"
|
||||
#include "item_delegates/FrequencyDelegate.hpp"
|
||||
#include "item_delegates/FrequencyDeltaDelegate.hpp"
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "Transceiver.hpp"
|
||||
#include "models/Bands.hpp"
|
||||
@ -209,6 +212,7 @@ namespace
|
||||
|LB|NU|YT|PEI
|
||||
|DC # District of Columbia
|
||||
|DX # anyone else
|
||||
|SCC # Slovenia Contest Club contest
|
||||
)
|
||||
)", QRegularExpression::CaseInsensitiveOption | QRegularExpression::ExtendedPatternSyntaxOption};
|
||||
|
||||
@ -247,6 +251,8 @@ namespace
|
||||
class FrequencyDialog final
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Item = FrequencyList_v2::Item;
|
||||
|
||||
@ -293,6 +299,8 @@ private:
|
||||
class StationDialog final
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StationDialog (StationList const * stations, Bands * bands, QWidget * parent = nullptr)
|
||||
: QDialog {parent}
|
||||
@ -563,6 +571,7 @@ private:
|
||||
DecodeHighlightingModel decode_highlighing_model_;
|
||||
DecodeHighlightingModel next_decode_highlighing_model_;
|
||||
bool highlight_by_mode_;
|
||||
bool include_WAE_entities_;
|
||||
int LotW_days_since_upload_;
|
||||
|
||||
TransceiverFactory::ParameterPack rig_params_;
|
||||
@ -608,6 +617,7 @@ private:
|
||||
bool miles_;
|
||||
bool quick_call_;
|
||||
bool disable_TX_on_73_;
|
||||
bool force_call_1st_;
|
||||
bool alternate_bindings_;
|
||||
int watchdog_;
|
||||
bool TX_messages_;
|
||||
@ -630,6 +640,7 @@ private:
|
||||
bool udpWindowToFront_;
|
||||
bool udpWindowRestore_;
|
||||
DataMode data_mode_;
|
||||
bool bLowSidelobes_;
|
||||
bool pwrBandTxMemory_;
|
||||
bool pwrBandTuneMemory_;
|
||||
|
||||
@ -703,6 +714,7 @@ bool Configuration::clear_DX () const {return m_->clear_DX_;}
|
||||
bool Configuration::miles () const {return m_->miles_;}
|
||||
bool Configuration::quick_call () const {return m_->quick_call_;}
|
||||
bool Configuration::disable_TX_on_73 () const {return m_->disable_TX_on_73_;}
|
||||
bool Configuration::force_call_1st() const {return m_->force_call_1st_;}
|
||||
bool Configuration::alternate_bindings() const {return m_->alternate_bindings_;}
|
||||
int Configuration::watchdog () const {return m_->watchdog_;}
|
||||
bool Configuration::TX_messages () const {return m_->TX_messages_;}
|
||||
@ -714,12 +726,14 @@ bool Configuration::x2ToneSpacing() const {return m_->x2ToneSpacing_;}
|
||||
bool Configuration::x4ToneSpacing() const {return m_->x4ToneSpacing_;}
|
||||
bool Configuration::split_mode () const {return m_->split_mode ();}
|
||||
QString Configuration::opCall() const {return m_->opCall_;}
|
||||
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
|
||||
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
|
||||
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
|
||||
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
|
||||
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
|
||||
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::lowSidelobes() const {return m_->bLowSidelobes_;}
|
||||
bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;}
|
||||
bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;}
|
||||
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_;}
|
||||
DecodeHighlightingModel const& Configuration::decode_highlighting () const {return m_->decode_highlighing_model_;}
|
||||
bool Configuration::highlight_by_mode () const {return m_->highlight_by_mode_;}
|
||||
bool Configuration::include_WAE_entities () const {return m_->include_WAE_entities_;}
|
||||
|
||||
void Configuration::set_calibration (CalibrationParams params)
|
||||
{
|
||||
@ -944,6 +959,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
, station_insert_action_ {tr ("&Insert ..."), nullptr}
|
||||
, station_dialog_ {new StationDialog {&next_stations_, &bands_, this}}
|
||||
, highlight_by_mode_ {false}
|
||||
, include_WAE_entities_ {false}
|
||||
, LotW_days_since_upload_ {0}
|
||||
, last_port_type_ {TransceiverFactory::Capabilities::none}
|
||||
, rig_is_dummy_ {false}
|
||||
@ -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"));
|
||||
|
||||
// load the dictionary if it exists
|
||||
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), false);
|
||||
|
||||
//
|
||||
// validation
|
||||
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);
|
||||
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
|
||||
@ -1109,9 +1129,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true);
|
||||
|
||||
// delegates
|
||||
auto frequencies_item_delegate = new QStyledItemDelegate {this};
|
||||
frequencies_item_delegate->setItemEditorFactory (item_editor_factory ());
|
||||
ui_->frequencies_table_view->setItemDelegate (frequencies_item_delegate);
|
||||
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::frequency_column, new FrequencyDelegate {this});
|
||||
ui_->frequencies_table_view->setItemDelegateForColumn (FrequencyList_v2::region_column, new ForeignKeyDelegate {®ions_, 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);
|
||||
|
||||
// stations delegates
|
||||
auto stations_item_delegate = new QStyledItemDelegate {this};
|
||||
stations_item_delegate->setItemEditorFactory (item_editor_factory ());
|
||||
ui_->stations_table_view->setItemDelegate (stations_item_delegate);
|
||||
ui_->stations_table_view->setItemDelegateForColumn (StationList::offset_column, new FrequencyDeltaDelegate {this});
|
||||
ui_->stations_table_view->setItemDelegateForColumn (StationList::band_column, new ForeignKeyDelegate {&bands_, &next_stations_, 0, StationList::band_column, this});
|
||||
|
||||
// stations actions
|
||||
@ -1235,6 +1251,7 @@ void Configuration::impl::initialize_models ()
|
||||
ui_->miles_check_box->setChecked (miles_);
|
||||
ui_->quick_call_check_box->setChecked (quick_call_);
|
||||
ui_->disable_TX_on_73_check_box->setChecked (disable_TX_on_73_);
|
||||
ui_->force_call_1st_check_box->setChecked (force_call_1st_);
|
||||
ui_->alternate_bindings_check_box->setChecked (alternate_bindings_);
|
||||
ui_->tx_watchdog_spin_box->setValue (watchdog_);
|
||||
ui_->TX_messages_check_box->setChecked (TX_messages_);
|
||||
@ -1285,6 +1302,8 @@ void Configuration::impl::initialize_models ()
|
||||
ui_->udpWindowRestore->setChecked(udpWindowRestore_);
|
||||
ui_->calibration_intercept_spin_box->setValue (calibration_.intercept);
|
||||
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 ())
|
||||
{
|
||||
@ -1306,6 +1325,7 @@ void Configuration::impl::initialize_models ()
|
||||
|
||||
next_decode_highlighing_model_.items (decode_highlighing_model_.items ());
|
||||
ui_->highlight_by_mode_check_box->setChecked (highlight_by_mode_);
|
||||
ui_->include_WAE_check_box->setChecked (include_WAE_entities_);
|
||||
ui_->LotW_days_since_upload_spin_box->setValue (LotW_days_since_upload_);
|
||||
|
||||
set_rig_invariants ();
|
||||
@ -1454,6 +1474,7 @@ void Configuration::impl::read_settings ()
|
||||
if (!highlight_items.size ()) highlight_items = DecodeHighlightingModel::default_items ();
|
||||
decode_highlighing_model_.items (highlight_items);
|
||||
highlight_by_mode_ = settings_->value("HighlightByMode", false).toBool ();
|
||||
include_WAE_entities_ = settings_->value("IncludeWAEEntities", false).toBool ();
|
||||
LotW_days_since_upload_ = settings_->value ("LotWDaysSinceLastUpload", 365).toInt ();
|
||||
lotw_users_.set_age_constraint (LotW_days_since_upload_);
|
||||
|
||||
@ -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_.ptt_port = settings_->value ("PTTport").toString ();
|
||||
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 ();
|
||||
autoLog_ = settings_->value ("AutoLog", 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 ();
|
||||
quick_call_ = settings_->value ("QuickCall", false).toBool ();
|
||||
disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool ();
|
||||
force_call_1st_ = settings_->value ("ForceCallFirst", false).toBool ();
|
||||
alternate_bindings_ = settings_->value ("AlternateBindings", false).toBool ();
|
||||
watchdog_ = settings_->value ("TxWatchdog", 6).toInt ();
|
||||
TX_messages_ = settings_->value ("Tx2QSO", true).toBool ();
|
||||
@ -1565,6 +1588,7 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("stations", QVariant::fromValue (stations_.station_list ()));
|
||||
settings_->setValue ("DecodeHighlighting", QVariant::fromValue (decode_highlighing_model_.items ()));
|
||||
settings_->setValue ("HighlightByMode", highlight_by_mode_);
|
||||
settings_->setValue ("IncludeWAEEntities", include_WAE_entities_);
|
||||
settings_->setValue ("LotWDaysSinceLastUpload", LotW_days_since_upload_);
|
||||
settings_->setValue ("toRTTY", log_as_RTTY_);
|
||||
settings_->setValue ("dBtoComments", report_in_comments_);
|
||||
@ -1577,6 +1601,7 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("CATStopBits", QVariant::fromValue (rig_params_.stop_bits));
|
||||
settings_->setValue ("CATHandshake", QVariant::fromValue (rig_params_.handshake));
|
||||
settings_->setValue ("DataMode", QVariant::fromValue (data_mode_));
|
||||
settings_->setValue ("LowSidelobes",bLowSidelobes_);
|
||||
settings_->setValue ("PromptToLog", prompt_to_log_);
|
||||
settings_->setValue ("AutoLog", autoLog_);
|
||||
settings_->setValue ("DecodesFromTop", decodes_from_top_);
|
||||
@ -1587,6 +1612,7 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("Miles", miles_);
|
||||
settings_->setValue ("QuickCall", quick_call_);
|
||||
settings_->setValue ("73TxDisable", disable_TX_on_73_);
|
||||
settings_->setValue ("ForceCallFirst", force_call_1st_);
|
||||
settings_->setValue ("AlternateBindings", alternate_bindings_);
|
||||
settings_->setValue ("TxWatchdog", watchdog_);
|
||||
settings_->setValue ("Tx2QSO", TX_messages_);
|
||||
@ -2031,10 +2057,12 @@ void Configuration::impl::accept ()
|
||||
miles_ = ui_->miles_check_box->isChecked ();
|
||||
quick_call_ = ui_->quick_call_check_box->isChecked ();
|
||||
disable_TX_on_73_ = ui_->disable_TX_on_73_check_box->isChecked ();
|
||||
force_call_1st_ = ui_->force_call_1st_check_box->isChecked ();
|
||||
alternate_bindings_ = ui_->alternate_bindings_check_box->isChecked ();
|
||||
watchdog_ = ui_->tx_watchdog_spin_box->value ();
|
||||
TX_messages_ = ui_->TX_messages_check_box->isChecked ();
|
||||
data_mode_ = static_cast<DataMode> (ui_->TX_mode_button_group->checkedId ());
|
||||
bLowSidelobes_ = ui_->rbLowSidelobes->isChecked();
|
||||
save_directory_ = ui_->save_path_display_label->text ();
|
||||
azel_directory_ = ui_->azel_path_display_label->text ();
|
||||
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);
|
||||
}
|
||||
|
||||
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
|
||||
{
|
||||
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
|
||||
Q_EMIT self_->accept_udp_requests_changed (accept_udp_requests_);
|
||||
}
|
||||
|
||||
n1mm_server_name_ = ui_->n1mm_server_name_line_edit->text ();
|
||||
n1mm_server_port_ = ui_->n1mm_server_port_spin_box->value ();
|
||||
broadcast_to_n1mm_ = ui_->enable_n1mm_broadcast_check_box->isChecked ();
|
||||
@ -2097,6 +2130,7 @@ void Configuration::impl::accept ()
|
||||
Q_EMIT self_->decode_highlighting_changed (decode_highlighing_model_);
|
||||
}
|
||||
highlight_by_mode_ = ui_->highlight_by_mode_check_box->isChecked ();
|
||||
include_WAE_entities_ = ui_->include_WAE_check_box->isChecked ();
|
||||
LotW_days_since_upload_ = ui_->LotW_days_since_upload_spin_box->value ();
|
||||
lotw_users_.set_age_constraint (LotW_days_since_upload_);
|
||||
|
||||
@ -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*/)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@ -2919,9 +2953,15 @@ void Configuration::impl::fill_port_combo_box (QComboBox * cb)
|
||||
// remove possibly confusing Windows device path (OK because
|
||||
// it gets added back by Hamlib)
|
||||
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->setItemData (cb->count () - 1, "Custom USB device", Qt::ToolTipRole);
|
||||
cb->setEditText (current_text);
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,7 @@ public:
|
||||
bool miles () const;
|
||||
bool quick_call () const;
|
||||
bool disable_TX_on_73 () const;
|
||||
bool force_call_1st() const;
|
||||
bool alternate_bindings() const;
|
||||
int watchdog () const;
|
||||
bool TX_messages () const;
|
||||
@ -137,6 +138,7 @@ public:
|
||||
bool twoPass() const;
|
||||
bool bFox() const;
|
||||
bool bHound() const;
|
||||
bool bLowSidelobes() const;
|
||||
bool x2ToneSpacing() const;
|
||||
bool x4ToneSpacing() const;
|
||||
bool MyDx() const;
|
||||
@ -146,12 +148,14 @@ public:
|
||||
bool EMEonly() const;
|
||||
bool post_decodes () const;
|
||||
QString opCall() const;
|
||||
void opCall (QString const&);
|
||||
QString udp_server_name () const;
|
||||
port_type udp_server_port () const;
|
||||
QString n1mm_server_name () const;
|
||||
port_type n1mm_server_port () const;
|
||||
bool valid_n1mm_info () const;
|
||||
bool broadcast_to_n1mm() const;
|
||||
bool lowSidelobes() const;
|
||||
bool accept_udp_requests () const;
|
||||
bool udpWindowToFront () const;
|
||||
bool udpWindowRestore () const;
|
||||
@ -173,6 +177,7 @@ public:
|
||||
LotWUsers const& lotw_users () const;
|
||||
DecodeHighlightingModel const& decode_highlighting () const;
|
||||
bool highlight_by_mode () const;
|
||||
bool include_WAE_entities () const;
|
||||
|
||||
enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, FOX, HOUND};
|
||||
SpecialOperatingActivity special_op_id () const;
|
||||
@ -266,6 +271,7 @@ public:
|
||||
//
|
||||
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
|
||||
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
|
||||
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
|
||||
|
||||
// signal updates to decode highlighting
|
||||
Q_SIGNAL void decode_highlighting_changed (DecodeHighlightingModel const&) const;
|
||||
|
246
Configuration.ui
246
Configuration.ui
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>546</width>
|
||||
<height>536</height>
|
||||
<height>553</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -301,7 +301,14 @@
|
||||
<string>Behavior</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="4" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="decode_at_52s_check_box">
|
||||
<property name="text">
|
||||
<string>Decode after EME delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_7">
|
||||
@ -347,10 +354,27 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="decode_at_52s_check_box">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="enable_VHF_features_check_box">
|
||||
<property name="text">
|
||||
<string>Decode after EME delay</string>
|
||||
<string>Enable VHF/UHF/Microwave features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="single_decode_check_box">
|
||||
<property name="text">
|
||||
<string>Single decode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="tx_QSY_check_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>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.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow Tx frequency changes while transmitting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -367,31 +391,35 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="single_decode_check_box">
|
||||
<property name="text">
|
||||
<string>Single decode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="enable_VHF_features_check_box">
|
||||
<property name="text">
|
||||
<string>Enable VHF/UHF/Microwave features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="tx_QSY_check_box">
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="monitor_last_used_check_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>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.</p></body></html></string>
|
||||
<string><html><head/><body><p>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.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Allow Tx frequency changes while transmitting</string>
|
||||
<string>Monitor returns to last used frequency</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="alternate_bindings_check_box">
|
||||
<property name="text">
|
||||
<string>Alternate F1-F6 bindings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="disable_TX_on_73_check_box">
|
||||
<property name="toolTip">
|
||||
<string>Turns off automatic transmissions after sending a 73 or any other free
|
||||
text message.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Di&sable Tx after sending 73</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="CW_id_after_73_check_box">
|
||||
@ -444,34 +472,6 @@ quiet period when decoding is done.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="monitor_last_used_check_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>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.</p></body></html></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&sable Tx after sending 73</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="quick_call_check_box">
|
||||
<property name="toolTip">
|
||||
@ -482,6 +482,13 @@ text message.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="force_call_1st_check_box">
|
||||
<property name="text">
|
||||
<string>Calling CQ forces Call 1st</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -1912,7 +1919,7 @@ for assessing propagation and system performance.</string>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="n1mm_group_box">
|
||||
<property name="title">
|
||||
<string>N1MM Logger+ Broadcasts</string>
|
||||
<string>Secondary UDP Server (deprecated)</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_15">
|
||||
<item row="0" column="0" colspan="2">
|
||||
@ -1928,7 +1935,7 @@ for assessing propagation and system performance.</string>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="n1mm_server_name_label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>N1MM Server name or IP address:</p></body></html></string>
|
||||
<string>Server name or IP address:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>n1mm_server_name_line_edit</cstring>
|
||||
@ -1945,7 +1952,7 @@ for assessing propagation and system performance.</string>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="n1mm_server_port_label">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>N1MM Server port number:</p></body></html></string>
|
||||
<string>Server port number:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>n1mm_server_port_spin_box</cstring>
|
||||
@ -2287,6 +2294,23 @@ Right click for insert and delete options.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_20">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="include_WAE_check_box"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="includeExtraWAEEntitiesLabel">
|
||||
<property name="text">
|
||||
<string>Include extra WAE entities</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>include_WAE_check_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -2299,35 +2323,6 @@ Right click for insert and delete options.</string>
|
||||
<string>Logbook of the World User Validation</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_18">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Age of last upload less than:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>LotW_days_since_upload_spin_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>365</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
@ -2362,6 +2357,35 @@ Right click for insert and delete options.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Age of last upload less than:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>LotW_days_since_upload_spin_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>365</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -2469,7 +2493,7 @@ Right click for insert and delete options.</string>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="gbSpecialOpActivity">
|
||||
<property name="title">
|
||||
<string>Special operating activity: Generation of FT8 and MSK144 messages</string>
|
||||
<string>Special operating activity: Generation of FT4, FT8, and MSK144 messages</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
@ -2556,7 +2580,7 @@ Right click for insert and delete options.</string>
|
||||
<string><html><head/><body><p>ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &quot;DX&quot;.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>ARRL RTTY Roundup</string>
|
||||
<string>RTTY Roundup messages</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">special_op_activity_button_group</string>
|
||||
@ -2821,6 +2845,48 @@ Right click for insert and delete options.</string>
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
@ -3036,13 +3102,13 @@ Right click for insert and delete options.</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -127,7 +127,7 @@ int DXLabSuiteCommanderTransceiver::do_start ()
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
|
||||
}
|
||||
|
||||
poll ();
|
||||
do_poll ();
|
||||
return resolution;
|
||||
}
|
||||
|
||||
@ -247,7 +247,7 @@ void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
|
||||
update_mode (m);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::poll ()
|
||||
void DXLabSuiteCommanderTransceiver::do_poll ()
|
||||
{
|
||||
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
|
||||
bool quiet {false};
|
||||
|
@ -39,7 +39,7 @@ protected:
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
void poll () override;
|
||||
void do_poll () override;
|
||||
|
||||
private:
|
||||
MODE get_mode (bool no_debug = false);
|
||||
|
@ -38,5 +38,7 @@
|
||||
<string>True</string>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<true/>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app requires microphone access to receive signals.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
23
Detector.cpp
23
Detector.cpp
@ -2,6 +2,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QtAlgorithms>
|
||||
#include <QDebug>
|
||||
#include <math.h>
|
||||
#include "commons.h"
|
||||
|
||||
#include "moc_Detector.cpp"
|
||||
@ -10,7 +11,7 @@ extern "C" {
|
||||
void fil4_(qint16*, qint32*, qint16*, qint32*);
|
||||
}
|
||||
|
||||
Detector::Detector (unsigned frameRate, unsigned periodLengthInSeconds,
|
||||
Detector::Detector (unsigned frameRate, double periodLengthInSeconds,
|
||||
unsigned downSampleFactor, QObject * parent)
|
||||
: AudioDevice (parent)
|
||||
, m_frameRate (frameRate)
|
||||
@ -54,12 +55,14 @@ void Detector::clear ()
|
||||
|
||||
qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||
{
|
||||
int ns=secondInPeriod();
|
||||
if(ns < m_ns) { // When ns has wrapped around to zero, restart the buffers
|
||||
static unsigned mstr0=999999;
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
if(mstr < mstr0) { //When mstr has wrapped around to 0, restart the buffer
|
||||
dec_data.params.kin = 0;
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
m_ns=ns;
|
||||
mstr0=mstr;
|
||||
|
||||
// no torn frames
|
||||
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ())));
|
||||
@ -72,7 +75,7 @@ qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
|
||||
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
|
||||
<< " frames of data on the floor!"
|
||||
<< dec_data.params.kin << ns;
|
||||
<< dec_data.params.kin << mstr;
|
||||
}
|
||||
|
||||
for (unsigned remaining = framesAccepted; remaining; ) {
|
||||
@ -120,13 +123,3 @@ qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||
return maxSize; // we drop any data past the end of the buffer on
|
||||
// the floor until the next period starts
|
||||
}
|
||||
|
||||
unsigned Detector::secondInPeriod () const
|
||||
{
|
||||
// we take the time of the data as the following assuming no latency
|
||||
// delivering it to us (not true but close enough for us)
|
||||
qint64 now (QDateTime::currentMSecsSinceEpoch ());
|
||||
|
||||
unsigned secondInToday ((now % 86400000LL) / 1000);
|
||||
return secondInToday % m_period;
|
||||
}
|
||||
|
@ -22,9 +22,10 @@ public:
|
||||
//
|
||||
// the samplesPerFFT argument is the number after down sampling
|
||||
//
|
||||
Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned downSampleFactor = 4u, QObject * parent = 0);
|
||||
Detector (unsigned frameRate, double periodLengthInSeconds, unsigned downSampleFactor = 4u,
|
||||
QObject * parent = 0);
|
||||
|
||||
void setTRPeriod(unsigned p) {m_period=p;}
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
bool reset () override;
|
||||
|
||||
Q_SIGNAL void framesWritten (qint64) const;
|
||||
@ -40,10 +41,9 @@ protected:
|
||||
|
||||
private:
|
||||
void clear (); // discard buffer contents
|
||||
unsigned secondInPeriod () const;
|
||||
|
||||
unsigned m_frameRate;
|
||||
unsigned m_period;
|
||||
double m_period;
|
||||
unsigned m_downSampleFactor;
|
||||
qint32 m_samplesPerFFT; // after any down sampling
|
||||
qint32 m_ns;
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "EmulateSplitTransceiver.hpp"
|
||||
|
||||
#include "moc_EmulateSplitTransceiver.cpp"
|
||||
|
||||
EmulateSplitTransceiver::EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped, QObject * parent)
|
||||
: Transceiver {parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
|
@ -27,6 +27,8 @@
|
||||
class EmulateSplitTransceiver final
|
||||
: public Transceiver
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped,
|
||||
|
@ -19,6 +19,8 @@ namespace
|
||||
int constexpr yaesu_delay {250};
|
||||
}
|
||||
|
||||
#include "moc_HRDTransceiver.cpp"
|
||||
|
||||
void HRDTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
|
||||
{
|
||||
(*registry)[HRD_transceiver_name] = TransceiverFactory::Capabilities (id, TransceiverFactory::Capabilities::network, true, true /* maybe */);
|
||||
@ -885,7 +887,7 @@ bool HRDTransceiver::is_button_checked (int button_index, bool no_debug)
|
||||
return "1" == reply;
|
||||
}
|
||||
|
||||
void HRDTransceiver::poll ()
|
||||
void HRDTransceiver::do_poll ()
|
||||
{
|
||||
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
|
||||
bool quiet {false};
|
||||
|
@ -27,6 +27,8 @@ class QByteArray;
|
||||
class HRDTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id);
|
||||
|
||||
@ -48,7 +50,7 @@ protected:
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
// Implement the PollingTransceiver interface.
|
||||
void poll () override;
|
||||
void do_poll () override;
|
||||
|
||||
private:
|
||||
QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false);
|
||||
|
@ -632,7 +632,7 @@ int HamlibTransceiver::do_start ()
|
||||
resolution = -1; // best guess
|
||||
}
|
||||
|
||||
poll ();
|
||||
do_poll ();
|
||||
|
||||
TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution);
|
||||
return resolution;
|
||||
@ -898,7 +898,7 @@ void HamlibTransceiver::do_mode (MODE mode)
|
||||
update_mode (mode);
|
||||
}
|
||||
|
||||
void HamlibTransceiver::poll ()
|
||||
void HamlibTransceiver::do_poll ()
|
||||
{
|
||||
#if !WSJT_TRACE_CAT_POLLS
|
||||
#if defined (NDEBUG)
|
||||
|
@ -21,7 +21,7 @@ extern "C"
|
||||
class HamlibTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
Q_OBJECT // for translation context
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *);
|
||||
@ -40,7 +40,7 @@ class HamlibTransceiver final
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool) override;
|
||||
|
||||
void poll () override;
|
||||
void do_poll () override;
|
||||
|
||||
void error_check (int ret_code, QString const& doing) const;
|
||||
void set_conf (char const * item, char const * value);
|
||||
|
62
INSTALL
62
INSTALL
@ -30,13 +30,13 @@ Mac".
|
||||
|
||||
Qt v5, preferably v5.5 or later is required to build WSJT-X.
|
||||
|
||||
Qt v5 multimedia support and serial port is necessary as well as the
|
||||
core Qt v5 components, normally installing the Qt multimedia
|
||||
development package and Qt serialport development package are
|
||||
sufficient to pull in all the required Qt components and dependants as
|
||||
a single transaction. On some systems the Qt multimedia plugin
|
||||
component is separate in the distribution repository an it may also
|
||||
need installing.
|
||||
Qt v5 multimedia support, serial port, and Linguist is necessary as
|
||||
well as the core Qt v5 components, normally installing the Qt
|
||||
multimedia development, Qt serialport development packages, and the Qt
|
||||
Linguist packages are sufficient to pull in all the required Qt
|
||||
components and dependants as a single transaction. On some systems
|
||||
the Qt multimedia plugin component is separate in the distribution
|
||||
repository an it may also need installing.
|
||||
|
||||
The single precision FFTW v3 library libfftw3f is required along with
|
||||
the libfftw library development package. Normally installing the
|
||||
@ -256,47 +256,6 @@ The above commands will build hamlib and install it into
|
||||
~/hamlib-prefix. If `make install-strip` fails, try `make install`.
|
||||
|
||||
|
||||
Qt
|
||||
--
|
||||
|
||||
NOTE: As of Qt v5.4 building Qt from source on Mac OS X is no longer
|
||||
necessary since the Qt team have switched to using the modern libc++
|
||||
Standard C++ Library for all distributable run time
|
||||
components. Instead you may simply download a binary installer for OS
|
||||
X 64-bit. The binary installer is here:
|
||||
|
||||
http://www.qt.io/download
|
||||
|
||||
The binary Qt distributions prior to Qt v5.4 from
|
||||
http://www.qt.io/download unfortunately are built to use the libstdc++
|
||||
C++ support library, WSJT-X uses a less geriatric C++ dialect which
|
||||
uses the libc++ C++ support library. This means that you need to
|
||||
build Qt from sources. This is not difficult but does take some time.
|
||||
|
||||
Download the Qt source tarball from
|
||||
http://www.qt.io/download-open-source/, the link is about half way
|
||||
down the page, you want the full sources tar ball shown as a 'tar.gz'
|
||||
link.
|
||||
|
||||
Unpack the sources and cd into the top level directory then type:
|
||||
|
||||
$ ./configure -prefix ~/local/qt-macx-clang -opensource \
|
||||
-confirm-license -platform macx-clang -silent -nomake tests \
|
||||
-nomake examples -sdk macosx10.10 -skip qtwebkit \
|
||||
-skip qtwebkit-examples -skip qtquick1 -skip qtconnectivity \
|
||||
-skip qtlocation -skip qtsensors -skip qtscript \
|
||||
-skip qtwebsockets -skip qtwebengine -skip qtwebchannel \
|
||||
-skip qtwayland -skip qtquickcontrols -skip qtdeclarative \
|
||||
-skip qtxmlpatterns -skip qtenginio
|
||||
$ make -j4
|
||||
$ make install
|
||||
|
||||
If you are building on 10.8 or don't have the 10.10 Mac SDK (Xcode 6)
|
||||
available, you can substitute '-sdk macosx10.9' above.
|
||||
|
||||
The build above will take a few hours to complete.
|
||||
|
||||
|
||||
CMake
|
||||
-----
|
||||
Although CMake is available via MacPorts I prefer to use the binary
|
||||
@ -328,6 +287,13 @@ $ sudo chgrp wheel /usr/local/bin
|
||||
and then retry the install command.
|
||||
|
||||
|
||||
Qt
|
||||
--
|
||||
|
||||
Download the latest on-line installer package from the Qt web site and
|
||||
isntall the latest Qt stable version development package.
|
||||
|
||||
|
||||
WSJT-X
|
||||
------
|
||||
First fetch the source from the repository:
|
||||
|
@ -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
|
||||
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);
|
||||
if (current_url_.isValid () && !QSslSocket::supportsSsl ())
|
||||
@ -57,11 +58,14 @@ public:
|
||||
download (current_url_);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exists)
|
||||
{
|
||||
// load the database asynchronously
|
||||
future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void download (QUrl url)
|
||||
{
|
||||
@ -254,9 +258,9 @@ void LotWUsers::set_local_file_path (QString const& 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)
|
||||
|
@ -23,7 +23,7 @@ public:
|
||||
|
||||
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);
|
||||
|
||||
// returns true if the specified call sign 'call' has uploaded their
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QHostInfo>
|
||||
@ -35,6 +36,7 @@ public:
|
||||
impl (QString const& id, QString const& version, QString const& revision,
|
||||
port_type server_port, MessageClient * self)
|
||||
: self_ {self}
|
||||
, enabled_ {false}
|
||||
, id_ {id}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
@ -79,6 +81,7 @@ public:
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
|
||||
MessageClient * self_;
|
||||
bool enabled_;
|
||||
QString id_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
@ -152,7 +155,7 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
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
|
||||
// negotiated schema
|
||||
@ -160,6 +163,12 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
|
||||
schema_ = in.schema ();
|
||||
}
|
||||
|
||||
if (!enabled_)
|
||||
{
|
||||
TRACE_UDP ("message processing disabled for id:" << in.id ());
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
@ -200,6 +209,15 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
TRACE_UDP ("Close");
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
last_message_.clear ();
|
||||
Q_EMIT self_->close ();
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Replay:
|
||||
TRACE_UDP ("Replay");
|
||||
if (check_status (in) != Fail)
|
||||
@ -261,6 +279,42 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::SwitchConfiguration:
|
||||
{
|
||||
QByteArray configuration_name;
|
||||
in >> configuration_name;
|
||||
TRACE_UDP ("Switch Configuration name:" << configuration_name);
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->switch_configuration (QString::fromUtf8 (configuration_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Configure:
|
||||
{
|
||||
QByteArray mode;
|
||||
quint32 frequency_tolerance;
|
||||
QByteArray submode;
|
||||
bool fast_mode {false};
|
||||
quint32 tr_period {std::numeric_limits<quint32>::max ()};
|
||||
quint32 rx_df {std::numeric_limits<quint32>::max ()};
|
||||
QByteArray dx_call;
|
||||
QByteArray dx_grid;
|
||||
bool generate_messages {false};
|
||||
in >> mode >> frequency_tolerance >> submode >> fast_mode >> tr_period >> rx_df
|
||||
>> dx_call >> dx_grid >> generate_messages;
|
||||
TRACE_UDP ("Configure mode:" << mode << "frequency tolerance:" << frequency_tolerance << "submode:" << submode << "fast mode:" << fast_mode << "T/R period:" << tr_period << "rx df:" << rx_df << "dx call:" << dx_call << "dx grid:" << dx_grid << "generate messages:" << generate_messages);
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->configure (QString::fromUtf8 (mode), frequency_tolerance
|
||||
, QString::fromUtf8 (submode), fast_mode, tr_period, rx_df
|
||||
, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, generate_messages);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore
|
||||
//
|
||||
@ -438,13 +492,20 @@ void MessageClient::add_blocked_destination (QHostAddress const& a)
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::enable (bool flag)
|
||||
{
|
||||
m_->enabled_ = flag;
|
||||
}
|
||||
|
||||
void MessageClient::status_update (Frequency f, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode
|
||||
, bool tx_enabled, bool transmitting, bool decoding
|
||||
, qint32 rx_df, qint32 tx_df, QString const& de_call
|
||||
, quint32 rx_df, quint32 tx_df, QString const& de_call
|
||||
, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode
|
||||
, bool fast_mode, quint8 special_op_mode)
|
||||
, bool fast_mode, quint8 special_op_mode
|
||||
, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
{
|
||||
@ -453,8 +514,8 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
|
||||
out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
|
||||
<< tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
|
||||
<< de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
|
||||
<< fast_mode << special_op_mode;
|
||||
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode);
|
||||
<< fast_mode << special_op_mode << frequency_tolerance << tr_period << configuration_name.toUtf8 ();
|
||||
TRACE_UDP ("frequency:" << f << "mode:" << mode << "DX:" << dx_call << "report:" << report << "Tx mode:" << tx_mode << "tx_enabled:" << tx_enabled << "Tx:" << transmitting << "decoding:" << decoding << "Rx df:" << rx_df << "Tx df:" << tx_df << "DE:" << de_call << "DE grid:" << de_grid << "DX grid:" << dx_grid << "w/d t/o:" << watchdog_timeout << "sub_mode:" << sub_mode << "fast mode:" << fast_mode << "spec op mode:" << special_op_mode << "frequency tolerance:" << frequency_tolerance << "T/R period:" << tr_period << "configuration name:" << configuration_name);
|
||||
m_->send_message (out, message);
|
||||
}
|
||||
}
|
||||
@ -527,7 +588,7 @@ void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
|
||||
QByteArray ADIF {"\n<adif_ver:5>3.0.7\n<programid:6>WSJT-X\n<EOH>\n" + ADIF_record + " <EOR>"};
|
||||
QByteArray ADIF {"\n<adif_ver:5>3.1.0\n<programid:6>WSJT-X\n<EOH>\n" + ADIF_record + " <EOR>"};
|
||||
out << ADIF;
|
||||
TRACE_UDP ("ADIF:" << ADIF);
|
||||
m_->send_message (out, message);
|
||||
|
@ -47,12 +47,16 @@ public:
|
||||
// change the server port messages are sent to
|
||||
Q_SLOT void set_server_port (port_type server_port = 0u);
|
||||
|
||||
// enable incoming messages
|
||||
Q_SLOT void enable (bool);
|
||||
|
||||
// outgoing messages
|
||||
Q_SLOT void status_update (Frequency, QString const& mode, QString const& dx_call, QString const& report
|
||||
, QString const& tx_mode, bool tx_enabled, bool transmitting, bool decoding
|
||||
, qint32 rx_df, qint32 tx_df, QString const& de_call, QString const& de_grid
|
||||
, quint32 rx_df, quint32 tx_df, QString const& de_call, QString const& de_grid
|
||||
, QString const& dx_grid, bool watchdog_timeout, QString const& sub_mode
|
||||
, bool fast_mode, quint8 special_op_mode);
|
||||
, bool fast_mode, quint8 special_op_mode, quint32 frequency_tolerance
|
||||
, quint32 tr_period, QString const& configuration_name);
|
||||
Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
, QString const& mode, QString const& message, bool low_confidence
|
||||
, bool off_air);
|
||||
@ -89,6 +93,10 @@ public:
|
||||
Q_SIGNAL void reply (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message_text, bool low_confidence, quint8 modifiers);
|
||||
|
||||
// this signal is emitted if the server has requested this client to
|
||||
// close down gracefully
|
||||
Q_SIGNAL void close ();
|
||||
|
||||
// this signal is emitted if the server has requested a replay of
|
||||
// all decodes
|
||||
Q_SIGNAL void replay ();
|
||||
@ -105,6 +113,16 @@ public:
|
||||
// callsign request for the specified call
|
||||
Q_SIGNAL void highlight_callsign (QString const& callsign, QColor const& bg, QColor const& fg, bool last_only);
|
||||
|
||||
// this signal is emitted if the server has requested a
|
||||
// configuration switch
|
||||
Q_SIGNAL void switch_configuration (QString const& configuration_name);
|
||||
|
||||
// this signal is emitted if the server has requested a
|
||||
// configuration change
|
||||
Q_SIGNAL void configure (QString const& mode, quint32 frequency_tolerance, QString const& submode
|
||||
, bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call
|
||||
, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
// this signal is emitted when network errors occur or if a host
|
||||
// lookup fails
|
||||
Q_SIGNAL void error (QString const&) const;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "MessageServer.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
@ -16,6 +17,11 @@
|
||||
|
||||
#include "moc_MessageServer.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto quint32_max = std::numeric_limits<quint32>::max ();
|
||||
}
|
||||
|
||||
class MessageServer::impl
|
||||
: public QUdpSocket
|
||||
{
|
||||
@ -238,8 +244,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
bool tx_enabled {false};
|
||||
bool transmitting {false};
|
||||
bool decoding {false};
|
||||
qint32 rx_df {-1};
|
||||
qint32 tx_df {-1};
|
||||
quint32 rx_df {quint32_max};
|
||||
quint32 tx_df {quint32_max};
|
||||
QByteArray de_call;
|
||||
QByteArray de_grid;
|
||||
QByteArray dx_grid;
|
||||
@ -247,9 +253,12 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
QByteArray sub_mode;
|
||||
bool fast_mode {false};
|
||||
quint8 special_op_mode {0};
|
||||
quint32 frequency_tolerance {quint32_max};
|
||||
quint32 tr_period {quint32_max};
|
||||
QByteArray configuration_name;
|
||||
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
|
||||
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
|
||||
>> fast_mode >> special_op_mode;
|
||||
>> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
|
||||
@ -258,7 +267,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
|
||||
, QString::fromUtf8 (dx_grid), watchdog_timeout
|
||||
, QString::fromUtf8 (sub_mode), fast_mode
|
||||
, special_op_mode);
|
||||
, special_op_mode, frequency_tolerance, tr_period
|
||||
, QString::fromUtf8 (configuration_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -493,6 +503,17 @@ void MessageServer::replay (QString const& id)
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::close (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::halt_tx (QString const& id, bool auto_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
@ -541,3 +562,30 @@ void MessageServer::highlight_callsign (QString const& id, QString const& callsi
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
|
||||
out << configuration_name.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_};
|
||||
out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
|
||||
<< dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,9 @@ public:
|
||||
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
|
||||
|
||||
// ask the client with identification 'id' to close down gracefully
|
||||
Q_SLOT void close (QString const& id);
|
||||
|
||||
// ask the client with identification 'id' to replay all decodes
|
||||
Q_SLOT void replay (QString const& id);
|
||||
|
||||
@ -72,15 +75,25 @@ public:
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
|
||||
// ask the client with identification 'id' to switch to
|
||||
// configuration 'configuration_name'
|
||||
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
|
||||
// ask the client with identification 'id' to change configuration
|
||||
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
// the following signals are emitted when a client broadcasts the
|
||||
// matching message
|
||||
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
|
||||
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode);
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name);
|
||||
Q_SIGNAL void client_closed (QString const& id);
|
||||
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
|
@ -14,16 +14,35 @@
|
||||
#include "WFPalette.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
#include "models/DecodeHighlightingModel.hpp"
|
||||
#include "widgets/FrequencyLineEdit.hpp"
|
||||
#include "widgets/DateTimeEdit.hpp"
|
||||
|
||||
QItemEditorFactory * item_editor_factory ()
|
||||
namespace
|
||||
{
|
||||
static QItemEditorFactory * our_item_editor_factory = new QItemEditorFactory;
|
||||
return our_item_editor_factory;
|
||||
class ItemEditorFactory final
|
||||
: public QItemEditorFactory
|
||||
{
|
||||
public:
|
||||
ItemEditorFactory ()
|
||||
: default_factory_ {QItemEditorFactory::defaultFactory ()}
|
||||
{
|
||||
}
|
||||
|
||||
QWidget * createEditor (int user_type, QWidget * parent) const override
|
||||
{
|
||||
auto editor = QItemEditorFactory::createEditor (user_type, parent);
|
||||
return editor ? editor : default_factory_->createEditor (user_type, parent);
|
||||
}
|
||||
|
||||
private:
|
||||
QItemEditorFactory const * default_factory_;
|
||||
};
|
||||
}
|
||||
|
||||
void register_types ()
|
||||
{
|
||||
auto item_editor_factory = new ItemEditorFactory;
|
||||
QItemEditorFactory::setDefaultFactory (item_editor_factory);
|
||||
|
||||
// types in Radio.hpp are registered in their own translation unit
|
||||
// as they are needed in the wsjtx_udp shared library too
|
||||
|
||||
@ -31,9 +50,7 @@ void register_types ()
|
||||
// used as signal/slot connection arguments since the new Qt 5.5
|
||||
// Q_ENUM macro only seems to register the unqualified name
|
||||
|
||||
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::Frequency> (), new QStandardItemEditorCreator<FrequencyLineEdit> ());
|
||||
//auto frequency_delta_type_id = qRegisterMetaType<Radio::FrequencyDelta> ("FrequencyDelta");
|
||||
item_editor_factory ()->registerEditor (qMetaTypeId<Radio::FrequencyDelta> (), new QStandardItemEditorCreator<FrequencyDeltaLineEdit> ());
|
||||
item_editor_factory->registerEditor (qMetaTypeId<QDateTime> (), new QStandardItemEditorCreator<DateTimeEdit> ());
|
||||
|
||||
// Frequency list model
|
||||
qRegisterMetaTypeStreamOperators<FrequencyList_v2::Item> ("Item_v2");
|
||||
|
@ -1,9 +1,6 @@
|
||||
#ifndef META_DATA_REGISTRY_HPP__
|
||||
#define META_DATA_REGISTRY_HPP__
|
||||
|
||||
class QItemEditorFactory;
|
||||
|
||||
QItemEditorFactory * item_editor_factory ();
|
||||
void register_types ();
|
||||
|
||||
#endif
|
||||
|
@ -25,15 +25,15 @@ double constexpr Modulator::m_twoPi;
|
||||
// unsigned m_nspd=1.2*48000.0/wpm;
|
||||
// m_nspd=3072; //18.75 WPM
|
||||
|
||||
Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
|
||||
Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
|
||||
QObject * parent)
|
||||
: AudioDevice {parent}
|
||||
, m_quickClose {false}
|
||||
, m_phi {0.0}
|
||||
, m_toneSpacing {0.0}
|
||||
, m_fSpread {0.0}
|
||||
, m_frameRate {frameRate}
|
||||
, m_period {periodLengthInSeconds}
|
||||
, m_frameRate {frameRate}
|
||||
, m_state {Idle}
|
||||
, m_tuning {false}
|
||||
, m_cwLevel {false}
|
||||
@ -45,19 +45,15 @@ Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds,
|
||||
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
double frequency, double toneSpacing,
|
||||
SoundOutput * stream, Channel channel,
|
||||
bool synchronize, bool fastMode, double dBSNR, int TRperiod)
|
||||
bool synchronize, bool fastMode, double dBSNR, double TRperiod)
|
||||
{
|
||||
Q_ASSERT (stream);
|
||||
// Time according to this computer which becomes our base time
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
|
||||
if (m_state != Idle)
|
||||
{
|
||||
stop ();
|
||||
}
|
||||
|
||||
if(m_state != Idle) stop();
|
||||
m_quickClose = false;
|
||||
|
||||
m_symbolsLength = symbolsLength;
|
||||
m_isym0 = std::numeric_limits<unsigned>::max (); // big number
|
||||
m_frequency0 = 0.;
|
||||
@ -69,7 +65,9 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
m_toneSpacing = toneSpacing;
|
||||
m_bFastMode=fastMode;
|
||||
m_TRperiod=TRperiod;
|
||||
unsigned delay_ms = 1920 == m_nsps && 15 == m_period ? 500 : 1000;
|
||||
unsigned delay_ms=1000;
|
||||
if(m_nsps==1920) delay_ms=500; //FT8
|
||||
if(m_nsps==576) delay_ms=300; //FT4
|
||||
|
||||
// noise generator parameters
|
||||
if (m_addNoise) {
|
||||
@ -78,10 +76,7 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
if (m_snr > 1.0) m_fac = 3000.0 / m_snr;
|
||||
}
|
||||
|
||||
unsigned mstr = ms0 % (1000 * m_period); // ms in period
|
||||
|
||||
// round up to an exact portion of a second that allows for startup
|
||||
// delays
|
||||
// round up to an exact portion of a second that allows for startup delays
|
||||
m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000;
|
||||
|
||||
if(m_bFastMode) m_ic=0;
|
||||
@ -93,6 +88,11 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
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);
|
||||
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
|
||||
Synchronizing : Active));
|
||||
@ -149,6 +149,8 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
|
||||
qint64 framesGenerated (0);
|
||||
|
||||
// if(m_ic==0) qDebug() << "Modulator::readData" << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case Synchronizing:
|
||||
@ -170,17 +172,19 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
case Active:
|
||||
{
|
||||
unsigned int isym=0;
|
||||
// qDebug() << "Mod A" << m_toneSpacing << m_ic;
|
||||
|
||||
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
|
||||
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode);
|
||||
if(m_TRperiod==3) slowCwId=false;
|
||||
if(m_TRperiod==3.0) slowCwId=false;
|
||||
bool fastCwId=false;
|
||||
static bool bCwId=false;
|
||||
qint64 ms = QDateTime::currentMSecsSinceEpoch();
|
||||
float tsec=0.001*(ms % (1000*m_TRperiod));
|
||||
float tsec=0.001*(ms % int(1000*m_TRperiod));
|
||||
if(m_bFastMode and (icw[0]>0) and (tsec > (m_TRperiod-5.0))) fastCwId=true;
|
||||
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
|
||||
|
||||
// qDebug() << "Mod A" << m_ic << isym << tsec;
|
||||
|
||||
if(slowCwId or fastCwId) { // Transmit CW ID?
|
||||
m_dphi = m_twoPi*m_frequency/m_frameRate;
|
||||
if(m_bFastMode and !bCwId) {
|
||||
@ -247,15 +251,15 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
i1= m_symbolsLength * 4.0 * m_nsps;
|
||||
}
|
||||
if(m_bFastMode and !m_tuning) {
|
||||
i1=m_TRperiod*48000 - 24000;
|
||||
i1=m_TRperiod*48000.0 - 24000.0;
|
||||
i0=i1-816;
|
||||
}
|
||||
|
||||
qint16 sample;
|
||||
|
||||
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
|
||||
isym=0;
|
||||
if(!m_tuning and m_TRperiod!=3) isym=m_ic / (4.0 * m_nsps); //Actual
|
||||
//fsample=48000
|
||||
if(!m_tuning and m_TRperiod!=3.0) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
|
||||
if(m_bFastMode) isym=isym%m_symbolsLength;
|
||||
if (isym != m_isym0 || m_frequency != m_frequency0) {
|
||||
if(itone[0]>=100) {
|
||||
@ -267,8 +271,6 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
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_isym0 = isym;
|
||||
m_frequency0 = m_frequency; //???
|
||||
@ -289,9 +291,12 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
if (m_ic > i1) m_amp = 0.0;
|
||||
|
||||
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);
|
||||
++framesGenerated;
|
||||
@ -309,6 +314,14 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
|
||||
m_frequency0 = m_frequency;
|
||||
// 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 ();
|
||||
}
|
||||
// fall through
|
||||
|
@ -23,7 +23,7 @@ class Modulator
|
||||
public:
|
||||
enum ModulatorState {Synchronizing, Active, Idle};
|
||||
|
||||
Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent = nullptr);
|
||||
Modulator (unsigned frameRate, double periodLengthInSeconds, QObject * parent = nullptr);
|
||||
|
||||
void close () override;
|
||||
|
||||
@ -31,13 +31,14 @@ public:
|
||||
double frequency () const {return m_frequency;}
|
||||
bool isActive () const {return m_state != Idle;}
|
||||
void setSpread(double s) {m_fSpread=s;}
|
||||
void setTRPeriod(unsigned p) {m_period=p;}
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
void set_nsym(int n) {m_symbolsLength=n;}
|
||||
void set_ms0(qint64 ms) {m_ms0=ms;}
|
||||
|
||||
Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, double frequency,
|
||||
double toneSpacing, SoundOutput *, Channel = Mono,
|
||||
bool synchronize = true, bool fastMode = false,
|
||||
double dBSNR = 99., int TRperiod=60);
|
||||
double dBSNR = 99., double TRperiod=60.0);
|
||||
Q_SLOT void stop (bool quick = false);
|
||||
Q_SLOT void tune (bool newState = true);
|
||||
Q_SLOT void setFrequency (double newFrequency) {m_frequency = newFrequency;}
|
||||
@ -71,13 +72,14 @@ private:
|
||||
double m_fac;
|
||||
double m_toneSpacing;
|
||||
double m_fSpread;
|
||||
double m_TRperiod;
|
||||
double m_period;
|
||||
|
||||
qint64 m_silentFrames;
|
||||
qint32 m_TRperiod;
|
||||
qint64 m_ms0;
|
||||
qint16 m_ramp;
|
||||
|
||||
unsigned m_frameRate;
|
||||
unsigned m_period;
|
||||
ModulatorState volatile m_state;
|
||||
|
||||
bool volatile m_tuning;
|
||||
|
@ -65,6 +65,8 @@ namespace
|
||||
class NameDialog final
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NameDialog (QString const& current_name,
|
||||
QStringList const& current_names,
|
||||
@ -112,6 +114,8 @@ namespace
|
||||
class ExistingNameDialog final
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ExistingNameDialog (QStringList const& current_names, QWidget * parent = nullptr)
|
||||
: QDialog {parent}
|
||||
@ -155,13 +159,16 @@ public:
|
||||
bool exit ();
|
||||
|
||||
QSettings settings_;
|
||||
QString current_;
|
||||
|
||||
// switch to this configuration
|
||||
void select_configuration (QString const& target_name);
|
||||
|
||||
private:
|
||||
using Dictionary = QMap<QString, QVariant>;
|
||||
|
||||
// create a configuration maintenance sub menu
|
||||
QMenu * create_sub_menu (QMainWindow * main_window,
|
||||
QMenu * parent,
|
||||
QMenu * create_sub_menu (QMenu * parent,
|
||||
QString const& menu_title,
|
||||
QActionGroup * = nullptr);
|
||||
|
||||
@ -171,32 +178,32 @@ private:
|
||||
// write the settings values from the dictionary to the current group
|
||||
void load_from (Dictionary const&, bool add_placeholder = true);
|
||||
|
||||
// switch to this configuration
|
||||
void select_configuration (QMainWindow *, QMenu const *);
|
||||
|
||||
// clone this configuration
|
||||
void clone_configuration (QMainWindow * main_window, QMenu *, QMenu const *);
|
||||
void clone_configuration (QMenu *, QMenu const *);
|
||||
|
||||
// update this configuration from another
|
||||
void clone_into_configuration (QMainWindow *, QMenu const *);
|
||||
void clone_into_configuration (QMenu const *);
|
||||
|
||||
// reset configuration to default values
|
||||
void reset_configuration (QMainWindow *, QMenu const *);
|
||||
void reset_configuration (QMenu const *);
|
||||
|
||||
// change configuration name
|
||||
void rename_configuration (QMainWindow *, QMenu *);
|
||||
void rename_configuration (QMenu *);
|
||||
|
||||
// remove a configuration
|
||||
void delete_configuration (QMainWindow *, QMenu *);
|
||||
void delete_configuration (QMenu *);
|
||||
|
||||
// action to take on restart
|
||||
enum class RepositionType {unchanged, replace, save_and_replace};
|
||||
void restart (RepositionType);
|
||||
|
||||
MultiSettings const * parent_; // required for emitting signals
|
||||
QMainWindow * main_window_;
|
||||
bool name_change_emit_pending_; // delayed until menu built
|
||||
|
||||
QFont original_font_;
|
||||
QString current_;
|
||||
|
||||
// action to take on restart
|
||||
enum class RepositionType {unchanged, replace, save_and_replace} reposition_type_;
|
||||
RepositionType reposition_type_;
|
||||
Dictionary new_settings_;
|
||||
bool exit_flag_; // false means loop around with new
|
||||
// configuration
|
||||
@ -263,6 +270,16 @@ void MultiSettings::create_menu_actions (QMainWindow * main_window, QMenu * menu
|
||||
m_->create_menu_actions (main_window, menu);
|
||||
}
|
||||
|
||||
void MultiSettings::select_configuration (QString const& name)
|
||||
{
|
||||
m_->select_configuration (name);
|
||||
}
|
||||
|
||||
QString MultiSettings::configuration_name () const
|
||||
{
|
||||
return m_->current_;
|
||||
}
|
||||
|
||||
bool MultiSettings::exit ()
|
||||
{
|
||||
return m_->exit ();
|
||||
@ -271,6 +288,7 @@ bool MultiSettings::exit ()
|
||||
MultiSettings::impl::impl (MultiSettings const * parent, QString const& config_name)
|
||||
: settings_ {settings_path (), QSettings::IniFormat}
|
||||
, parent_ {parent}
|
||||
, main_window_ {nullptr}
|
||||
, name_change_emit_pending_ {true}
|
||||
, reposition_type_ {RepositionType::unchanged}
|
||||
, exit_flag_ {true}
|
||||
@ -407,13 +425,14 @@ bool MultiSettings::impl::reposition ()
|
||||
// and, reset
|
||||
void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu * menu)
|
||||
{
|
||||
main_window_ = main_window;
|
||||
auto const& current_group = settings_.group ();
|
||||
if (current_group.size ()) settings_.endGroup ();
|
||||
SettingsGroup alternatives {&settings_, multi_settings_root_group};
|
||||
// get the current configuration name
|
||||
auto const& current_configuration_name = settings_.value (multi_settings_current_name_key, tr (default_string)).toString ();
|
||||
// add the default configuration sub menu
|
||||
QMenu * default_menu = create_sub_menu (main_window, menu, current_configuration_name, configurations_group_);
|
||||
QMenu * default_menu = create_sub_menu (menu, current_configuration_name, configurations_group_);
|
||||
// and set as the current configuration
|
||||
default_menu->menuAction ()->setChecked (true);
|
||||
|
||||
@ -423,7 +442,7 @@ void MultiSettings::impl::create_menu_actions (QMainWindow * main_window, QMenu
|
||||
// add all the other configurations
|
||||
for (auto const& configuration_name: available_configurations)
|
||||
{
|
||||
create_sub_menu (main_window, menu, configuration_name, configurations_group_);
|
||||
create_sub_menu (menu, configuration_name, configurations_group_);
|
||||
}
|
||||
|
||||
if (current_group.size ()) settings_.beginGroup (current_group);
|
||||
@ -446,8 +465,7 @@ bool MultiSettings::impl::exit ()
|
||||
return reposition ();
|
||||
}
|
||||
|
||||
QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
|
||||
QMenu * parent_menu,
|
||||
QMenu * MultiSettings::impl::create_sub_menu (QMenu * parent_menu,
|
||||
QString const& menu_title,
|
||||
QActionGroup * action_group)
|
||||
{
|
||||
@ -456,7 +474,7 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
|
||||
sub_menu->menuAction ()->setCheckable (true);
|
||||
|
||||
// populate sub-menu actions before showing
|
||||
connect (sub_menu, &QMenu::aboutToShow, [this, main_window, parent_menu, sub_menu] () {
|
||||
connect (sub_menu, &QMenu::aboutToShow, [this, parent_menu, sub_menu] () {
|
||||
// depopulate before populating and showing because on Mac OS X
|
||||
// there is an issue with depopulating in QMenu::aboutToHide()
|
||||
// with connections being disconnected before they are actioned
|
||||
@ -470,16 +488,16 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
|
||||
{
|
||||
auto select_action = new QAction {tr ("&Switch To"), this};
|
||||
sub_menu->addAction (select_action);
|
||||
connect (select_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
|
||||
select_configuration (main_window, sub_menu);
|
||||
connect (select_action, &QAction::triggered, [this, sub_menu] (bool) {
|
||||
select_configuration (sub_menu->title ());
|
||||
});
|
||||
sub_menu->addSeparator ();
|
||||
}
|
||||
|
||||
auto clone_action = new QAction {tr ("&Clone"), this};
|
||||
sub_menu->addAction (clone_action);
|
||||
connect (clone_action, &QAction::triggered, [this, main_window, parent_menu, sub_menu] (bool) {
|
||||
clone_configuration (main_window, parent_menu, sub_menu);
|
||||
connect (clone_action, &QAction::triggered, [this, parent_menu, sub_menu] (bool) {
|
||||
clone_configuration (parent_menu, sub_menu);
|
||||
});
|
||||
|
||||
auto const& current_group = settings_.group ();
|
||||
@ -489,29 +507,29 @@ QMenu * MultiSettings::impl::create_sub_menu (QMainWindow * main_window,
|
||||
{
|
||||
auto clone_into_action = new QAction {tr ("Clone &Into ..."), this};
|
||||
sub_menu->addAction (clone_into_action);
|
||||
connect (clone_into_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
|
||||
clone_into_configuration (main_window, sub_menu);
|
||||
connect (clone_into_action, &QAction::triggered, [this, sub_menu] (bool) {
|
||||
clone_into_configuration (sub_menu);
|
||||
});
|
||||
}
|
||||
if (current_group.size ()) settings_.beginGroup (current_group);
|
||||
auto reset_action = new QAction {tr ("R&eset"), this};
|
||||
sub_menu->addAction (reset_action);
|
||||
connect (reset_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
|
||||
reset_configuration (main_window, sub_menu);
|
||||
connect (reset_action, &QAction::triggered, [this, sub_menu] (bool) {
|
||||
reset_configuration (sub_menu);
|
||||
});
|
||||
|
||||
auto rename_action = new QAction {tr ("&Rename ..."), this};
|
||||
sub_menu->addAction (rename_action);
|
||||
connect (rename_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
|
||||
rename_configuration (main_window, sub_menu);
|
||||
connect (rename_action, &QAction::triggered, [this, sub_menu] (bool) {
|
||||
rename_configuration (sub_menu);
|
||||
});
|
||||
|
||||
if (!is_current)
|
||||
{
|
||||
auto delete_action = new QAction {tr ("&Delete"), this};
|
||||
sub_menu->addAction (delete_action);
|
||||
connect (delete_action, &QAction::triggered, [this, main_window, sub_menu] (bool) {
|
||||
delete_configuration (main_window, sub_menu);
|
||||
connect (delete_action, &QAction::triggered, [this, sub_menu] (bool) {
|
||||
delete_configuration (sub_menu);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -554,11 +572,9 @@ void MultiSettings::impl::load_from (Dictionary const& dictionary, bool add_plac
|
||||
settings_.sync ();
|
||||
}
|
||||
|
||||
void MultiSettings::impl::select_configuration (QMainWindow * main_window, QMenu const * menu)
|
||||
void MultiSettings::impl::select_configuration (QString const& target_name)
|
||||
{
|
||||
auto const& target_name = menu->title ();
|
||||
|
||||
if (target_name != current_)
|
||||
if (main_window_ && target_name != current_)
|
||||
{
|
||||
{
|
||||
auto const& current_group = settings_.group ();
|
||||
@ -573,13 +589,11 @@ void MultiSettings::impl::select_configuration (QMainWindow * main_window, QMenu
|
||||
// and set up the restart
|
||||
current_ = target_name;
|
||||
Q_EMIT parent_->configurationNameChanged (unescape_ampersands (current_));
|
||||
reposition_type_ = RepositionType::save_and_replace;
|
||||
exit_flag_ = false;
|
||||
main_window->close ();
|
||||
restart (RepositionType::save_and_replace);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSettings::impl::clone_configuration (QMainWindow * main_window, QMenu * parent_menu, QMenu const * menu)
|
||||
void MultiSettings::impl::clone_configuration (QMenu * parent_menu, QMenu const * menu)
|
||||
{
|
||||
auto const& current_group = settings_.group ();
|
||||
if (current_group.size ()) settings_.endGroup ();
|
||||
@ -612,12 +626,15 @@ void MultiSettings::impl::clone_configuration (QMainWindow * main_window, QMenu
|
||||
load_from (source_settings);
|
||||
|
||||
// insert the new configuration sub menu in the parent menu
|
||||
create_sub_menu (main_window, parent_menu, new_name, configurations_group_);
|
||||
create_sub_menu (parent_menu, new_name, configurations_group_);
|
||||
if (current_group.size ()) settings_.beginGroup (current_group);
|
||||
}
|
||||
|
||||
void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, QMenu const * menu)
|
||||
void MultiSettings::impl::clone_into_configuration (QMenu const * menu)
|
||||
{
|
||||
Q_ASSERT (main_window_);
|
||||
if (!main_window_) return;
|
||||
|
||||
auto const& current_group = settings_.group ();
|
||||
if (current_group.size ()) settings_.endGroup ();
|
||||
auto const& target_name = menu->title ();
|
||||
@ -638,11 +655,12 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
|
||||
}
|
||||
|
||||
// pick a source configuration
|
||||
ExistingNameDialog dialog {sources, main_window};
|
||||
ExistingNameDialog dialog {sources, main_window_};
|
||||
if (sources.size () && (1 == sources.size () || QDialog::Accepted == dialog.exec ()))
|
||||
{
|
||||
QString source_name {1 == sources.size () ? sources.at (0) : dialog.name ()};
|
||||
if (MessageBox::Yes == MessageBox::query_message (main_window,
|
||||
if (main_window_
|
||||
&& MessageBox::Yes == MessageBox::query_message (main_window_,
|
||||
tr ("Clone Into Configuration"),
|
||||
tr ("Confirm overwrite of all values for configuration \"%1\" with values from \"%2\"?")
|
||||
.arg (unescape_ampersands (target_name))
|
||||
@ -665,9 +683,7 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
|
||||
if (target_name == current_)
|
||||
{
|
||||
// restart with new settings
|
||||
reposition_type_ = RepositionType::replace;
|
||||
exit_flag_ = false;
|
||||
main_window->close ();
|
||||
restart (RepositionType::replace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -682,11 +698,15 @@ void MultiSettings::impl::clone_into_configuration (QMainWindow * main_window, Q
|
||||
if (current_group.size ()) settings_.beginGroup (current_group);
|
||||
}
|
||||
|
||||
void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu const * menu)
|
||||
void MultiSettings::impl::reset_configuration (QMenu const * menu)
|
||||
{
|
||||
Q_ASSERT (main_window_);
|
||||
if (!main_window_) return;
|
||||
|
||||
auto const& target_name = menu->title ();
|
||||
|
||||
if (MessageBox::Yes != MessageBox::query_message (main_window,
|
||||
if (!main_window_
|
||||
|| MessageBox::Yes != MessageBox::query_message (main_window_,
|
||||
tr ("Reset Configuration"),
|
||||
tr ("Confirm reset to default values for configuration \"%1\"?")
|
||||
.arg (unescape_ampersands (target_name))))
|
||||
@ -697,10 +717,8 @@ void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu
|
||||
if (target_name == current_)
|
||||
{
|
||||
// restart with default settings
|
||||
reposition_type_ = RepositionType::replace;
|
||||
new_settings_.clear ();
|
||||
exit_flag_ = false;
|
||||
main_window->close ();
|
||||
restart (RepositionType::replace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -717,8 +735,11 @@ void MultiSettings::impl::reset_configuration (QMainWindow * main_window, QMenu
|
||||
}
|
||||
}
|
||||
|
||||
void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu * menu)
|
||||
void MultiSettings::impl::rename_configuration (QMenu * menu)
|
||||
{
|
||||
Q_ASSERT (main_window_);
|
||||
if (!main_window_) return;
|
||||
|
||||
auto const& current_group = settings_.group ();
|
||||
if (current_group.size ()) settings_.endGroup ();
|
||||
auto const& target_name = menu->title ();
|
||||
@ -729,7 +750,7 @@ void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu
|
||||
invalid_names << settings_.value (multi_settings_current_name_key).toString ();
|
||||
|
||||
// get the new name
|
||||
NameDialog dialog {target_name, invalid_names, main_window};
|
||||
NameDialog dialog {target_name, invalid_names, main_window_};
|
||||
if (QDialog::Accepted == dialog.exec ())
|
||||
{
|
||||
if (target_name == current_)
|
||||
@ -760,8 +781,9 @@ void MultiSettings::impl::rename_configuration (QMainWindow * main_window, QMenu
|
||||
if (current_group.size ()) settings_.beginGroup (current_group);
|
||||
}
|
||||
|
||||
void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu * menu)
|
||||
void MultiSettings::impl::delete_configuration (QMenu * menu)
|
||||
{
|
||||
Q_ASSERT (main_window_);
|
||||
auto const& target_name = menu->title ();
|
||||
|
||||
if (target_name == current_)
|
||||
@ -770,7 +792,8 @@ void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MessageBox::Yes != MessageBox::query_message (main_window,
|
||||
if (!main_window_
|
||||
|| MessageBox::Yes != MessageBox::query_message (main_window_,
|
||||
tr ("Delete Configuration"),
|
||||
tr ("Confirm deletion of configuration \"%1\"?")
|
||||
.arg (unescape_ampersands (target_name))))
|
||||
@ -789,3 +812,12 @@ void MultiSettings::impl::delete_configuration (QMainWindow * main_window, QMenu
|
||||
// update the menu
|
||||
menu->deleteLater ();
|
||||
}
|
||||
|
||||
void MultiSettings::impl::restart (RepositionType type)
|
||||
{
|
||||
Q_ASSERT (main_window_);
|
||||
reposition_type_ = type;
|
||||
exit_flag_ = false;
|
||||
main_window_->close ();
|
||||
main_window_ = nullptr;
|
||||
}
|
||||
|
@ -80,6 +80,10 @@ public:
|
||||
// action is triggered.
|
||||
void create_menu_actions (QMainWindow *, QMenu *);
|
||||
|
||||
// switch to this configuration if it exists
|
||||
Q_SLOT void select_configuration (QString const& name);
|
||||
QString configuration_name () const;
|
||||
|
||||
// Access to the QSettings object instance.
|
||||
QSettings * settings ();
|
||||
|
||||
|
22
NEWS
22
NEWS
@ -13,6 +13,28 @@
|
||||
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.0.1
|
||||
February 25, 2019
|
||||
---------------------
|
||||
|
@ -116,15 +116,18 @@
|
||||
* Tx Enabled bool
|
||||
* Transmitting bool
|
||||
* Decoding bool
|
||||
* Rx DF qint32
|
||||
* Tx DF qint32
|
||||
* Rx DF quint32
|
||||
* Tx DF quint32
|
||||
* DE call utf8
|
||||
* DE grid utf8
|
||||
* DX grid utf8
|
||||
* Tx Watchdog bool
|
||||
* Sub-mode utf8
|
||||
* Fast mode bool
|
||||
* Special operation mode quint8
|
||||
* Special Operation Mode quint8
|
||||
* Frequency Tolerance quint32
|
||||
* T/R Period quint32
|
||||
* Configuration Name utf8
|
||||
*
|
||||
* WSJT-X sends this status message when various internal state
|
||||
* changes to allow the server to track the relevant state of each
|
||||
@ -133,19 +136,22 @@
|
||||
*
|
||||
* Application start up,
|
||||
* "Enable Tx" button status changes,
|
||||
* Dial frequency changes,
|
||||
* Changes to the "DX Call" field,
|
||||
* Operating mode, sub-mode or fast mode changes,
|
||||
* Transmit mode changed (in dual JT9+JT65 mode),
|
||||
* Changes to the "Rpt" spinner,
|
||||
* After an old decodes replay sequence (see Replay below),
|
||||
* When switching between Tx and Rx mode,
|
||||
* At the start and end of decoding,
|
||||
* When the Rx DF changes,
|
||||
* When the Tx DF changes,
|
||||
* When settings are exited,
|
||||
* When the DX call or grid changes,
|
||||
* When the Tx watchdog is set or reset.
|
||||
* dial frequency changes,
|
||||
* changes to the "DX Call" field,
|
||||
* operating mode, sub-mode or fast mode changes,
|
||||
* transmit mode changed (in dual JT9+JT65 mode),
|
||||
* changes to the "Rpt" spinner,
|
||||
* after an old decodes replay sequence (see Replay below),
|
||||
* when switching between Tx and Rx mode,
|
||||
* at the start and end of decoding,
|
||||
* when the Rx DF changes,
|
||||
* when the Tx DF changes,
|
||||
* when settings are exited,
|
||||
* when the DX call or grid changes,
|
||||
* when the Tx watchdog is set or reset,
|
||||
* when the frequency tolerance is changed,
|
||||
* when the T/R period is changed,
|
||||
* when the configuration name changes.
|
||||
*
|
||||
* The Special operation mode is an enumeration that indicates the
|
||||
* setting selected in the WSJT-X "Settings->Advanced->Special
|
||||
@ -159,6 +165,10 @@
|
||||
* 5 -> FOX
|
||||
* 6 -> HOUND
|
||||
*
|
||||
* The Frequency Tolerance and T/R period fields may have a value
|
||||
* of the maximum quint32 value which implies the field is not
|
||||
* applicable.
|
||||
*
|
||||
*
|
||||
* Decode Out 2 quint32
|
||||
* Id (unique key) utf8
|
||||
@ -271,11 +281,12 @@
|
||||
* button.
|
||||
*
|
||||
*
|
||||
* Close Out 6 quint32
|
||||
* Close Out/In 6 quint32
|
||||
* Id (unique key) utf8
|
||||
*
|
||||
* Close is sent by a client immediately prior to it shutting
|
||||
* down gracefully.
|
||||
* down gracefully. When sent by a server it requests the target
|
||||
* client to close down gracefully.
|
||||
*
|
||||
*
|
||||
* Replay In 7 quint32
|
||||
@ -414,6 +425,35 @@
|
||||
* the last instance only instead of all instances of the
|
||||
* specified call be highlighted or have it's highlighting
|
||||
* cleared.
|
||||
*
|
||||
*
|
||||
* SwitchConfiguration In 14 quint32
|
||||
* Id (unique key) utf8
|
||||
* Configuration Name utf8
|
||||
*
|
||||
* The server may send this message at any time. The message
|
||||
* specifies the name of the configuration to switch to. The new
|
||||
* configuration must exist.
|
||||
*
|
||||
*
|
||||
* Configure In 15 quint32
|
||||
* Id (unique key) utf8
|
||||
* Mode utf8
|
||||
* Frequency Tolerance quint32
|
||||
* Submode utf8
|
||||
* Fast Mode bool
|
||||
* T/R Period quint32
|
||||
* Rx DF quint32
|
||||
* DX Call utf8
|
||||
* DX Grid utf8
|
||||
* Generate Messages bool
|
||||
*
|
||||
* The server may send this message at any time. The message
|
||||
* specifies various configuration options. For utf8 string
|
||||
* fields an empty value implies no change, for the quint32 Rx DF
|
||||
* and Frequency Tolerance fields the maximum quint32 value
|
||||
* implies no change. Invalid or unrecognized values will be
|
||||
* silently ignored.
|
||||
*/
|
||||
|
||||
#include <QDataStream>
|
||||
@ -443,6 +483,8 @@ namespace NetworkMessage
|
||||
Location,
|
||||
LoggedADIF,
|
||||
HighlightCallsign,
|
||||
SwitchConfiguration,
|
||||
Configure,
|
||||
maximum_message_type_ // ONLY add new message types
|
||||
// immediately before here
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <QDebug>
|
||||
#include <objbase.h>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "qt_helpers.hpP"
|
||||
|
||||
@ -109,6 +110,15 @@ OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped
|
||||
{
|
||||
}
|
||||
|
||||
// returns false on time out
|
||||
bool OmniRigTransceiver::await_notification_with_timeout (int timeout)
|
||||
{
|
||||
QEventLoop el;
|
||||
connect (this, &OmniRigTransceiver::notified, &el, [&el] () {el.exit (1);});
|
||||
QTimer::singleShot (timeout, Qt::CoarseTimer, &el, [&el] () {el.exit (0);});
|
||||
return 1 == el.exec (); // wait for notify or timer
|
||||
}
|
||||
|
||||
int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "starting");
|
||||
@ -182,19 +192,26 @@ int OmniRigTransceiver::do_start ()
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number_).toLocal8Bit ());
|
||||
|
||||
offline_timer_.reset (new QTimer);
|
||||
offline_timer_->setSingleShot (true);
|
||||
offline_timer_->setInterval (5 * 1000);
|
||||
connect (&*offline_timer_, &QTimer::timeout, this, &OmniRigTransceiver::timeout_check);
|
||||
|
||||
for (unsigned tries {0}; tries < 10; ++tries)
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
QThread::msleep (100); // wait until OmniRig polls the rig
|
||||
if (OmniRig::ST_ONLINE == rig_->Status ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
await_notification_with_timeout (1000);
|
||||
}
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
throw_qstring ("OmniRig: " + rig_->StatusStr ());
|
||||
}
|
||||
auto f = rig_->GetRxFrequency ();
|
||||
int resolution {0};
|
||||
if (f)
|
||||
for (int i = 0; (f == 0) && (i < 5); ++i)
|
||||
{
|
||||
await_notification_with_timeout (1000);
|
||||
f = rig_->GetRxFrequency ();
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
int resolution {0};
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
|
||||
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
@ -203,6 +220,7 @@ int OmniRigTransceiver::do_start ()
|
||||
// can't query VFO but can set explicitly
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
f = state ().frequency ();
|
||||
if (f % 10) return resolution; // 1Hz resolution
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
@ -221,6 +239,11 @@ int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
if (!await_notification_with_timeout (1000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
switch (rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
case -5: resolution = -1; break; // 10Hz truncated
|
||||
@ -244,6 +267,11 @@ int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
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
|
||||
@ -264,19 +292,9 @@ int OmniRigTransceiver::do_start ()
|
||||
update_rx_frequency (f);
|
||||
return resolution;
|
||||
}
|
||||
}
|
||||
throw_qstring (tr ("OmniRig: Initialization timed out"));
|
||||
return 0; // keep compiler happy
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_stop ()
|
||||
{
|
||||
if (offline_timer_)
|
||||
{
|
||||
offline_timer_->stop ();
|
||||
offline_timer_.reset ();
|
||||
}
|
||||
|
||||
QThread::msleep (200); // leave some time for pending
|
||||
// commands at the server end
|
||||
if (port_)
|
||||
@ -300,17 +318,6 @@ void OmniRigTransceiver::do_stop ()
|
||||
TRACE_CAT ("OmniRigTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_sync (bool force_signal, bool /*no_poll*/)
|
||||
{
|
||||
// nothing much we can do here, we just have to let OmniRig do its
|
||||
// stuff and its first poll should send us and update that will
|
||||
// trigger a update signal from us. Any attempt to query OmniRig
|
||||
// leads to a whole mess of trouble since its internal state is
|
||||
// garbage until it has done its first rig poll.
|
||||
send_update_signal_ = force_signal;
|
||||
update_complete ();
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + help + ')');
|
||||
@ -319,16 +326,20 @@ void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString
|
||||
|
||||
void OmniRigTransceiver::handle_visible_change ()
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_rig_type_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig type change: rig =" << rig_number);
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_->RigType ())
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
@ -338,48 +349,47 @@ void OmniRigTransceiver::handle_rig_type_change (int rig_number)
|
||||
|
||||
void OmniRigTransceiver::handle_status_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"status change for rig %1"}.arg (rig_number).toLocal8Bit ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
auto const& status = rig_->StatusStr ().toLocal8Bit ();
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig status change: new status for rig %1 = "}.arg (rig_number).toLocal8Bit () << status);
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig status change: new status = " << status);
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
if (!offline_timer_->isActive ())
|
||||
{
|
||||
offline_timer_->start (); // give OmniRig time to recover
|
||||
}
|
||||
offline ("Rig went offline");
|
||||
}
|
||||
else
|
||||
{
|
||||
offline_timer_->stop ();
|
||||
update_rx_frequency (rig_->GetRxFrequency ());
|
||||
update_complete ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig frequency:" << state ().frequency ());
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// update_rx_frequency (rig_->GetRxFrequency ());
|
||||
// update_complete ();
|
||||
// TRACE_CAT ("OmniRigTransceiver", "frequency:" << state ().frequency ());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::timeout_check ()
|
||||
{
|
||||
offline ("Rig went offline");
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
{
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig params change: params = 0x%1 for rig %2"}
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
|
||||
.arg (params, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ()
|
||||
<< "state before:" << state ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
// starting_ = false;
|
||||
TransceiverState old_state {state ()};
|
||||
auto need_frequency = false;
|
||||
// state_.online = true; // sometimes we don't get an initial
|
||||
// // OmniRig::ST_ONLINE status change
|
||||
// // event
|
||||
|
||||
if (params & OmniRig::PM_VFOAA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
|
||||
update_split (false);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
@ -387,6 +397,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
}
|
||||
if (params & OmniRig::PM_VFOAB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
|
||||
update_split (true);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
@ -394,6 +405,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
|
||||
update_split (true);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
@ -401,6 +413,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
|
||||
update_split (false);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
@ -408,154 +421,195 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
}
|
||||
if (params & OmniRig::PM_VFOA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOA");
|
||||
reversed_ = false;
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_VFOB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOB");
|
||||
reversed_ = true;
|
||||
need_frequency = true;
|
||||
}
|
||||
|
||||
if (params & OmniRig::PM_FREQ)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ");
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
if (need_frequency)
|
||||
{
|
||||
if (readable_params_ & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
need_frequency = false;
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
need_frequency = false;
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
if (need_frequency && (readable_params_ & OmniRig::PM_FREQ)
|
||||
&& !state ().ptt ())
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQ && !state ().ptt ())
|
||||
{
|
||||
update_rx_frequency (rig_->Freq ());
|
||||
auto f = rig_->Freq ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ = " << f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_PITCH)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "PITCH");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFFSET)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
|
||||
}
|
||||
if (params & OmniRig::PM_RIT0)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RIT0");
|
||||
}
|
||||
if (params & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq ();
|
||||
auto m = map_mode (rig_->Mode ());
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"VFOEQUAL f=%1 m=%2"}.arg (f).arg (m));
|
||||
update_rx_frequency (f);
|
||||
update_other_frequency (f);
|
||||
update_mode (map_mode (rig_->Mode ()));
|
||||
update_mode (m);
|
||||
}
|
||||
if (params & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
auto temp = state ().tx_frequency ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOSWAP");
|
||||
auto f = state ().tx_frequency ();
|
||||
update_other_frequency (state ().frequency ());
|
||||
update_rx_frequency (temp);
|
||||
update_rx_frequency (f);
|
||||
update_mode (map_mode (rig_->Mode ()));
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
|
||||
update_split (true);
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
|
||||
update_split (false);
|
||||
}
|
||||
if (params & OmniRig::PM_RITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITON");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_XITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITON");
|
||||
}
|
||||
if (params & OmniRig::PM_XITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_RX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RX");
|
||||
update_PTT (false);
|
||||
}
|
||||
if (params & OmniRig::PM_TX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "TX");
|
||||
update_PTT ();
|
||||
}
|
||||
if (params & OmniRig::PM_CW_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW-R");
|
||||
update_mode (CW_R);
|
||||
}
|
||||
if (params & OmniRig::PM_CW_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW");
|
||||
update_mode (CW);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "USB");
|
||||
update_mode (USB);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "LSB");
|
||||
update_mode (LSB);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
|
||||
update_mode (DIG_U);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
|
||||
update_mode (DIG_L);
|
||||
}
|
||||
if (params & OmniRig::PM_AM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "AM");
|
||||
update_mode (AM);
|
||||
}
|
||||
if (params & OmniRig::PM_FM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FM");
|
||||
update_mode (FM);
|
||||
}
|
||||
|
||||
@ -566,6 +620,7 @@ void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig params change: state after:" << state ());
|
||||
}
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply)
|
||||
@ -573,8 +628,10 @@ void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& co
|
||||
(void)command;
|
||||
(void)reply;
|
||||
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
|
||||
<< "with reply" << reply.toString ().toLocal8Bit ()
|
||||
<< QString ("for rig %1").arg (rig_number).toLocal8Bit ());
|
||||
|
@ -38,10 +38,11 @@ public:
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
void do_sync (bool force_signal, bool no_poll) override;
|
||||
|
||||
private:
|
||||
Q_SLOT void timeout_check ();
|
||||
bool await_notification_with_timeout (int timeout);
|
||||
Q_SIGNAL void notified () const;
|
||||
// Q_SLOT void timeout_check ();
|
||||
Q_SLOT void handle_COM_exception (int, QString, QString, QString);
|
||||
Q_SLOT void handle_visible_change ();
|
||||
Q_SLOT void handle_rig_type_change (int rig_number);
|
||||
@ -62,7 +63,7 @@ private:
|
||||
QString rig_type_;
|
||||
int readable_params_;
|
||||
int writable_params_;
|
||||
QScopedPointer<QTimer> offline_timer_;
|
||||
// QScopedPointer<QTimer> offline_timer_;
|
||||
bool send_update_signal_;
|
||||
bool reversed_; // some rigs can reverse VFOs
|
||||
};
|
||||
|
@ -129,29 +129,34 @@ bool PollingTransceiver::do_pre_update ()
|
||||
return true;
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_sync (bool force_signal, bool no_poll)
|
||||
void PollingTransceiver::handle_timeout ()
|
||||
{
|
||||
if (!no_poll) poll (); // tell sub-classes to update our state
|
||||
QString message;
|
||||
bool force_signal {false};
|
||||
|
||||
// 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.
|
||||
// we must catch all exceptions here since we are called by Qt and
|
||||
// inform our parent of the failure via the offline() message
|
||||
try
|
||||
{
|
||||
do_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 (force_signal || state () == next_state_ || !retries_)
|
||||
if (state () == next_state_ || !retries_)
|
||||
{
|
||||
// our client wants a signal regardless
|
||||
// or the expected state has arrived
|
||||
// or there are no more retries
|
||||
// the expected state has arrived or there are no more
|
||||
// retries
|
||||
force_signal = true;
|
||||
}
|
||||
}
|
||||
else if (force_signal || state () != last_signalled_state_)
|
||||
else if (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
|
||||
// here is the normal passive polling path where state has
|
||||
// changed asynchronously
|
||||
force_signal = true;
|
||||
}
|
||||
@ -165,17 +170,6 @@ void PollingTransceiver::do_sync (bool force_signal, bool no_poll)
|
||||
update_complete (true);
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::handle_timeout ()
|
||||
{
|
||||
QString message;
|
||||
|
||||
// we must catch all exceptions here since we are called by Qt and
|
||||
// inform our parent of the failure via the offline() message
|
||||
try
|
||||
{
|
||||
do_sync ();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
|
@ -39,11 +39,9 @@ protected:
|
||||
QObject * parent);
|
||||
|
||||
protected:
|
||||
void do_sync (bool force_signal = false, bool no_poll = false) override final;
|
||||
|
||||
// Sub-classes implement this and fetch what they can from the rig
|
||||
// in a non-intrusive manner.
|
||||
virtual void poll () = 0;
|
||||
virtual void do_poll () = 0;
|
||||
|
||||
void do_post_start () override final;
|
||||
void do_post_stop () override final;
|
||||
|
@ -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)
|
||||
|
@ -42,8 +42,8 @@ namespace Radio
|
||||
//
|
||||
// Frequency type formatting
|
||||
//
|
||||
QString UDP_EXPORT frequency_MHz_string (Frequency, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT frequency_MHz_string (Frequency, int precision = 6, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT frequency_MHz_string (FrequencyDelta, int precision = 6, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT pretty_frequency_MHz_string (Frequency, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT pretty_frequency_MHz_string (double, int scale, QLocale const& = QLocale ());
|
||||
QString UDP_EXPORT pretty_frequency_MHz_string (FrequencyDelta, QLocale const& = QLocale ());
|
||||
|
@ -12,6 +12,178 @@
|
||||
|
||||
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
|
||||
February 25, 2019
|
||||
---------------------
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
@ -28,10 +30,11 @@ private:
|
||||
QTextStream * original_stream_;
|
||||
QtMessageHandler original_handler_;
|
||||
static QTextStream * current_stream_;
|
||||
static QMutex mutex_;
|
||||
};
|
||||
|
||||
QTextStream * TraceFile::impl::current_stream_;
|
||||
|
||||
QMutex TraceFile::impl::mutex_;
|
||||
|
||||
// delegate to implementation class
|
||||
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)
|
||||
{
|
||||
Q_ASSERT_X (current_stream_, "TraceFile:message_handler", "no stream to write to");
|
||||
{
|
||||
QMutexLocker lock {&mutex_}; // thread safety - serialize writes to the trace file
|
||||
*current_stream_ << qFormatLogMessage (type, context, msg) << endl;
|
||||
}
|
||||
|
||||
if (QtFatalMsg == type)
|
||||
{
|
||||
|
@ -19,10 +19,10 @@ void TransceiverBase::start (unsigned sequence_number) noexcept
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
last_sequence_number_ = sequence_number;
|
||||
may_update u {this, true};
|
||||
shutdown ();
|
||||
startup ();
|
||||
last_sequence_number_ = sequence_number;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
@ -46,6 +46,7 @@ void TransceiverBase::set (TransceiverState const& s,
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
last_sequence_number_ = sequence_number;
|
||||
may_update u {this, true};
|
||||
bool was_online {requested_.online ()};
|
||||
if (!s.online () && was_online)
|
||||
@ -115,7 +116,6 @@ void TransceiverBase::set (TransceiverState const& s,
|
||||
// record what actually changed
|
||||
requested_.ptt (actual_.ptt ());
|
||||
}
|
||||
last_sequence_number_ = sequence_number;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
@ -133,10 +133,27 @@ void TransceiverBase::set (TransceiverState const& s,
|
||||
|
||||
void TransceiverBase::startup ()
|
||||
{
|
||||
Q_EMIT resolution (do_start ());
|
||||
do_post_start ();
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
actual_.online (true);
|
||||
requested_.online (true);
|
||||
auto res = do_start ();
|
||||
do_post_start ();
|
||||
Q_EMIT resolution (res);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = unexpected;
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::shutdown ()
|
||||
@ -163,8 +180,8 @@ void TransceiverBase::shutdown ()
|
||||
}
|
||||
do_stop ();
|
||||
do_post_stop ();
|
||||
actual_.online (false);
|
||||
requested_.online (false);
|
||||
actual_ = TransceiverState {};
|
||||
requested_ = TransceiverState {};
|
||||
}
|
||||
|
||||
void TransceiverBase::stop () noexcept
|
||||
@ -193,10 +210,13 @@ void TransceiverBase::stop () noexcept
|
||||
}
|
||||
|
||||
void TransceiverBase::update_rx_frequency (Frequency rx)
|
||||
{
|
||||
if (rx)
|
||||
{
|
||||
actual_.frequency (rx);
|
||||
requested_.frequency (rx); // track rig changes
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::update_other_frequency (Frequency tx)
|
||||
{
|
||||
|
@ -112,8 +112,6 @@ protected:
|
||||
virtual void do_ptt (bool = true) = 0;
|
||||
virtual void do_post_ptt (bool = true) {}
|
||||
|
||||
virtual void do_sync (bool force_signal = false, bool no_poll = false) = 0;
|
||||
|
||||
virtual bool do_pre_update () {return true;}
|
||||
|
||||
// sub classes report rig state changes with these methods
|
||||
|
@ -1,8 +1,11 @@
|
||||
#include "ClientWidget.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <QRegExp>
|
||||
#include <QColor>
|
||||
#include <QtWidgets>
|
||||
#include <QAction>
|
||||
#include <QDebug>
|
||||
|
||||
#include "validators/MaidenheadLocatorValidator.hpp"
|
||||
|
||||
@ -11,6 +14,9 @@ namespace
|
||||
//QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"};
|
||||
QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"};
|
||||
QRegularExpression cq_re {"(CQ|CQDX|QRZ)[^A-Z0-9/]+"};
|
||||
QRegExpValidator message_validator {message_alphabet};
|
||||
MaidenheadLocatorValidator locator_validator;
|
||||
quint32 quint32_max {std::numeric_limits<quint32>::max ()};
|
||||
|
||||
void update_dynamic_property (QWidget * widget, char const * property, QVariant const& value)
|
||||
{
|
||||
@ -21,9 +27,10 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id)
|
||||
: client_id_ {client_id}
|
||||
, rx_df_ (-1)
|
||||
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
|
||||
: QSortFilterProxyModel {parent}
|
||||
, client_id_ {client_id}
|
||||
, rx_df_ (quint32_max)
|
||||
{
|
||||
}
|
||||
|
||||
@ -49,7 +56,7 @@ QVariant ClientWidget::IdFilterModel::data (QModelIndex const& proxy_index, int
|
||||
break;
|
||||
|
||||
case 4: // DF
|
||||
if (qAbs (QSortFilterProxyModel::data (proxy_index).toInt () - rx_df_) <= 10)
|
||||
if (qAbs (QSortFilterProxyModel::data (proxy_index).toUInt () - rx_df_) <= 10)
|
||||
{
|
||||
return QColor {255, 200, 200};
|
||||
}
|
||||
@ -87,7 +94,7 @@ void ClientWidget::IdFilterModel::de_call (QString const& call)
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWidget::IdFilterModel::rx_df (int df)
|
||||
void ClientWidget::IdFilterModel::rx_df (quint32 df)
|
||||
{
|
||||
if (df != rx_df_)
|
||||
{
|
||||
@ -119,26 +126,48 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
, QListWidget const * calls_of_interest, QWidget * parent)
|
||||
: QDockWidget {make_title (id, version, revision), parent}
|
||||
, id_ {id}
|
||||
, done_ {false}
|
||||
, calls_of_interest_ {calls_of_interest}
|
||||
, decodes_proxy_model_ {id_}
|
||||
, decodes_proxy_model_ {id}
|
||||
, beacons_proxy_model_ {id}
|
||||
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
|
||||
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
|
||||
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
|
||||
, decodes_table_view_ {new QTableView}
|
||||
, beacons_table_view_ {new QTableView}
|
||||
, message_line_edit_ {new QLineEdit}
|
||||
, grid_line_edit_ {new QLineEdit}
|
||||
, decodes_table_view_ {new QTableView {this}}
|
||||
, beacons_table_view_ {new QTableView {this}}
|
||||
, message_line_edit_ {new QLineEdit {this}}
|
||||
, grid_line_edit_ {new QLineEdit {this}}
|
||||
, generate_messages_push_button_ {new QPushButton {tr ("&Gen Msgs"), this}}
|
||||
, auto_off_button_ {nullptr}
|
||||
, halt_tx_button_ {nullptr}
|
||||
, de_label_ {new QLabel {this}}
|
||||
, frequency_label_ {new QLabel {this}}
|
||||
, tx_df_label_ {new QLabel {this}}
|
||||
, report_label_ {new QLabel {this}}
|
||||
, configuration_line_edit_ {new QLineEdit {this}}
|
||||
, mode_line_edit_ {new QLineEdit {this}}
|
||||
, frequency_tolerance_spin_box_ {new QSpinBox {this}}
|
||||
, tx_mode_label_ {new QLabel {this}}
|
||||
, submode_line_edit_ {new QLineEdit {this}}
|
||||
, fast_mode_check_box_ {new QCheckBox {this}}
|
||||
, tr_period_spin_box_ {new QSpinBox {this}}
|
||||
, rx_df_spin_box_ {new QSpinBox {this}}
|
||||
, dx_call_line_edit_ {new QLineEdit {this}}
|
||||
, dx_grid_line_edit_ {new QLineEdit {this}}
|
||||
, decodes_page_ {new QWidget {this}}
|
||||
, beacons_page_ {new QWidget {this}}
|
||||
, content_widget_ {new QFrame {this}}
|
||||
, status_bar_ {new QStatusBar {this}}
|
||||
, control_button_box_ {new QDialogButtonBox {this}}
|
||||
, form_layout_ {new QFormLayout}
|
||||
, horizontal_layout_ {new QHBoxLayout}
|
||||
, subform1_layout_ {new QFormLayout}
|
||||
, subform2_layout_ {new QFormLayout}
|
||||
, subform3_layout_ {new QFormLayout}
|
||||
, decodes_layout_ {new QVBoxLayout {decodes_page_}}
|
||||
, beacons_layout_ {new QVBoxLayout {beacons_page_}}
|
||||
, content_layout_ {new QVBoxLayout {content_widget_}}
|
||||
, decodes_stack_ {new QStackedLayout}
|
||||
, auto_off_button_ {new QPushButton {tr ("&Auto Off")}}
|
||||
, halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}}
|
||||
, de_label_ {new QLabel}
|
||||
, mode_label_ {new QLabel}
|
||||
, fast_mode_ {false}
|
||||
, frequency_label_ {new QLabel}
|
||||
, dx_label_ {new QLabel}
|
||||
, rx_df_label_ {new QLabel}
|
||||
, tx_df_label_ {new QLabel}
|
||||
, report_label_ {new QLabel}
|
||||
, columns_resized_ {false}
|
||||
{
|
||||
// set up widgets
|
||||
@ -152,11 +181,33 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
decodes_table_view_->insertAction (nullptr, erase_rx_frequency_action_);
|
||||
decodes_table_view_->insertAction (nullptr, erase_both_action_);
|
||||
|
||||
auto form_layout = new QFormLayout;
|
||||
form_layout->addRow (tr ("Free text:"), message_line_edit_);
|
||||
form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_);
|
||||
message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this});
|
||||
grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this});
|
||||
message_line_edit_->setValidator (&message_validator);
|
||||
grid_line_edit_->setValidator (&locator_validator);
|
||||
dx_grid_line_edit_->setValidator (&locator_validator);
|
||||
tr_period_spin_box_->setRange (5, 30);
|
||||
tr_period_spin_box_->setSuffix (" s");
|
||||
rx_df_spin_box_->setRange (200, 5000);
|
||||
frequency_tolerance_spin_box_->setRange (10, 1000);
|
||||
frequency_tolerance_spin_box_->setPrefix ("\u00b1");
|
||||
frequency_tolerance_spin_box_->setSuffix (" Hz");
|
||||
|
||||
form_layout_->addRow (tr ("Free text:"), message_line_edit_);
|
||||
form_layout_->addRow (tr ("Temporary grid:"), grid_line_edit_);
|
||||
form_layout_->addRow (tr ("Configuration name:"), configuration_line_edit_);
|
||||
form_layout_->addRow (horizontal_layout_);
|
||||
subform1_layout_->addRow (tr ("Mode:"), mode_line_edit_);
|
||||
subform2_layout_->addRow (tr ("Submode:"), submode_line_edit_);
|
||||
subform3_layout_->addRow (tr ("Fast mode:"), fast_mode_check_box_);
|
||||
subform1_layout_->addRow (tr ("T/R period:"), tr_period_spin_box_);
|
||||
subform2_layout_->addRow (tr ("Rx DF:"), rx_df_spin_box_);
|
||||
subform3_layout_->addRow (tr ("Freq. Tol:"), frequency_tolerance_spin_box_);
|
||||
subform1_layout_->addRow (tr ("DX call:"), dx_call_line_edit_);
|
||||
subform2_layout_->addRow (tr ("DX grid:"), dx_grid_line_edit_);
|
||||
subform3_layout_->addRow (generate_messages_push_button_);
|
||||
horizontal_layout_->addLayout (subform1_layout_);
|
||||
horizontal_layout_->addLayout (subform2_layout_);
|
||||
horizontal_layout_->addLayout (subform3_layout_);
|
||||
|
||||
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
|
||||
Q_EMIT do_free_text (id_, text, false);
|
||||
});
|
||||
@ -166,66 +217,102 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
Q_EMIT location (id_, grid_line_edit_->text ());
|
||||
});
|
||||
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ());
|
||||
});
|
||||
connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
|
||||
Q_EMIT configure (id_, empty, f, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
, i, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, i, empty, empty, false);
|
||||
});
|
||||
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false);
|
||||
});
|
||||
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
|
||||
});
|
||||
|
||||
auto decodes_page = new QWidget;
|
||||
auto decodes_layout = new QVBoxLayout {decodes_page};
|
||||
decodes_layout->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
decodes_layout->addWidget (decodes_table_view_);
|
||||
decodes_layout->addLayout (form_layout);
|
||||
decodes_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
decodes_layout_->addWidget (decodes_table_view_);
|
||||
decodes_layout_->addLayout (form_layout_);
|
||||
|
||||
auto beacons_proxy_model = new IdFilterModel {id_};
|
||||
beacons_proxy_model->setSourceModel (beacons_model);
|
||||
beacons_table_view_->setModel (beacons_proxy_model);
|
||||
beacons_proxy_model_.setSourceModel (beacons_model);
|
||||
beacons_table_view_->setModel (&beacons_proxy_model_);
|
||||
beacons_table_view_->verticalHeader ()->hide ();
|
||||
beacons_table_view_->hideColumn (0);
|
||||
beacons_table_view_->horizontalHeader ()->setStretchLastSection (true);
|
||||
beacons_table_view_->setContextMenuPolicy (Qt::ActionsContextMenu);
|
||||
beacons_table_view_->insertAction (nullptr, erase_action_);
|
||||
|
||||
auto beacons_page = new QWidget;
|
||||
auto beacons_layout = new QVBoxLayout {beacons_page};
|
||||
beacons_layout->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
beacons_layout->addWidget (beacons_table_view_);
|
||||
beacons_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
beacons_layout_->addWidget (beacons_table_view_);
|
||||
|
||||
decodes_stack_->addWidget (decodes_page);
|
||||
decodes_stack_->addWidget (beacons_page);
|
||||
decodes_stack_->addWidget (decodes_page_);
|
||||
decodes_stack_->addWidget (beacons_page_);
|
||||
|
||||
// stack alternative views
|
||||
auto content_layout = new QVBoxLayout;
|
||||
content_layout->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
content_layout->addLayout (decodes_stack_);
|
||||
content_layout_->setContentsMargins (QMargins {2, 2, 2, 2});
|
||||
content_layout_->addLayout (decodes_stack_);
|
||||
|
||||
// set up controls
|
||||
auto control_button_box = new QDialogButtonBox;
|
||||
control_button_box->addButton (auto_off_button_, QDialogButtonBox::ActionRole);
|
||||
control_button_box->addButton (halt_tx_button_, QDialogButtonBox::ActionRole);
|
||||
auto_off_button_ = control_button_box_->addButton (tr ("&Auto Off"), QDialogButtonBox::ActionRole);
|
||||
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
|
||||
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, true);
|
||||
});
|
||||
connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
|
||||
Q_EMIT do_halt_tx (id_, true);
|
||||
});
|
||||
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
|
||||
Q_EMIT do_halt_tx (id_, false);
|
||||
});
|
||||
content_layout->addWidget (control_button_box);
|
||||
content_layout_->addWidget (control_button_box_);
|
||||
|
||||
// set up status area
|
||||
auto status_bar = new QStatusBar;
|
||||
status_bar->addPermanentWidget (de_label_);
|
||||
status_bar->addPermanentWidget (mode_label_);
|
||||
status_bar->addPermanentWidget (frequency_label_);
|
||||
status_bar->addPermanentWidget (dx_label_);
|
||||
status_bar->addPermanentWidget (rx_df_label_);
|
||||
status_bar->addPermanentWidget (tx_df_label_);
|
||||
status_bar->addPermanentWidget (report_label_);
|
||||
content_layout->addWidget (status_bar);
|
||||
connect (this, &ClientWidget::topLevelChanged, status_bar, &QStatusBar::setSizeGripEnabled);
|
||||
status_bar_->addPermanentWidget (de_label_);
|
||||
status_bar_->addPermanentWidget (tx_mode_label_);
|
||||
status_bar_->addPermanentWidget (frequency_label_);
|
||||
status_bar_->addPermanentWidget (tx_df_label_);
|
||||
status_bar_->addPermanentWidget (report_label_);
|
||||
content_layout_->addWidget (status_bar_);
|
||||
connect (this, &ClientWidget::topLevelChanged, status_bar_, &QStatusBar::setSizeGripEnabled);
|
||||
|
||||
// set up central widget
|
||||
auto content_widget = new QFrame;
|
||||
content_widget->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
|
||||
content_widget->setLayout (content_layout);
|
||||
setWidget (content_widget);
|
||||
content_widget_->setFrameStyle (QFrame::StyledPanel | QFrame::Sunken);
|
||||
setWidget (content_widget_);
|
||||
// setMinimumSize (QSize {550, 0});
|
||||
setFeatures (DockWidgetMovable | DockWidgetFloatable);
|
||||
setAllowedAreas (Qt::BottomDockWidgetArea);
|
||||
setFloating (true);
|
||||
|
||||
@ -252,6 +339,25 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWidget::dispose ()
|
||||
{
|
||||
done_ = true;
|
||||
close ();
|
||||
}
|
||||
|
||||
void ClientWidget::closeEvent (QCloseEvent *e)
|
||||
{
|
||||
if (!done_)
|
||||
{
|
||||
Q_EMIT do_close (id_);
|
||||
e->ignore (); // defer closure until client actually closes
|
||||
}
|
||||
else
|
||||
{
|
||||
QDockWidget::closeEvent (e);
|
||||
}
|
||||
}
|
||||
|
||||
ClientWidget::~ClientWidget ()
|
||||
{
|
||||
for (int row = 0; row < calls_of_interest_->count (); ++row)
|
||||
@ -261,16 +367,45 @@ ClientWidget::~ClientWidget ()
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientWidget::fast_mode () const
|
||||
{
|
||||
return fast_mode_check_box_->isChecked ();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void update_line_edit (QLineEdit * le, QString const& value, bool allow_empty = true)
|
||||
{
|
||||
le->setEnabled (value.size () || allow_empty);
|
||||
if (!(le->hasFocus () && le->isModified ()))
|
||||
{
|
||||
le->setText (value);
|
||||
}
|
||||
}
|
||||
|
||||
void update_spin_box (QSpinBox * sb, int value, QString const& special_value = QString {})
|
||||
{
|
||||
sb->setSpecialValueText (special_value);
|
||||
bool enable {0 == special_value.size ()};
|
||||
sb->setEnabled (enable);
|
||||
if (!sb->hasFocus () && enable)
|
||||
{
|
||||
sb->setValue (value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode)
|
||||
, bool watchdog_timeout, QString const& submode, bool fast_mode
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (id == id_)
|
||||
{
|
||||
fast_mode_ = fast_mode;
|
||||
fast_mode_check_box_->setChecked (fast_mode);
|
||||
decodes_proxy_model_.de_call (de_call);
|
||||
decodes_proxy_model_.rx_df (rx_df);
|
||||
QString special;
|
||||
@ -288,21 +423,25 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
|
||||
.arg (de_grid.size () ? '(' + de_grid + ')' : QString {})
|
||||
.arg (special)
|
||||
: QString {});
|
||||
mode_label_->setText (QString {"Mode: %1%2%3%4"}
|
||||
.arg (mode)
|
||||
.arg (sub_mode)
|
||||
.arg (fast_mode && !mode.contains (QRegularExpression {R"(ISCAT|MSK144)"}) ? "fast" : "")
|
||||
update_line_edit (mode_line_edit_, mode);
|
||||
update_spin_box (frequency_tolerance_spin_box_, frequency_tolerance
|
||||
, quint32_max == frequency_tolerance ? QString {"n/a"} : QString {});
|
||||
update_line_edit (submode_line_edit_, submode, false);
|
||||
tx_mode_label_->setText (QString {"Tx Mode: %1"}
|
||||
.arg (tx_mode.isEmpty () || tx_mode == mode ? "" : '(' + tx_mode + ')'));
|
||||
frequency_label_->setText ("QRG: " + Radio::pretty_frequency_MHz_string (f));
|
||||
dx_label_->setText (dx_call.size () >= 0 ? QString {"DX: %1%2"}.arg (dx_call)
|
||||
.arg (dx_grid.size () ? '(' + dx_grid + ')' : QString {}) : QString {});
|
||||
rx_df_label_->setText (rx_df >= 0 ? QString {"Rx: %1"}.arg (rx_df) : "");
|
||||
tx_df_label_->setText (tx_df >= 0 ? QString {"Tx: %1"}.arg (tx_df) : "");
|
||||
update_line_edit (dx_call_line_edit_, dx_call);
|
||||
update_line_edit (dx_grid_line_edit_, dx_grid);
|
||||
if (rx_df != quint32_max) update_spin_box (rx_df_spin_box_, rx_df);
|
||||
update_spin_box (tr_period_spin_box_, tr_period
|
||||
, quint32_max == tr_period ? QString {"n/a"} : QString {});
|
||||
tx_df_label_->setText (QString {"Tx: %1"}.arg (tx_df));
|
||||
report_label_->setText ("SNR: " + report);
|
||||
update_dynamic_property (frequency_label_, "transmitting", transmitting);
|
||||
auto_off_button_->setEnabled (tx_enabled);
|
||||
halt_tx_button_->setEnabled (transmitting);
|
||||
update_dynamic_property (mode_label_, "decoding", decoding);
|
||||
update_line_edit (configuration_line_edit_, configuration_name);
|
||||
update_dynamic_property (mode_line_edit_, "decoding", decoding);
|
||||
update_dynamic_property (tx_df_label_, "watchdog_timeout", watchdog_timeout);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
#ifndef WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
|
||||
#define WSJTX_UDP_CLIENT_WIDGET_MODEL_HPP__
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "MessageServer.hpp"
|
||||
|
||||
@ -13,6 +13,20 @@ class QAbstractItemModel;
|
||||
class QModelIndex;
|
||||
class QColor;
|
||||
class QAction;
|
||||
class QListWidget;
|
||||
class QFormLayout;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class QStackedLayout;
|
||||
class QTableView;
|
||||
class QLineEdit;
|
||||
class QAbstractButton;
|
||||
class QLabel;
|
||||
class QCheckBox;
|
||||
class QSpinBox;
|
||||
class QFrame;
|
||||
class QStatusBar;
|
||||
class QDialogButtonBox;
|
||||
|
||||
using Frequency = MessageServer::Frequency;
|
||||
|
||||
@ -25,16 +39,18 @@ public:
|
||||
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
|
||||
, QString const& id, QString const& version, QString const& revision
|
||||
, QListWidget const * calls_of_interest, QWidget * parent = nullptr);
|
||||
void dispose ();
|
||||
~ClientWidget ();
|
||||
|
||||
bool fast_mode () const {return fast_mode_;}
|
||||
bool fast_mode () const;
|
||||
|
||||
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode);
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name);
|
||||
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr
|
||||
, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence, bool off_air);
|
||||
@ -45,6 +61,7 @@ public:
|
||||
Q_SLOT void decodes_cleared (QString const& client_id);
|
||||
|
||||
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0);
|
||||
Q_SIGNAL void do_close (QString const& id);
|
||||
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier);
|
||||
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
|
||||
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
|
||||
@ -52,30 +69,39 @@ public:
|
||||
Q_SIGNAL void highlight_callsign (QString const& id, QString const& call
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
private:
|
||||
QString id_;
|
||||
QListWidget const * calls_of_interest_;
|
||||
class IdFilterModel final
|
||||
: public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
IdFilterModel (QString const& client_id);
|
||||
IdFilterModel (QString const& client_id, QObject * = nullptr);
|
||||
|
||||
void de_call (QString const&);
|
||||
void rx_df (int);
|
||||
void rx_df (quint32);
|
||||
|
||||
QVariant data (QModelIndex const& proxy_index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
protected:
|
||||
private:
|
||||
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
|
||||
|
||||
private:
|
||||
QString client_id_;
|
||||
QString call_;
|
||||
QRegularExpression base_call_re_;
|
||||
int rx_df_;
|
||||
} decodes_proxy_model_;
|
||||
quint32 rx_df_;
|
||||
};
|
||||
|
||||
void closeEvent (QCloseEvent *) override;
|
||||
|
||||
QString id_;
|
||||
bool done_;
|
||||
QListWidget const * calls_of_interest_;
|
||||
IdFilterModel decodes_proxy_model_;
|
||||
IdFilterModel beacons_proxy_model_;
|
||||
|
||||
QAction * erase_action_;
|
||||
QAction * erase_rx_frequency_action_;
|
||||
QAction * erase_both_action_;
|
||||
@ -83,17 +109,39 @@ private:
|
||||
QTableView * beacons_table_view_;
|
||||
QLineEdit * message_line_edit_;
|
||||
QLineEdit * grid_line_edit_;
|
||||
QStackedLayout * decodes_stack_;
|
||||
QAbstractButton * generate_messages_push_button_;
|
||||
QAbstractButton * auto_off_button_;
|
||||
QAbstractButton * halt_tx_button_;
|
||||
QLabel * de_label_;
|
||||
QLabel * mode_label_;
|
||||
bool fast_mode_;
|
||||
QLabel * frequency_label_;
|
||||
QLabel * dx_label_;
|
||||
QLabel * rx_df_label_;
|
||||
QLabel * tx_df_label_;
|
||||
QLabel * report_label_;
|
||||
QLineEdit * configuration_line_edit_;
|
||||
QLineEdit * mode_line_edit_;
|
||||
QSpinBox * frequency_tolerance_spin_box_;
|
||||
QLabel * tx_mode_label_;
|
||||
QLineEdit * submode_line_edit_;
|
||||
QCheckBox * fast_mode_check_box_;
|
||||
QSpinBox * tr_period_spin_box_;
|
||||
QSpinBox * rx_df_spin_box_;
|
||||
QLineEdit * dx_call_line_edit_;
|
||||
QLineEdit * dx_grid_line_edit_;
|
||||
QWidget * decodes_page_;
|
||||
QWidget * beacons_page_;
|
||||
QFrame * content_widget_;
|
||||
QStatusBar * status_bar_;
|
||||
QDialogButtonBox * control_button_box_;
|
||||
|
||||
QFormLayout * form_layout_;
|
||||
QHBoxLayout * horizontal_layout_;
|
||||
QFormLayout * subform1_layout_;
|
||||
QFormLayout * subform2_layout_;
|
||||
QFormLayout * subform3_layout_;
|
||||
QVBoxLayout * decodes_layout_;
|
||||
QVBoxLayout * beacons_layout_;
|
||||
QVBoxLayout * content_layout_;
|
||||
QStackedLayout * decodes_stack_;
|
||||
|
||||
bool columns_resized_;
|
||||
};
|
||||
|
||||
|
@ -250,12 +250,15 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
|
||||
connect (server_, &MessageServer::WSPR_decode, dock, &ClientWidget::beacon_spot_added);
|
||||
connect (server_, &MessageServer::decodes_cleared, dock, &ClientWidget::decodes_cleared);
|
||||
connect (dock, &ClientWidget::do_clear_decodes, server_, &MessageServer::clear_decodes);
|
||||
connect (dock, &ClientWidget::do_close, server_, &MessageServer::close);
|
||||
connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply);
|
||||
connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx);
|
||||
connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text);
|
||||
connect (dock, &ClientWidget::location, server_, &MessageServer::location);
|
||||
connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible);
|
||||
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign);
|
||||
connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration);
|
||||
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
|
||||
dock_widgets_[id] = dock;
|
||||
server_->replay (id); // request decodes and status
|
||||
}
|
||||
@ -265,7 +268,7 @@ void MessageAggregatorMainWindow::remove_client (QString const& id)
|
||||
auto iter = dock_widgets_.find (id);
|
||||
if (iter != std::end (dock_widgets_))
|
||||
{
|
||||
(*iter)->close ();
|
||||
(*iter)->dispose ();
|
||||
dock_widgets_.erase (iter);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,8 @@ public:
|
||||
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
|
||||
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
|
||||
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/
|
||||
, quint8 /*special_op_mode*/)
|
||||
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
|
||||
, QString const& /*configuration_name*/)
|
||||
{
|
||||
if (id == id_)
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Version number components
|
||||
set (WSJTX_VERSION_MAJOR 2)
|
||||
set (WSJTX_VERSION_MINOR 0)
|
||||
set (WSJTX_VERSION_PATCH 1)
|
||||
set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions
|
||||
set (WSJTX_VERSION_MINOR 1)
|
||||
set (WSJTX_VERSION_PATCH 0)
|
||||
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
|
||||
|
@ -44,6 +44,8 @@ namespace
|
||||
class Dialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using BandList = QList<QString>;
|
||||
|
||||
@ -69,6 +71,8 @@ private:
|
||||
static int constexpr band_index_role {Qt::UserRole};
|
||||
};
|
||||
|
||||
#include "WSPRBandHopping.moc"
|
||||
|
||||
Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
|
||||
, QBitArray * bands, int * gray_line_duration, QWidget * parent)
|
||||
: QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}
|
||||
|
@ -8,7 +8,8 @@
|
||||
#ifdef __cplusplus
|
||||
#include <cstdbool>
|
||||
extern "C" {
|
||||
#else
|
||||
#endif
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
|
@ -51,10 +51,11 @@ DecodedText::DecodedText (QString const& the_string)
|
||||
|
||||
QStringList DecodedText::messageWords () const
|
||||
{
|
||||
if (is_standard_)
|
||||
{
|
||||
if(is_standard_) {
|
||||
// extract up to the first four message words
|
||||
return words_re.match (message_).capturedTexts ();
|
||||
QString t=message_;
|
||||
if(t.left(4)=="TU; ") t=message_.mid(4,-1);
|
||||
return words_re.match(t).capturedTexts();
|
||||
}
|
||||
// simple word split for free text messages
|
||||
auto words = message_.split (' ', QString::SkipEmptyParts);
|
||||
|
@ -52,6 +52,7 @@ set (UG_SRCS
|
||||
tutorial-example1.adoc
|
||||
tutorial-example2.adoc
|
||||
tutorial-example3.adoc
|
||||
tutorial-example4.adoc
|
||||
tutorial-main-window.adoc
|
||||
tutorial-wide-graph-settings.adoc
|
||||
utilities.adoc
|
||||
@ -77,6 +78,8 @@ set (UG_IMGS
|
||||
images/FreqCal_Graph.png
|
||||
images/FreqCal_Results.png
|
||||
images/freemsg.png
|
||||
images/ft4_decodes.png
|
||||
images/ft4_waterfall.png
|
||||
images/ft8_decodes.png
|
||||
images/FT8_waterfall.png
|
||||
images/help-menu.png
|
||||
|
@ -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]
|
||||
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice]
|
||||
:win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages]
|
||||
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_0_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]
|
||||
:wsjt_yahoo_group: https://groups.yahoo.com/neo/groups/wsjtgroup/info[WSJT Group]
|
||||
:wsjtx: http://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]
|
||||
@ -115,6 +116,7 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
|
||||
:QRA64_EME: http://physics.princeton.edu/pulsar/K1JT/QRA64_EME.pdf[QRA64 for microwave EME]
|
||||
:svn: http://subversion.apache.org/packages.html#windows[Subversion]
|
||||
:win32: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win32.exe[wsjtx-{VERSION}-win32.exe]
|
||||
:win64: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win64.exe[wsjtx-{VERSION}-win64.exe]
|
||||
:wsjt-devel: https://lists.sourceforge.net/lists/listinfo/wsjt-devel[here]
|
||||
:wsjt_repo: https://sourceforge.net/p/wsjt/wsjt_orig/ci/master/tree/[WSJT Source Repository]
|
||||
:wspr_code: http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe[WSPRcode.exe]
|
||||
|
@ -95,3 +95,19 @@ QT_QPA_PLATFORMTHEME set to empty (the space after the '=' character
|
||||
is necessary):
|
||||
|
||||
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.
|
||||
|
BIN
doc/user_guide/en/images/ft4_decodes.png
Normal file
BIN
doc/user_guide/en/images/ft4_decodes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
doc/user_guide/en/images/ft4_waterfall.png
Normal file
BIN
doc/user_guide/en/images/ft4_waterfall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -1,11 +1,12 @@
|
||||
// Status=review
|
||||
|
||||
Download and execute the package file {win32}, following these
|
||||
instructions:
|
||||
Download and execute the package file {win32} (WinXP, Vista, Win 7,
|
||||
Win 8, Win10, 32-bit) or {win64} (Vista, Win 7, Win 8, Win10, 64-bit)
|
||||
following these instructions:
|
||||
|
||||
* Install _WSJT-X_ into its own directory, for example `C:\WSJTX` or `
|
||||
C:\WSJT\WSJTX`, rather than the conventional location `C:\Program
|
||||
Files (x86)\WSJTX`.
|
||||
Files ...\WSJTX`.
|
||||
|
||||
* All program files relating to _WSJT-X_ will be stored in the chosen
|
||||
installation directory and its subdirectories.
|
||||
@ -29,26 +30,31 @@ TIP: Your computer may be configured so that this directory is
|
||||
|
||||
[[OPENSSL]]
|
||||
|
||||
* image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error, role="right"]
|
||||
From this version onward _WSJT-X_ requires the _OpenSSL_ libraries
|
||||
to be installed. Suitable libraries may already be installed on your
|
||||
* image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error,
|
||||
role="right"] _WSJT-X_ requires the _OpenSSL_ libraries to be
|
||||
installed. Suitable libraries may already be installed on your
|
||||
system, if they are not you will see this error shortly after
|
||||
startup. To fix this you need to install the _OpenSSL_ libraries.
|
||||
|
||||
** You can download a suitable _OpenSSL_ package for from
|
||||
{win_openssl_packages}, you need the latest *Win32 v1.0.2 Lite*
|
||||
version (Note it is the Win32 package even if you are using a
|
||||
64-bit Windows operating system) which at the time of writing was
|
||||
{win32_openssl}.
|
||||
{win_openssl_packages}, you need the latest *Windows v1.0.2 Lite*
|
||||
version. For the 32-bit _WSJT-X_ build use the Win32 version of the
|
||||
_OpenSSL_ libraries, for the 64-bit _WSJT-X_ use the Win64 version
|
||||
of the _OpenSSL_ libraries (Note it is OK to install both versions
|
||||
on a 64-bit system) which at the time of writing were
|
||||
{win32_openssl} and {win64_openssl} respectively.
|
||||
|
||||
** Install the package and accept the default options, including the
|
||||
option to copy the _OpenSSL_ DLLs to the Windows system directory
|
||||
(this is important). +
|
||||
option to copy the _OpenSSL_ DLLs to the Windows system
|
||||
directory. There is no obligation to donate to the _OpenSSL_
|
||||
project, un-check all the donation options if desired. +
|
||||
|
||||
NOTE: If you still get the same network error after installing the
|
||||
_OpenSSL_ libraries then you also need to install the
|
||||
{msvcpp_redist} component. From the download page select
|
||||
`vcredist_x86.exe` and run it to install.
|
||||
`vcredist_x86.exe` for use with the 32-bit _WSJT-X_ build or
|
||||
`vcredist_x64.exe` with the 64-bit build, then run it to
|
||||
install.
|
||||
|
||||
TIP: If you cannot install the _OpenSSL_ libraries or do not have an
|
||||
Internet connection on the computer used to run
|
||||
|
@ -7,10 +7,10 @@ K1**JT**,`" while the suffix "`-X`" indicates that _WSJT-X_ started as
|
||||
an extended and experimental branch of the program
|
||||
_WSJT_.
|
||||
|
||||
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers nine different
|
||||
protocols or modes: *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*, *ISCAT*,
|
||||
*MSK144*, *WSPR*, and *Echo*. The first five are designed for making
|
||||
reliable QSOs under extreme weak-signal conditions. They use nearly
|
||||
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers ten different
|
||||
protocols or modes: *FT4*, *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*,
|
||||
*ISCAT*, *MSK144*, *WSPR*, and *Echo*. The first six are designed for
|
||||
making reliable QSOs under weak-signal conditions. They use nearly
|
||||
identical message structure and source encoding. JT65 and QRA64 were
|
||||
designed for EME ("`moonbounce`") on the VHF/UHF bands and have also
|
||||
proven very effective for worldwide QRP communication on the HF bands.
|
||||
@ -25,12 +25,19 @@ one-minute timed sequences of alternating transmission and reception,
|
||||
so a minimal QSO takes four to six minutes — two or three
|
||||
transmissions by each station, one sending in odd UTC minutes and the
|
||||
other even. FT8 is operationally similar but four times faster
|
||||
(15-second T/R sequences) and less sensitive by a few dB. On the HF
|
||||
bands, world-wide QSOs are possible with any of these modes using
|
||||
power levels of a few watts (or even milliwatts) and compromise
|
||||
antennas. On VHF bands and higher, QSOs are possible (by EME and
|
||||
other propagation types) at signal levels 10 to 15 dB below those
|
||||
required for CW.
|
||||
(15-second T/R sequences) and less sensitive by a few dB. FT4 is
|
||||
faster still (7.5 s T/R sequences) and especially well suited for
|
||||
radio contesting. On the HF bands, world-wide QSOs are possible with
|
||||
any of these modes using power levels of a few watts (or even
|
||||
milliwatts) and compromise antennas. On VHF bands and higher, QSOs
|
||||
are possible (by EME and other propagation types) at signal levels 10
|
||||
to 15 dB below those required for CW.
|
||||
|
||||
Note that even though their T/R sequences are short, FT4 and FT8 are
|
||||
classified as slow modes because their message frames are sent only
|
||||
once per transmission. All fast modes in _WSJT-X_ send their message
|
||||
frames repeatedly, as many times as will fit into the Tx sequence
|
||||
length.
|
||||
|
||||
*ISCAT*, *MSK144*, and optionally submodes *JT9E-H* are "`fast`"
|
||||
protocols designed to take advantage of brief signal enhancements from
|
||||
@ -65,10 +72,10 @@ are available for all three platforms.
|
||||
|
||||
*Version Numbers:* _WSJT-X_ release numbers have major, minor, and
|
||||
patch numbers separated by periods: for example, _WSJT-X_ Version
|
||||
1.9.0. Temporary "`beta`" release candidates are sometimes made in
|
||||
2.1.0. Temporary _beta release_ candidates are sometimes made in
|
||||
advance of a new general-availability release, in order to obtain user
|
||||
feedback. For example, version 1.9.0-rc1, 1.9.0-rc2, etc., would
|
||||
be beta releases leading up to the final release of v1.9.0.
|
||||
feedback. For example, version 2.1.0-rc1, 2.1.0-rc2, etc., would
|
||||
be beta releases leading up to the final release of v2.1.0.
|
||||
Release candidates should be used _only_ during a short testing
|
||||
period. They carry an implied obligation to provide feedback to the
|
||||
program development group. Candidate releases should not be used on
|
||||
|
@ -1,40 +1,15 @@
|
||||
=== New in Version {VERSION}
|
||||
|
||||
For quick reference, here's a short list of features and capabilities
|
||||
added to _WSJT-X_ since Version 1.9.1:
|
||||
|
||||
- New FT8 and MSK144 protocols with 77-bit payloads permit these enhancements:
|
||||
|
||||
* Optimized contest messages for NA VHF, EU VHF, Field Day, RTTY Roundup
|
||||
|
||||
* Full support for "/R" and "/P" calls in relevant contests
|
||||
|
||||
* New logging features for contesting
|
||||
|
||||
* Integration with {n1mm_logger} and {writelog} for contesting
|
||||
|
||||
* Improved support for compound and nonstandard callsigns
|
||||
|
||||
* Nearly equal (or better) sensitivity compared to old protocols
|
||||
|
||||
* Lower false decode rates
|
||||
|
||||
- Improved color highlighting of received messages
|
||||
|
||||
- Improved WSPR sensitivity
|
||||
|
||||
- Expanded and improved UDP messages sent to companion programs
|
||||
|
||||
- Bug fixes and other minor tweaks to user interface
|
||||
|
||||
IMPORTANT: Note that for FT8 and MSK144 there is no backward
|
||||
compatibility with WSJT-X 1.9.1 and earlier. Everyone using these
|
||||
modes should upgrade to WSJT-X 2.0 by January 1, 2019.
|
||||
|
||||
IMPORTANT: _WSJT-X_ Version 2.0 drops support for Apple Mac OS X 10.9
|
||||
(Mavericks). It is possible to build from source for this operating
|
||||
system version but the DMG installer package requires 10.10 or later.
|
||||
|
||||
The most important feature added to _WSJT-X_ since Version 2.0.1 is
|
||||
the new *FT4 protocol*, designed especially for radio contesting. It
|
||||
has T/R sequence length 7.5 s, bandwidth 80 Hz, and threshold
|
||||
sensitivity -17.5 dB. Version 2.1.0 also has improvements to FT8
|
||||
waveform generation, waterfall and spectrum display, contest logging,
|
||||
rig control, the user interface, keyboard shortcuts, UDP messaging for
|
||||
inter-program communication, and accessibility, as well as a number of
|
||||
more minor enhancements and bug fixes. We now provide a separate
|
||||
installation package for 64-bit Windows Vista and later, offering
|
||||
significant improvements in decoding speed.
|
||||
|
||||
=== Documentation Conventions
|
||||
|
||||
|
@ -12,10 +12,11 @@ Special cases allow other information such as add-on callsign prefixes
|
||||
aim is to compress the most common messages used for minimally valid
|
||||
QSOs into a fixed 72-bit length.
|
||||
|
||||
The information payload for FT8 and MSK144 contains 77 bits. The 5
|
||||
additional bits are used to flag special message types used for FT8
|
||||
DXpedition Mode, contesting, nonstandard callsigns, and a few other
|
||||
special types.
|
||||
The information payload for FT4, FT8, and MSK144 contains 77 bits.
|
||||
The 5 new bits added to the original 72 are used to flag special
|
||||
message types signifying special message types used for FT8 DXpedition
|
||||
Mode, contesting, nonstandard callsigns, and a few other
|
||||
possibilities.
|
||||
|
||||
A standard amateur callsign consists of a one- or two-character
|
||||
prefix, at least one of which must be a letter, followed by a digit
|
||||
@ -67,18 +68,29 @@ _WSJT-X_ modes have continuous phase and constant envelope.
|
||||
[[SLOW_MODES]]
|
||||
=== Slow Modes
|
||||
|
||||
[[FT4PRO]]
|
||||
==== FT4
|
||||
|
||||
Forward error correction (FEC) in FT4 uses a low-density parity check
|
||||
(LDPC) code with 77 information bits, a 14-bit cyclic redundancy check
|
||||
(CRC), and 83 parity bits making a 174-bit codeword. It is thus
|
||||
called an LDPC (174,91) code. Synchronization uses four 4×4 Costas
|
||||
arrays, and ramp-up and ramp-down symbols are inserted at the start
|
||||
and end of each transmission. Modulation is 4-tone frequency-shift
|
||||
keying (4-GFSK) with Gaussian smoothing of frequency transitions. The
|
||||
keying rate is 12000/576 = 20.8333 baud. Each transmitted symbol
|
||||
conveys two bits, so the total number of channel symbols is 174/2 + 16
|
||||
+ 2 = 105. The total bandwidth is 4 × 20.8333 = 83.3 Hz.
|
||||
|
||||
[[FT8PRO]]
|
||||
==== FT8
|
||||
|
||||
Forward error correction (FEC) in FT8 uses a low-density parity check
|
||||
(LDPC) code with 77 information bits, a 14-bit cyclic redundancy check
|
||||
(CRC), and 83 parity bits making a 174-bit codeword. It is thus
|
||||
called an LDPC (174,91) code. Synchronization uses 7×7 Costas arrays
|
||||
at the beginning, middle, and end of each transmission. Modulation is
|
||||
8-tone frequency-shift keying (8-FSK) at 12000/1920 = 6.25 baud. Each
|
||||
transmitted symbol carries three bits, so the total number of channel
|
||||
symbols is 174/3 + 21 = 79. The total occupied bandwidth is 8 × 6.25
|
||||
= 50 Hz.
|
||||
FT8 uses the same LDPC (174,91) code as FT4. Modulation is 8-tone
|
||||
frequency-shift keying (8-GFSK) at 12000/1920 = 6.25 baud.
|
||||
Synchronization uses 7×7 Costas arrays at the beginning, middle, and
|
||||
end of each transmission. Transmitted symbols carry three bits, so
|
||||
the total number of channel symbols is 174/3 + 21 = 79. The total
|
||||
occupied bandwidth is 8 × 6.25 = 50 Hz.
|
||||
|
||||
[[JT4PRO]]
|
||||
==== JT4
|
||||
@ -227,7 +239,8 @@ which the probability of decoding is 50% or higher.
|
||||
|===============================================================================
|
||||
|Mode |FEC Type |(n,k) | Q|Modulation type|Keying rate (Baud)|Bandwidth (Hz)
|
||||
|Sync Energy|Tx Duration (s)|S/N Threshold (dB)
|
||||
|FT8 |LDPC, r=1/2|(174,91)| 8| 8-FSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|
||||
|FT4 |LDPC, r=1/2|(174,91)| 4| 4-GFSK| 20.8333 | 83.3 | 0.15| 5.04 | -17.5
|
||||
|FT8 |LDPC, r=1/2|(174,91)| 8| 8-GFSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|
||||
|JT4A |K=32, r=1/2|(206,72)| 2| 4-FSK| 4.375| 17.5 | 0.50| 47.1 | -23
|
||||
|JT9A |K=32, r=1/2|(206,72)| 8| 9-FSK| 1.736| 15.6 | 0.19| 49.0 | -27
|
||||
|JT65A |Reed Solomon|(63,12) |64|65-FSK| 2.692| 177.6 | 0.50| 46.8 | -25
|
||||
@ -246,6 +259,7 @@ comparable to tone spacing.
|
||||
[width="50%",cols="h,3*^",frame=topbot,options="header"]
|
||||
|=====================================
|
||||
|Mode |Tone Spacing |BW (Hz)|S/N (dB)
|
||||
|FT4 |20.8333 | 83.3 |-17.5
|
||||
|FT8 |6.25 | 50.0 |-21
|
||||
|JT4A |4.375| 17.5 |-23
|
||||
|JT4B |8.75 | 30.6 |-22
|
||||
|
52
doc/user_guide/en/tutorial-example4.adoc
Normal file
52
doc/user_guide/en/tutorial-example4.adoc
Normal 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.
|
@ -140,6 +140,10 @@ include::tutorial-example2.adoc[]
|
||||
=== FT8
|
||||
include::tutorial-example3.adoc[]
|
||||
|
||||
[[TUT_EX4]]
|
||||
=== FT4
|
||||
include::tutorial-example4.adoc[]
|
||||
|
||||
[[MAKE_QSOS]]
|
||||
== Making QSOs
|
||||
include::make-qso.adoc[]
|
||||
|
@ -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
|
27
item_delegates/FrequencyDelegate.cpp
Normal file
27
item_delegates/FrequencyDelegate.cpp
Normal 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);
|
||||
}
|
||||
|
21
item_delegates/FrequencyDelegate.hpp
Normal file
21
item_delegates/FrequencyDelegate.hpp
Normal 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
|
26
item_delegates/FrequencyDeltaDelegate.cpp
Normal file
26
item_delegates/FrequencyDeltaDelegate.cpp
Normal 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);
|
||||
}
|
21
item_delegates/FrequencyDeltaDelegate.hpp
Normal file
21
item_delegates/FrequencyDeltaDelegate.hpp
Normal 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
|
@ -1,12 +1,13 @@
|
||||
SOURCES += \
|
||||
item_delegates/ForeignKeyDelegate.cpp \
|
||||
item_delegates/FrequencyItemDelegate.cpp \
|
||||
item_delegates/FrequencyDelegate.cpp \
|
||||
item_delegates/FrequencyDeltaDelegate.cpp \
|
||||
item_delegates/CallsignDelegate.cpp \
|
||||
item_delegates/MaidenheadLocatorItemDelegate.cpp
|
||||
|
||||
HEADERS += \
|
||||
item_delegates/ForeignKeyDelegate.hpp \
|
||||
item_delegates/FrequencyItemDelegate.hpp \
|
||||
item_delegates/FrequencyDelegate.hpp \
|
||||
item_delegates/FrequencyDeltaDelegate.hpp \
|
||||
item_delegates/CallsignDelegate.hpp \
|
||||
item_delegates/MaidenheadLocatorDelegate.hpp \
|
||||
item_delegates/DateTimeAsSecsSinceEpochDelegate.hpp
|
||||
item_delegates/MaidenheadLocatorDelegate.hpp
|
||||
|
@ -22,15 +22,10 @@ i3.n3 Example message Bits Total Purpose
|
||||
2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest
|
||||
3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup
|
||||
4 <WA9XYZ> PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls
|
||||
5 ... tbd
|
||||
5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ?
|
||||
6 ... tbd
|
||||
7 ... tbd
|
||||
----------------------------------------------------------------------------------
|
||||
In case we need them, later:
|
||||
|
||||
5 TU; W9XYZ K1ABC R 579 8 MA 1 28 28 1 3 6 7 74 CQ WW RTTY
|
||||
6 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 CQ WPX RTTY
|
||||
----------------------------------------------------------------------------------
|
||||
NB: three 74-bit message types and two 71-bit message subtypes are still TBD.
|
||||
----------------------------------------------------------------------------------
|
||||
|
||||
|
@ -72,7 +72,18 @@ CQ W9XYZ EN37
|
||||
W9XYZ <YW18FIFA> R-09
|
||||
YW18FIFA <W9XYZ> RRR
|
||||
<W9XYZ> YW18FIFA 73
|
||||
10. Other stuff
|
||||
|
||||
10. WWROF FT8/FT4 contest
|
||||
-----------------------------------------------------------
|
||||
CQ TEST K1ABC FN42
|
||||
K1ABC W9XYZ -16 EN
|
||||
W9XYZ K1ABC R-07 FN
|
||||
K1ABC W9XYZ RR73
|
||||
K1ABC G3AAA -11 IO
|
||||
TU; G3AAA K1ABC R-09 FN
|
||||
K1ABC G3AAA RR73
|
||||
|
||||
11. Other stuff
|
||||
-----------------------------------------------------------
|
||||
TNX BOB 73 GL
|
||||
CQ YW18FIFA
|
||||
|
@ -172,6 +172,10 @@ subroutine pack77(msg0,i3,n3,c77)
|
||||
call pack77_4(nwords,w,i3,n3,c77)
|
||||
if(i3.ge.0) go to 900
|
||||
|
||||
! Check Type 5 (WWROF contest exchange)
|
||||
call pack77_5(nwords,w,i3,n3,c77)
|
||||
if(i3.ge.0) go to 900
|
||||
|
||||
! It defaults to free text
|
||||
800 i3=0
|
||||
n3=0
|
||||
@ -204,6 +208,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
|
||||
character*6 cexch,grid6
|
||||
character*4 grid4,cserial
|
||||
character*3 csec(NSEC)
|
||||
character*2 cfield
|
||||
character*38 c
|
||||
integer hashmy10,hashmy12,hashmy22,hashdx10,hashdx12,hashdx22
|
||||
logical unpk28_success,unpk77_success
|
||||
@ -491,8 +496,31 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
|
||||
else
|
||||
msg='CQ '//trim(call_2)
|
||||
endif
|
||||
|
||||
else if(i3.eq.5) then
|
||||
! 5 TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
|
||||
read(c77,1041) itu,n28a,n28b,ir,irpt,nexch,i3
|
||||
1041 format(b1,2b28.28,b1,b7.7,b9.9,b3.3)
|
||||
call unpack28(n28a,call_1,unpk28_success)
|
||||
if(.not.unpk28_success) unpk77_success=.false.
|
||||
call unpack28(n28b,call_2,unpk28_success)
|
||||
if(.not.unpk28_success) unpk77_success=.false.
|
||||
write(crpt,'(i3.2)') irpt-35
|
||||
if(crpt(1:1).eq.' ') crpt(1:1)='+'
|
||||
n1=nexch/18
|
||||
n2=nexch - 18*n1
|
||||
cfield(1:1)=char(ichar('A')+n1)
|
||||
cfield(2:2)=char(ichar('A')+n2)
|
||||
if(itu.eq.0 .and. ir.eq.0) msg=trim(call_1)//' '//trim(call_2)// &
|
||||
' '//crpt//' '//cfield
|
||||
if(itu.eq.1 .and. ir.eq.0) msg='TU; '//trim(call_1)//' '//trim(call_2)// &
|
||||
' '//crpt//' '//cfield
|
||||
if(itu.eq.0 .and. ir.eq.1) msg=trim(call_1)//' '//trim(call_2)// &
|
||||
' R'//crpt//' '//cfield
|
||||
if(itu.eq.1 .and. ir.eq.1) msg='TU; '//trim(call_1)//' '//trim(call_2)// &
|
||||
' R'//crpt//' '//cfield
|
||||
endif
|
||||
if(msg(1:4).eq.'CQ <') unpk77_success=.false.
|
||||
! if(msg(1:4).eq.'CQ <') unpk77_success=.false.
|
||||
|
||||
return
|
||||
end subroutine unpack77
|
||||
@ -1040,12 +1068,11 @@ subroutine pack77_3(nwords,w,i3,n3,c77)
|
||||
call chkcall(w(i1+1),bcall_2,ok2)
|
||||
if(.not.ok1 .or. .not.ok2) go to 900
|
||||
crpt=w(nwords-1)(1:3)
|
||||
if(index(crpt,'-').ge.1 .or. index(crpt,'+').ge.1) go to 900
|
||||
if(crpt(1:1).eq.'5' .and. crpt(2:2).ge.'2' .and. crpt(2:2).le.'9' .and. &
|
||||
crpt(3:3).eq.'9') then
|
||||
nserial=0
|
||||
read(w(nwords),*,err=1) nserial
|
||||
!1 i3=3
|
||||
! n3=0
|
||||
endif
|
||||
1 mult=' '
|
||||
imult=-1
|
||||
@ -1150,6 +1177,60 @@ subroutine pack77_4(nwords,w,i3,n3,c77)
|
||||
900 return
|
||||
end subroutine pack77_4
|
||||
|
||||
subroutine pack77_5(nwords,w,i3,n3,c77)
|
||||
! Check Type 5 (WWROF contest exchange)
|
||||
|
||||
character*13 w(19)
|
||||
character*77 c77
|
||||
character*6 bcall_1,bcall_2
|
||||
character*3 mult
|
||||
character crpt*4
|
||||
character c1*1,c2*2
|
||||
logical ok1,ok2
|
||||
|
||||
if(nwords.eq.4 .or. nwords.eq.5 .or. nwords.eq.6) then
|
||||
i1=1
|
||||
if(trim(w(1)).eq.'TU;') i1=2
|
||||
call chkcall(w(i1),bcall_1,ok1)
|
||||
call chkcall(w(i1+1),bcall_2,ok2)
|
||||
if(.not.ok1 .or. .not.ok2) go to 900
|
||||
crpt=w(nwords-1)(1:4)
|
||||
if(index(crpt,'-').lt.1 .and. index(crpt,'+').lt.1) go to 900
|
||||
|
||||
c1=crpt(1:1)
|
||||
c2=crpt(1:2)
|
||||
irpt=-1
|
||||
if(c1.eq.'+' .or. c1.eq.'-') then
|
||||
ir=0
|
||||
read(w(nwords-1),*,err=900) irpt
|
||||
irpt=irpt+35
|
||||
else if(c2.eq.'R+' .or. c2.eq.'R-') then
|
||||
ir=1
|
||||
read(w(nwords-1)(2:),*) irpt
|
||||
irpt=irpt+35
|
||||
endif
|
||||
if(irpt.eq.-1 .or. len(trim(w(nwords))).ne.2) go to 900
|
||||
c2=w(nwords)(1:2)
|
||||
n1=ichar(c2(1:1)) - ichar('A')
|
||||
n2=ichar(c2(2:2)) - ichar('A')
|
||||
if(n1.lt.0 .or. n1.gt.17) go to 900
|
||||
if(n2.lt.0 .or. n2.gt.17) go to 900
|
||||
nexch=18*n1 + n2
|
||||
i3=5
|
||||
n3=0
|
||||
itu=0
|
||||
if(trim(w(1)).eq.'TU;') itu=1
|
||||
call pack28(w(1+itu),n28a)
|
||||
call pack28(w(2+itu),n28b)
|
||||
! 5 TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
|
||||
write(c77,1010) itu,n28a,n28b,ir,irpt,nexch,i3
|
||||
1010 format(b1,2b28.28,b1,b7.7,b9.9,b3.3)
|
||||
|
||||
end if
|
||||
|
||||
900 return
|
||||
end subroutine pack77_5
|
||||
|
||||
subroutine packtext77(c13,c71)
|
||||
|
||||
character*13 c13,w
|
||||
|
@ -12,7 +12,9 @@ subroutine addit(itone,nfsample,nsym,nsps,ifreq,sig,dat)
|
||||
dphi=0.
|
||||
|
||||
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
|
||||
f=ifreq
|
||||
phi=0.
|
||||
@ -20,10 +22,12 @@ subroutine addit(itone,nfsample,nsym,nsps,ifreq,sig,dat)
|
||||
k=12000 !Start audio at t = 1.0 s
|
||||
t=0.
|
||||
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
|
||||
do i=1,ntot
|
||||
t=t+dt
|
||||
isym=nint(t/tsym) + 1
|
||||
if(isym.gt.nsym) exit
|
||||
if(isym.ne.isym0) then
|
||||
freq=f + itone(isym)*baud
|
||||
dphi=twopi*freq*dt
|
||||
@ -59,7 +63,7 @@ subroutine addcw(icw,ncw,ifreq,sig,dat)
|
||||
phi=0.
|
||||
k=12000 !Start audio at t = 1.0 s
|
||||
t=0.
|
||||
npts=60*12000
|
||||
npts=59*12000
|
||||
x=0.
|
||||
do i=1,npts
|
||||
t=t+dt
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ program allsim
|
||||
logical*1 bcontest
|
||||
real*4 dat(NMAX)
|
||||
character message*22,msgsent*22,arg*8,mygrid*6
|
||||
character*37 msg37,msgsent37
|
||||
|
||||
nargs=iargc()
|
||||
if(nargs.ne.1) then
|
||||
@ -60,15 +61,20 @@ program allsim
|
||||
call gen4(message,0,msgsent,itone,itype)
|
||||
call addit(itone,11025,206,2520,1200,sig,dat) !JT4
|
||||
|
||||
i3bit=0 ! ### TEMPORARY ??? ###
|
||||
call genft8(message,mygrid,bcontest,i3bit,msgsent,msgbits,itone)
|
||||
i3=-1
|
||||
n3=-1
|
||||
call genft8(message,i3,n3,msgsent,msgbits,itone)
|
||||
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 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 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))
|
||||
|
||||
|
@ -1,16 +1,56 @@
|
||||
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
|
||||
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &
|
||||
RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1,width2,bTx, &
|
||||
AzElFileName,jpleph)
|
||||
module astro_module
|
||||
use, intrinsic :: iso_c_binding, only : c_int, c_double, c_bool, c_char, c_ptr, c_size_t, c_f_pointer
|
||||
implicit none
|
||||
|
||||
implicit real*8 (a-h,o-z)
|
||||
character*6 mygrid,hisgrid,c1*1
|
||||
character*6 AzElFileName*(*),jpleph*(*)
|
||||
private
|
||||
public :: astrosub
|
||||
|
||||
contains
|
||||
|
||||
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid_cp,mygrid_len, &
|
||||
hisgrid_cp,hisgrid_len,AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8, &
|
||||
ntsky,ndop,ndop00,RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1, &
|
||||
width2,bTx,AzElFileName_cp,AzElFileName_len,jpleph_cp,jpleph_len) &
|
||||
bind (C, name="astrosub")
|
||||
|
||||
integer, parameter :: dp = selected_real_kind(15, 50)
|
||||
|
||||
integer(c_int), intent(in), value :: nyear, month, nday
|
||||
real(c_double), intent(in), value :: uth8, freq8
|
||||
real(c_double), intent(out) :: AzSun8, ElSun8, AzMoon8, ElMoon8, AzMoonB8, &
|
||||
ElMoonB8, Ramoon8, DecMoon8, Dgrd8, poloffset8, xnr8, techo8, width1, &
|
||||
width2
|
||||
integer(c_int), intent(out) :: ntsky, ndop, ndop00
|
||||
logical(c_bool), intent(in), value :: bTx
|
||||
type(c_ptr), intent(in), value :: mygrid_cp, hisgrid_cp, AzElFileName_cp, jpleph_cp
|
||||
integer(c_size_t), intent(in), value :: mygrid_len, hisgrid_len, AzElFileName_len, jpleph_len
|
||||
|
||||
character(len=6) :: mygrid, hisgrid
|
||||
character(kind=c_char, len=:), allocatable :: AzElFileName
|
||||
character(len=1) :: c1
|
||||
integer :: ih, im, imin, is, isec, nfreq, nRx
|
||||
real(dp) :: AzAux, ElAux, dbMoon8, dfdt, dfdt0, doppler, doppler00, HA8, sd8, xlst8
|
||||
character*256 jpleph_file_name
|
||||
logical*1 bTx
|
||||
common/jplcom/jpleph_file_name
|
||||
|
||||
jpleph_file_name=jpleph
|
||||
block
|
||||
character(kind=c_char, len=mygrid_len), pointer :: mygrid_fp
|
||||
character(kind=c_char, len=hisgrid_len), pointer :: hisgrid_fp
|
||||
character(kind=c_char, len=AzElFileName_len), pointer :: AzElFileName_fp
|
||||
character(kind=c_char, len=jpleph_len), pointer :: jpleph_fp
|
||||
call c_f_pointer(cptr=mygrid_cp, fptr=mygrid_fp)
|
||||
mygrid = mygrid_fp
|
||||
mygrid_fp => null()
|
||||
call c_f_pointer(cptr=hisgrid_cp, fptr=hisgrid_fp)
|
||||
hisgrid = hisgrid_fp
|
||||
hisgrid_fp => null()
|
||||
call c_f_pointer(cptr=AzElFileName_cp, fptr=AzElFileName_fp)
|
||||
AzElFileName = AzElFileName_fp
|
||||
AzElFileName_fp => null()
|
||||
call c_f_pointer(cptr=jpleph_cp, fptr=jpleph_fp)
|
||||
jpleph_file_name = jpleph_fp
|
||||
jpleph_fp => null()
|
||||
end block
|
||||
|
||||
call astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
|
||||
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &
|
||||
@ -53,3 +93,5 @@ subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
|
||||
|
||||
999 return
|
||||
end subroutine astrosub
|
||||
|
||||
end module astro_module
|
||||
|
@ -1,7 +1,7 @@
|
||||
subroutine azdist(grid1,grid2,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
|
||||
subroutine azdist(MyGrid,HisGrid,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
|
||||
|
||||
character*(*) grid1,grid2
|
||||
character*6 MyGrid,HisGrid,mygrid0,hisgrid0
|
||||
character(len=*) :: MyGrid,HisGrid
|
||||
character*6 mygrid0,hisgrid0
|
||||
real*8 utch,utch0
|
||||
logical HotABetter,IamEast
|
||||
real eltab(22),daztab(22)
|
||||
@ -12,11 +12,6 @@ subroutine azdist(grid1,grid2,utch,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter)
|
||||
data mygrid0/" "/,hisgrid0/" "/,utch0/-999.d0/
|
||||
save
|
||||
|
||||
MyGrid=grid1//' '
|
||||
HisGrid=grid2//' '
|
||||
if(ichar(MyGrid(5:5)).eq.0) MyGrid(5:6)=' '
|
||||
if(ichar(HisGrid(5:5)).eq.0) HisGrid(5:6)=' '
|
||||
|
||||
if(MyGrid.eq.HisGrid) then
|
||||
naz=0
|
||||
nel=0
|
||||
|
@ -7,6 +7,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
use jt65_decode
|
||||
use jt9_decode
|
||||
use ft8_decode
|
||||
use ft4_decode
|
||||
|
||||
include 'jt9com.f90'
|
||||
include 'timer_common.inc'
|
||||
@ -27,6 +28,10 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
integer :: decoded
|
||||
end type counting_ft8_decoder
|
||||
|
||||
type, extends(ft4_decoder) :: counting_ft4_decoder
|
||||
integer :: decoded
|
||||
end type counting_ft4_decoder
|
||||
|
||||
real ss(184,NSMAX)
|
||||
logical baddata,newdat65,newdat9,single_decode,bVHF,bad0,newdat,ex
|
||||
integer*2 id2(NTMAX*12000)
|
||||
@ -40,6 +45,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
type(counting_jt65_decoder) :: my_jt65
|
||||
type(counting_jt9_decoder) :: my_jt9
|
||||
type(counting_ft8_decoder) :: my_ft8
|
||||
type(counting_ft4_decoder) :: my_ft4
|
||||
|
||||
!cast C character arrays to Fortran character strings
|
||||
datetime=transfer(params%datetime, datetime)
|
||||
@ -53,6 +59,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
my_jt65%decoded = 0
|
||||
my_jt9%decoded = 0
|
||||
my_ft8%decoded = 0
|
||||
my_ft4%decoded = 0
|
||||
|
||||
single_decode=iand(params%nexp_decode,32).ne.0
|
||||
bVHF=iand(params%nexp_decode,64).ne.0
|
||||
@ -126,8 +133,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
n30fox(j)=n
|
||||
m=n30max-n
|
||||
if(len(trim(g2fox(j))).eq.4) then
|
||||
call azdist(mygrid,g2fox(j),0.d0,nAz,nEl,nDmiles,nDkm, &
|
||||
nHotAz,nHotABetter)
|
||||
call azdist(mygrid,g2fox(j)//' ',0.d0,nAz,nEl,nDmiles, &
|
||||
nDkm,nHotAz,nHotABetter)
|
||||
else
|
||||
nDkm=9999
|
||||
endif
|
||||
@ -142,6 +149,15 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
go to 800
|
||||
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)), &
|
||||
float(id2(300000:310000)))/10000.0)
|
||||
if(rms.lt.2.0) go to 800
|
||||
@ -258,7 +274,8 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
|
||||
!$omp end parallel sections
|
||||
|
||||
! 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
|
||||
1010 format('<DecodeFinished>',2i4)
|
||||
call flush(6)
|
||||
@ -500,7 +517,7 @@ contains
|
||||
decoded0=decoded
|
||||
|
||||
annot=' '
|
||||
if(ncontest.eq.0 .and. nap.ne.0) then
|
||||
if(nap.ne.0) then
|
||||
write(annot,'(a1,i1)') 'a',nap
|
||||
if(qual.lt.0.17) decoded0(37:37)='?'
|
||||
endif
|
||||
@ -561,4 +578,44 @@ contains
|
||||
return
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
|
||||
subroutine fast_decode(id2,narg,trperiod,line,mycall_12, &
|
||||
hiscall_12)
|
||||
|
||||
parameter (NMAX=30*12000)
|
||||
@ -6,6 +6,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
|
||||
integer*2 id2a(NMAX)
|
||||
integer*2 id2b(NMAX)
|
||||
integer narg(0:14)
|
||||
double precision trperiod
|
||||
real dat(30*12000)
|
||||
complex cdat(262145),cdat2(262145)
|
||||
real psavg(450)
|
||||
@ -41,7 +42,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
|
||||
nhashcalls=narg(12)
|
||||
|
||||
line(1:100)(1:1)=char(0)
|
||||
if(t0.gt.float(ntrperiod)) go to 900
|
||||
if(t0.gt.trperiod) go to 900
|
||||
if(t0.gt.t1) go to 900
|
||||
|
||||
if(nmode.eq.102) then
|
||||
@ -53,7 +54,7 @@ subroutine fast_decode(id2,narg,ntrperiod,line,mycall_12, &
|
||||
cdat2=cdat
|
||||
ndat=ndat0
|
||||
call wav11(id2,ndat,dat)
|
||||
nzz=11025*ntrperiod
|
||||
nzz=11025*int(trperiod) !beware if fractional T/R period ever used here
|
||||
if(ndat.lt.nzz) dat(ndat+1:nzz)=0.0
|
||||
ndat=min(ndat,30*11025)
|
||||
call ana932(dat,ndat,cdat,npts) !Make downsampled analytic signal
|
||||
|
@ -19,6 +19,7 @@ subroutine four2a(a,nfft,ndim,isign,iform)
|
||||
! This version of four2a makes calls to the FFTW library to do the
|
||||
! actual computations.
|
||||
|
||||
use fftw3
|
||||
parameter (NPMAX=2100) !Max numberf of stored plans
|
||||
parameter (NSMALL=16384) !Max size of "small" FFTs
|
||||
complex a(nfft) !Array to be transformed
|
||||
@ -29,7 +30,6 @@ subroutine four2a(a,nfft,ndim,isign,iform)
|
||||
logical found_plan
|
||||
data nplan/0/ !Number of stored plans
|
||||
common/patience/npatience,nthreads !Patience and threads for FFTW plans
|
||||
include 'fftw3.f90' !FFTW definitions
|
||||
save plan,nplan,nn,ns,nf,nl
|
||||
|
||||
if(nfft.lt.0) go to 999
|
||||
@ -107,7 +107,7 @@ subroutine four2a(a,nfft,ndim,isign,iform)
|
||||
!$omp end critical(fftw)
|
||||
end if
|
||||
enddo
|
||||
|
||||
call fftwf_cleanup()
|
||||
nplan=0
|
||||
!$omp end critical(four2a)
|
||||
|
||||
|
@ -41,6 +41,7 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
|
||||
endif
|
||||
smax=0.
|
||||
s=0.
|
||||
ipk=-99
|
||||
do i=ia,ib
|
||||
s(i)=real(cx(i))**2 + aimag(cx(i))**2
|
||||
if(s(i).gt.smax) then
|
||||
@ -49,6 +50,7 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
|
||||
endif
|
||||
enddo
|
||||
|
||||
if(ipk.ge.1) then
|
||||
call peakup(s(ipk-1),s(ipk),s(ipk+1),dx)
|
||||
fpeak=df * (ipk+dx)
|
||||
ap=(fpeak/fs+1.0/(2.0*NFFT))
|
||||
@ -67,6 +69,12 @@ subroutine freqcal(id2,k,nkhz,noffset,ntol,line)
|
||||
ave=xsum/nsum
|
||||
snr=db(smax/ave)
|
||||
pave=db(ave) + 8.0
|
||||
else
|
||||
snr=-99.9
|
||||
pave=-99.9
|
||||
fpeak=-99.9
|
||||
ferr=-99.9
|
||||
endif
|
||||
cflag=' '
|
||||
if(snr.lt.20.0) cflag='*'
|
||||
n=n+1
|
||||
|
12
lib/fsk4hf/ft2_params.f90
Normal file
12
lib/fsk4hf/ft2_params.f90
Normal 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
335
lib/fsk4hf/ft2d.f90
Normal 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
154
lib/fsk4hf/ft2sim.f90
Normal 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
329
lib/fsk4hf/ft4d.f90
Normal 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
86
lib/fsk4hf/genft2.f90
Normal 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
|
63
lib/fsk4hf/getcandidates2.f90
Normal file
63
lib/fsk4hf/getcandidates2.f90
Normal 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
|
@ -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
|
@ -70,14 +70,14 @@ endfunction
|
||||
# M-ary PSK Block Coded Modulation," Igal Sason and Gil Weichman,
|
||||
# doi: 10.1109/EEEI.2006.321097
|
||||
#-------------------------------------------------------------------------------
|
||||
N=174
|
||||
K=75
|
||||
N=128
|
||||
K=90
|
||||
R=K/N
|
||||
|
||||
delta=0.01;
|
||||
[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);
|
||||
esno=ebno*R;
|
||||
A=sqrt(2*esno);
|
||||
|
19
lib/fsk4hf/spb_128_90.dat
Normal file
19
lib/fsk4hf/spb_128_90.dat
Normal 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
6
lib/ft2/cdatetime.f90
Normal 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
279
lib/ft2/ft2.f90
Normal 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
2
lib/ft2/ft2.ini
Normal 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
298
lib/ft2/ft2_decode.f90
Normal 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
|
88
lib/ft2/ft2_gfsk_iwave.f90
Normal file
88
lib/ft2/ft2_gfsk_iwave.f90
Normal 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
64
lib/ft2/ft2_iwave.f90
Normal 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
12
lib/ft2/ft2_params.f90
Normal 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
347
lib/ft2/ft2audio.c
Normal 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
7
lib/ft2/g4.cmd
Normal 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
34
lib/ft2/gcom1.f90
Normal 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
86
lib/ft2/genft2.f90
Normal 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
Loading…
x
Reference in New Issue
Block a user