From f9d0a1863aaec0876f102a66fcc48da05dfb55e9 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Wed, 27 May 2015 13:08:28 +0000 Subject: [PATCH] Reintegrate the wsjtx_exp branch into the trunk This merge brings the WSPR feature development into the main line ready for release in a future v1.6 release. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5424 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- CMakeLists.txt | 22 +- Configuration.cpp | 11 +- Detector.hpp | 2 +- Modulator.cpp | 9 +- Modulator.hpp | 1 + about.cpp | 5 +- astro.cpp | 36 +- astro.h | 10 +- astro.ui | 476 ++++--- commons.h | 3 +- doc/common/license.adoc | 2 +- doc/user_guide/acknowledgements.adoc | 8 +- lib/astro0.f90 | 2 +- lib/astrosub.f90 | 40 +- lib/avg4.f90 | 2 +- lib/code426.f90 | 19 +- lib/constants.f90 | 2 +- lib/decode4.f90 | 3 +- lib/decoder.f90 | 9 +- lib/encode232.f90 | 2 +- lib/fil3c.f90 | 72 + lib/fillcom.f90 | 3 +- lib/flat1.f90 | 10 +- lib/genwspr.f90 | 31 + lib/grayline.f90 | 32 + lib/hash.f90 | 15 + lib/hopping.f90 | 81 ++ lib/indexx.f90 | 100 +- lib/inter_wspr.f90 | 45 + lib/jt4a.f90 | 9 +- lib/jt9.f90 | 11 +- lib/jt9c.f90 | 5 +- lib/mixlpf.f90 | 25 + lib/packjt.f90 | 1826 ++++++++++++++------------ lib/savec2.f90 | 54 + lib/smo.f90 | 10 +- lib/symspec.f90 | 28 +- lib/sync4.f90 | 1 - lib/timf2.f90 | 142 ++ lib/wqencode.f90 | 65 + lib/wsjt4.f90 | 10 +- lib/wspr_downsample.f90 | 76 ++ lib/wsprd/Makefile | 39 + lib/wsprd/Makefile.MinGW | 30 + lib/wsprd/WSPRcode.f90 | 132 ++ lib/wsprd/fano.c | 255 ++++ lib/wsprd/fano.h | 21 + lib/wsprd/fftw3.h | 410 ++++++ lib/wsprd/genmet.f90 | 50 + lib/wsprd/gran.c | 28 + lib/wsprd/metric_tables.c | 111 ++ lib/wsprd/mettab.c | 76 ++ lib/wsprd/nhash.c | 376 ++++++ lib/wsprd/t1.f90 | 63 + lib/wsprd/t2.f90 | 11 + lib/wsprd/tab.c | 41 + lib/wsprd/test_wspr.f90 | 61 + lib/wsprd/unpk.c | 123 ++ lib/wsprd/wsprd.c | 935 +++++++++++++ lib/wsprd/wsprd_stats.txt | 24 + lib/wsprd/wsprd_utils.c | 322 +++++ lib/wsprd/wsprd_utils.h | 24 + mainwindow.cpp | 1309 +++++++++++++----- mainwindow.h | 74 +- mainwindow.ui | 858 +++++++++--- plotter.cpp | 76 +- plotter.h | 7 +- widegraph.cpp | 32 +- widegraph.h | 4 + widegraph.ui | 499 ++++--- wsjtx.pro | 9 +- wsprnet.cpp | 192 +++ wsprnet.h | 37 + 73 files changed, 7563 insertions(+), 1981 deletions(-) create mode 100644 lib/fil3c.f90 create mode 100644 lib/genwspr.f90 create mode 100644 lib/grayline.f90 create mode 100644 lib/hash.f90 create mode 100644 lib/hopping.f90 create mode 100644 lib/inter_wspr.f90 create mode 100644 lib/mixlpf.f90 create mode 100644 lib/savec2.f90 create mode 100644 lib/timf2.f90 create mode 100644 lib/wqencode.f90 create mode 100644 lib/wspr_downsample.f90 create mode 100644 lib/wsprd/Makefile create mode 100644 lib/wsprd/Makefile.MinGW create mode 100644 lib/wsprd/WSPRcode.f90 create mode 100644 lib/wsprd/fano.c create mode 100644 lib/wsprd/fano.h create mode 100644 lib/wsprd/fftw3.h create mode 100644 lib/wsprd/genmet.f90 create mode 100644 lib/wsprd/gran.c create mode 100644 lib/wsprd/metric_tables.c create mode 100644 lib/wsprd/mettab.c create mode 100644 lib/wsprd/nhash.c create mode 100644 lib/wsprd/t1.f90 create mode 100644 lib/wsprd/t2.f90 create mode 100644 lib/wsprd/tab.c create mode 100644 lib/wsprd/test_wspr.f90 create mode 100644 lib/wsprd/unpk.c create mode 100644 lib/wsprd/wsprd.c create mode 100644 lib/wsprd/wsprd_stats.txt create mode 100644 lib/wsprd/wsprd_utils.c create mode 100644 lib/wsprd/wsprd_utils.h create mode 100644 wsprnet.cpp create mode 100644 wsprnet.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e4e02b066..9f2d3fc01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,7 @@ set (wsjtx_CXXSRCS mainwindow.cpp Configuration.cpp main.cpp + wsprnet.cpp ) if (WIN32) @@ -288,6 +289,7 @@ set (wsjt_FSRCS lib/fchisq.f90 lib/fchisq65.f90 lib/fil3.f90 + lib/fil3c.f90 lib/fil4.f90 lib/fil6521.f90 lib/filbig.f90 @@ -302,20 +304,26 @@ set (wsjt_FSRCS lib/gen4.f90 lib/gen65.f90 lib/gen9.f90 + lib/genwspr.f90 lib/geodist.f90 lib/getlags.f90 lib/graycode.f90 lib/graycode65.f90 + lib/grayline.f90 lib/grid2deg.f90 + lib/hash.f90 + lib/hopping.f90 lib/image.f90 lib/indexx.f90 lib/interleave4.f90 lib/interleave63.f90 lib/interleave9.f90 + lib/inter_wspr.f90 lib/jt4.f90 lib/jt4a.f90 lib/jt65a.f90 lib/lpf1.f90 + lib/mixlpf.f90 lib/moon2.f90 lib/moondop.f90 lib/morse.f90 @@ -328,6 +336,7 @@ set (wsjt_FSRCS lib/polyfit.f90 lib/prog_args.f90 lib/ps4.f90 + lib/savec2.f90 lib/sec_midn.f90 lib/setup65.f90 lib/sleep_msec.f90 @@ -344,6 +353,7 @@ set (wsjt_FSRCS lib/sync4.f90 lib/sync9.f90 lib/timer.f90 + lib/timf2.f90 lib/tm2.f90 lib/toxyz.f90 lib/twkfreq.f90 @@ -355,6 +365,8 @@ set (wsjt_FSRCS lib/xcor4.f90 lib/zplt.f90 lib/wavhdr.f90 + lib/wqencode.f90 + lib/wspr_downsample.f90 lib/zplot9.f90 ) @@ -364,6 +376,7 @@ set (wsjt_CSRCS lib/gran.c lib/igray.c lib/init_rs.c + lib/wsprd/nhash.c lib/tmoonsub.c lib/usleep.c lib/wisdom.c @@ -657,7 +670,6 @@ if (NOT "${QT_LIBRARY_DIR}" STREQUAL "/lib" AND NOT "${QT_LIBRARY_DIR}" STREQUAL set (QT_NEED_RPATH TRUE) endif () - # # OpenMP # @@ -666,10 +678,9 @@ find_package (OpenMP) # # fftw3 single precsion library # -find_package (FFTW3 COMPONENTS single threads REQUIRED) +find_package (FFTW3 COMPONENTS double single threads REQUIRED) include_directories (${FFTW3_INCLUDE_DIRS}) - # # libhamlib setup # @@ -838,6 +849,9 @@ target_link_libraries (jt65code wsjt_fort wsjt_cxx) add_executable (jt9code lib/jt9code.f90 wsjtx.rc) target_link_libraries (jt9code wsjt_fort wsjt_cxx) +add_executable (wsprd lib/wsprd/wsprd.c lib/wsprd/wsprd_utils.c lib/wsprd/fano.c lib/wsprd/tab.c lib/wsprd/nhash.c) +target_link_libraries (wsprd ${FFTW3_LIBRARIES}) + add_executable (jt4code lib/jt4code.f90 wsjtx.rc) target_link_libraries (jt4code wsjt_fort wsjt_cxx) @@ -946,7 +960,7 @@ install (TARGETS wsjtx BUNDLE DESTINATION . COMPONENT runtime ) -install (TARGETS jt9 jt65code jt9code jt4code message_aggregator +install (TARGETS jt9 jt65code jt9code jt4code wsprd message_aggregator RUNTIME DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime BUNDLE DESTINATION ${WSJT_BIN_DESTINATION} COMPONENT runtime ) diff --git a/Configuration.cpp b/Configuration.cpp index 2ddc86388..0804dbbc0 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -615,7 +615,8 @@ bool Configuration::enable_VHF_features () const {return m_->enable_VHF_features bool Configuration::decode_at_52s () const {return m_->decode_at_52s_;} bool Configuration::split_mode () const { - return !m_->rig_is_dummy_ && m_->rig_params_.split_mode != TransceiverFactory::split_mode_none; + return !m_->rig_is_dummy_ and + (m_->rig_params_.split_mode != TransceiverFactory::split_mode_none); } QString Configuration::udp_server_name () const {return m_->udp_server_name_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} @@ -704,9 +705,11 @@ Configuration::impl::impl (Configuration * self, QSettings * settings, QWidget * , settings_ {settings} , doc_dir_ {QApplication::applicationDirPath ()} , frequencies_ { - { 136130, 474200, 1838000, 3576000, 5357000, 7076000, 10138000, 14076000, 18102000, - 21076000, 24917000, 28076000, 50276000, 70091000, 144000000, 144489000, 222000000, - 432000000, 902000000, 1296000000, 2301000000, 2304000000, 2320000000, 3400000000, + { 136000, 136130, 474200, 1836600, 1838000, 3576000, 3592600, 5287200, 5357000, + 7038600, 7076000, 10138000, 10138700, 14076000, 14095600, 18102000, 18104600, + 21076000, 21094600, 24917000, 24924600, 28076000, 28124600, 50276000, 50293000, + 70091000, 144000000, 144489000, 222000000, 432000000, 432300000, + 902000000, 1296000000, 1296500000, 2301000000, 2304000000, 2320000000, 3400000000, 3456000000, 5760000000,10368000000, 24048000000 } } , stations_ {&bands_} diff --git a/Detector.hpp b/Detector.hpp index a90875a43..0ef171eae 100644 --- a/Detector.hpp +++ b/Detector.hpp @@ -27,7 +27,7 @@ public: Detector (unsigned frameRate, unsigned periodLengthInSeconds, unsigned samplesPerFFT, unsigned downSampleFactor = 4u, QObject * parent = 0); Q_SIGNAL void framesWritten (qint64) const; - + void setPeriod(unsigned p) {m_period=p;} bool reset () override; protected: diff --git a/Modulator.cpp b/Modulator.cpp index 8806a63a1..dce905b4f 100644 --- a/Modulator.cpp +++ b/Modulator.cpp @@ -24,7 +24,8 @@ double const Modulator::m_twoPi = 2.0 * 3.141592653589793238462; // m_nspd=3072; //18.75 WPM unsigned const Modulator::m_nspd = 2048 + 512; // 22.5 WPM -Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObject * parent) +Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, + QObject * parent) : AudioDevice {parent} , m_stream {nullptr} , m_quickClose {false} @@ -41,7 +42,10 @@ Modulator::Modulator (unsigned frameRate, unsigned periodLengthInSeconds, QObjec m_itone0=0; } -void Modulator::start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, double toneSpacing, SoundOutput * stream, Channel channel, bool synchronize, double dBSNR) +void Modulator::start (unsigned symbolsLength, double framesPerSymbol, + unsigned frequency, double toneSpacing, + SoundOutput * stream, Channel channel, + bool synchronize, double dBSNR) { Q_ASSERT (stream); @@ -264,6 +268,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize) m_itone0=itone[0]; */ m_frequency0 = m_frequency; +// qDebug() << "a" << m_frequency << m_nsps << m_toneSpacing << toneFrequency0 << baud << isym; // done for this chunk - continue on next call return framesGenerated * bytesPerFrame (); diff --git a/Modulator.hpp b/Modulator.hpp index c7d7fcc30..630d33e81 100644 --- a/Modulator.hpp +++ b/Modulator.hpp @@ -30,6 +30,7 @@ public: unsigned frequency () const {return m_frequency;} bool isActive () const {return m_state != Idle;} void setSpread(double s) {m_fSpread=s;} + void setPeriod(unsigned p) {m_period=p;} Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, unsigned frequency, double toneSpacing, SoundOutput *, Channel = Mono, bool synchronize = true, double dBSNR = 99.); Q_SLOT void stop (bool quick = false); diff --git a/about.cpp b/about.cpp index 99f51941a..5416580c0 100644 --- a/about.cpp +++ b/about.cpp @@ -23,8 +23,9 @@ CAboutDlg::CAboutDlg(QWidget *parent) : "Amateur Radio communication.

" "© 2001-2015 by Joe Taylor, K1JT, with grateful
" "acknowledgment for contributions from AC6SL, AE4JY,
" - "DJ0OT, G4KLA, G4WJS, K3WYC, KA6MAL, KA9Q, KB1ZMX,
" - "KI7MT, KK1D, PY2SDR, VK3ACF, VK4BDJ, W4TI, W4TV, and W9MDB.
"); + "DJ0OT, G4KLA, G4WJS, K3WYC, K9AN, KA6MAL, KA9Q,
" + "KB1ZMX, KD6EKQ, KI7MT, KK1D, ND0B, PY2SDR,
" + "VK3ACF, VK4BDJ, W4TI, W4TV, and W9MDB.
"); } CAboutDlg::~CAboutDlg() diff --git a/astro.cpp b/astro.cpp index f1c9822a9..384844365 100644 --- a/astro.cpp +++ b/astro.cpp @@ -29,6 +29,7 @@ Astro::Astro(QSettings * settings, QWidget * parent) setWindowTitle(QApplication::applicationName () + " - " + tr ("Astronomical Data")); setStyleSheet ("QWidget {background: white;}"); read_settings (); + m_Hz=0; ui_->text_label->clear(); } @@ -60,9 +61,8 @@ void Astro::read_settings () m_kHz=settings_->value("kHzAdd",100).toInt(); ui_->kHzSpinBox->setValue(m_kHz); m_bRxAudioTrack=settings_->value("RxAudioTrack",false).toBool(); - ui_->cbRxTrack->setChecked(m_bRxAudioTrack); m_bTxAudioTrack=settings_->value("TxAudioTrack",false).toBool(); - ui_->cbTxTrack->setChecked(m_bTxAudioTrack); + ui_->cbTxAudioTrack->setChecked(m_bTxAudioTrack); move (settings_->value ("window/pos", pos ()).toPoint ()); settings_->endGroup (); } @@ -82,10 +82,10 @@ void Astro::write_settings () } void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 freqMoon, - qint32* ndop, qint32* ndop00) + qint32* ndop, qint32* ndop00, bool bTx) { double azsun,elsun,azmoon,elmoon,azmoondx,elmoondx; - double ramoon,decmoon,dgrd,poloffset,xnr,techo; + double ramoon,decmoon,dgrd,poloffset,xnr,techo,width1,width2; int ntsky; QString date = t.date().toString("yyyy MMM dd").trimmed (); QString utc = t.time().toString().trimmed (); @@ -95,16 +95,19 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 fre int nhr=t.time().hour(); int nmin=t.time().minute(); double sec=t.time().second() + 0.001*t.time().msec(); - int isec=sec; double uth=nhr + nmin/60.0 + sec/3600.0; if(freqMoon < 1) freqMoon=144000000; int nfreq=freqMoon/1000000; double freq8=(double)freqMoon; + QDir dataDir = QStandardPaths::writableLocation (QStandardPaths::DataLocation); + QString fname = QDir::toNativeSeparators(dataDir.absoluteFilePath ("azel.dat")); + astrosub_(&nyear, &month, &nday, &uth, &freq8, mygrid.toLatin1(), hisgrid.toLatin1(), &azsun, &elsun, &azmoon, &elmoon, &azmoondx, &elmoondx, &ntsky, ndop, ndop00, &ramoon, &decmoon, - &dgrd, &poloffset, &xnr, &techo, 6, 6); + &dgrd, &poloffset, &xnr, &techo, &width1, &width2, &bTx, + fname.toLatin1(), 6, 6, fname.length()); QString message; { @@ -117,13 +120,15 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 fre << qSetRealNumberPrecision (1) << "Az: " << azmoon << "\n" "El: " << elmoon << "\n" - "MyDop: " << *ndop00 << "\n" + "Dop: " << *ndop00 << "\n" + "Width: " << int(width1) << "\n" << qSetRealNumberPrecision (2) << "Delay: " << techo << "\n" << qSetRealNumberPrecision (1) << "DxAz: " << azmoondx << "\n" "DxEl: " << elmoondx << "\n" "DxDop: " << *ndop << "\n" + "DxWid: " << int(width2) << "\n" "Dec: " << decmoon << "\n" "SunAz: " << azsun << "\n" "SunEl: " << elsun << "\n" @@ -134,6 +139,7 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 fre } ui_->text_label->setText(message); + /* static QFile f {QDir {QStandardPaths::writableLocation ( QStandardPaths::DataLocation)}.absoluteFilePath ("azel.dat")}; if (!f.open (QIODevice::WriteOnly | QIODevice::Text)) { @@ -185,13 +191,14 @@ void Astro::astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 fre << qSetFieldWidth (0) << ",Doppler"; } f.close(); + */ } void Astro::on_cbDopplerTracking_toggled(bool b) { QRect g=this->geometry(); if(b) { - g.setWidth(460); + g.setWidth(430); } else { g.setWidth(200); } @@ -228,15 +235,9 @@ void Astro::on_rb10Hz_clicked() void Astro::on_rb100Hz_clicked() { m_stepHz=100; - } -void Astro::on_cbRxTrack_toggled(bool b) -{ - m_bRxAudioTrack=b; -} - -void Astro::on_cbTxTrack_toggled(bool b) +void Astro::on_cbTxAudioTrack_toggled(bool b) { m_bTxAudioTrack=b; } @@ -245,3 +246,8 @@ void Astro::on_kHzSpinBox_valueChanged(int n) { m_kHz=n; } + +void Astro::on_HzSpinBox_valueChanged(int n) +{ + m_Hz=n; +} diff --git a/astro.h b/astro.h index 90e2fd728..dcf18a8fd 100644 --- a/astro.h +++ b/astro.h @@ -23,7 +23,7 @@ public: explicit Astro(QSettings * settings, QWidget * parent = nullptr); ~Astro (); void astroUpdate(QDateTime t, QString mygrid, QString hisgrid, qint64 freqMoon, - qint32* ndop, qint32 *ndop00); + qint32* ndop, qint32 *ndop00, bool bTx); bool m_bDopplerTracking; bool m_bRxAudioTrack; @@ -31,6 +31,7 @@ public: qint32 m_DopplerMethod; qint32 m_kHz; + qint32 m_Hz; qint32 m_stepHz; protected: @@ -44,9 +45,9 @@ private slots: void on_rb1Hz_clicked(); void on_rb10Hz_clicked(); void on_rb100Hz_clicked(); - void on_cbRxTrack_toggled(bool b); - void on_cbTxTrack_toggled(bool b); + void on_cbTxAudioTrack_toggled(bool b); void on_kHzSpinBox_valueChanged(int n); + void on_HzSpinBox_valueChanged(int n); private: void read_settings (); @@ -63,7 +64,8 @@ extern "C" { double* elsun, double* azmoon, double* elmoon, double* azmoondx, double* elmoondx, int* ntsky, int* ndop, int* ndop00, double* ramoon, double* decmoon, double* dgrd, double* poloffset, - double* xnr, double* techo, int len1, int len2); + double* xnr, double* techo, double* width1, double* width2, + bool* bTx, const char* fname, int len1, int len2, int len3); } #endif // ASTRO_H diff --git a/astro.ui b/astro.ui index 8ecf07d99..926a9774b 100644 --- a/astro.ui +++ b/astro.ui @@ -6,8 +6,8 @@ 0 0 - 460 - 420 + 400 + 440 @@ -19,7 +19,7 @@ 200 - 420 + 440 @@ -31,7 +31,7 @@ 0 0 201 - 361 + 400 @@ -48,7 +48,7 @@ - Courier New + Courier 14 75 false @@ -71,222 +71,11 @@ 6 - - - - 219 - 19 - 221 - 361 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 20 - 20 - 185 - 96 - - - - - 185 - 0 - - - - Doppler tracking - - - - - - Full Doppler to DX Grid - - - true - - - - - - - Constant frequency on Moon - - - false - - - - - - - None - - - false - - - - - - - - - 20 - 130 - 185 - 96 - - - - - 185 - 0 - - - - Transceiver step size - - - - - 10 - 23 - 61 - 17 - - - - 1 Hz - - - true - - - - - - 10 - 46 - 71 - 17 - - - - 10 Hz - - - - - - 10 - 69 - 71 - 17 - - - - 100 Hz - - - - - - false - - - - 20 - 230 - 185 - 73 - - - - - 185 - 0 - - - - Audio frequency tracking - - - - - 10 - 23 - 36 - 17 - - - - Rx - - - - - - 10 - 46 - 35 - 17 - - - - Tx - - - - - - - 20 - 310 - 185 - 51 - - - - - 185 - 0 - - - - kHz above nominal band edge - - - - - 50 - 20 - 51 - 22 - - - - 999 - - - 100 - - - - 1 - 386 + 410 195 22 @@ -327,6 +116,259 @@ + + + + 200 + 12 + 198 + 411 + + + + + + + + 196 + 0 + + + + + 16777215 + 60 + + + + Frequency above nominal band edge + + + + + 20 + 20 + 75 + 22 + + + + + 75 + 0 + + + + Qt::AlignCenter + + + kHz + + + 999 + + + 200 + + + + + + 100 + 20 + 75 + 22 + + + + + 75 + 0 + + + + Qt::AlignCenter + + + Hz + + + -2000 + + + 2000 + + + 100 + + + + + + + + + 196 + 0 + + + + + 16777215 + 100 + + + + Doppler tracking + + + + + + Full Doppler to DX Grid + + + true + + + + + + + Constant frequency on Moon + + + false + + + + + + + None + + + false + + + + + + + + + + + 196 + 0 + + + + + 16777215 + 90 + + + + Transceiver step size + + + + + 10 + 23 + 61 + 17 + + + + 1 Hz + + + true + + + + + + 10 + 46 + 71 + 17 + + + + 10 Hz + + + + + + 10 + 69 + 71 + 17 + + + + 100 Hz + + + + + + + + + 196 + 0 + + + + + 16777215 + 60 + + + + Tx audio tracking + + + + false + + + + 20 + 20 + 105 + 17 + + + + Enable + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 44 + + + + + + diff --git a/commons.h b/commons.h index a9dd67be2..d572e1d3c 100644 --- a/commons.h +++ b/commons.h @@ -2,7 +2,7 @@ #define COMMONS_H #define NSMAX 6827 -#define NTMAX 60 +#define NTMAX 120 #define RX_SAMPLE_RATE 12000 extern struct FortranCommon { @@ -28,6 +28,7 @@ extern struct FortranCommon { int nmode; int minw; int nclearave; + int minSync; float emedelay; float dttol; int nlist; diff --git a/doc/common/license.adoc b/doc/common/license.adoc index 2d1d32cc0..887e6682f 100644 --- a/doc/common/license.adoc +++ b/doc/common/license.adoc @@ -13,4 +13,4 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this documentation. If not, see {gnu_gpl}. -Copyright (C) 2001-2014 Joseph H Taylor, Jr, {joe_taylor}. +Copyright (C) 2001-2015 Joseph H Taylor, Jr, {joe_taylor}. diff --git a/doc/user_guide/acknowledgements.adoc b/doc/user_guide/acknowledgements.adoc index d5f03ee51..b9f73bb7e 100644 --- a/doc/user_guide/acknowledgements.adoc +++ b/doc/user_guide/acknowledgements.adoc @@ -8,10 +8,10 @@ suggestions and advice that have greatly aided the development of _WSJT_ and its sister programs. For _WSJT-X_ in particular, we acknowledge contributions from *AC6SL, -AE4JY, DJ0OT, G4KLA, G4WJS, K3WYC, KA6MAL, KA9Q, KB1ZMX, KI7MT, KK1D, -PY2SDR, VK3ACF, VK4BDJ, W4TI, W4TV, and W9MDB*. Each of these -amateurs has helped to bring the program’s design, code, and -documentation to its present state. +AE4JY, DJ0OT, G4KLA, G4WJS, K3WYC, K9AN, KA6MAL, KA9Q, KB1ZMX, KD6EKQ, +KI7MT, KK1D, ND0B, PY2SDR, VK3ACF, VK4BDJ, W4TI, W4TV, and W9MDB*. +Each of these amateurs has helped to bring the program’s design, code, +and documentation to its present state. Most of the color palettes for the _WSJT-X_ waterfall were copied from the excellent, well documented, open-source program _fldigi_, by *W1HKJ* diff --git a/lib/astro0.f90 b/lib/astro0.f90 index 847197cac..0075e1ffd 100644 --- a/lib/astro0.f90 +++ b/lib/astro0.f90 @@ -31,7 +31,7 @@ subroutine astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, & call tm2(day8,xlat2,xlon2,xl2,b2) call tm2(day8+1.d0/1440.0,xlat1,xlon1,xl1a,b1a) call tm2(day8+1.d0/1440.0,xlat2,xlon2,xl2a,b2a) - fghz=0.001*nfreq + fghz=1.d-9*freq8 dldt1=DEGS*(xl1a-xl1) dbdt1=DEGS*(b1a-b1) dldt2=DEGS*(xl2a-xl2) diff --git a/lib/astrosub.f90 b/lib/astrosub.f90 index 41c9717f4..db424356c 100644 --- a/lib/astrosub.f90 +++ b/lib/astrosub.f90 @@ -1,14 +1,48 @@ subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid,hisgrid, & AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, & - RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8) + RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1,width2,bTx,fname) implicit real*8 (a-h,o-z) - character*6 mygrid,hisgrid + character*6 mygrid,hisgrid,fname*(*),c1*1 + logical*1 bTx call astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, & AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, & dbMoon8,RAMoon8,DecMoon8,HA8,Dgrd8,sd8,poloffset8,xnr8,dfdt,dfdt0, & width1,width2,w501,w502,xlst8,techo8) - return + imin=60*uth8 + isec=3600*uth8 + ih=uth8 + im=mod(imin,60) + is=mod(isec,60) + open(15,file=fname,status='unknown',err=900) + c1='R' + nRx=1 + if(bTx) then + c1='T' + nRx=0 + endif + AzAux=0. + ElAux=0. + nfreq=freq8/1000000 + doppler=ndop + doppler00=ndop00 + write(15,1010,err=10) ih,im,is,AzMoon8,ElMoon8, & + ih,im,is,AzSun8,ElSun8, & + ih,im,is,AzAux,ElAux, & + nfreq,doppler,dfdt,doppler00,dfdt0,c1 +! TXFirst,TRPeriod,poloffset,Dgrd,xnr,ave,rms,nRx +1010 format( & + i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Moon'/ & + i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Sun'/ & + i2.2,':',i2.2,':',i2.2,',',f5.1,',',f5.1,',Source'/ & + i5,',',f8.1,',',f8.2,',',f8.1,',',f8.2,',Doppler, ',a1) +! i1,',',i3,',',f8.1,','f8.1,',',f8.1,',',f12.3,',',f12.3,',',i1,',RPol') +10 close(15) + go to 999 + +900 print*,'Error opening azel.dat' + +999 return end subroutine astrosub diff --git a/lib/avg4.f90 b/lib/avg4.f90 index d97fe7f67..da9c9358b 100644 --- a/lib/avg4.f90 +++ b/lib/avg4.f90 @@ -69,7 +69,7 @@ subroutine avg4(nutc,snrsync,dtxx,flip,nfreq,mode4,ntol,ndepth,neme, & do i=1,nsave csync='*' if(flipsave(i).lt.0.0) csync='#' - write(14,1000) cused(i),iutc(i),syncsave(i),dtsave(i),nfsave(i),csync + write(14,1000) cused(i),iutc(i),syncsave(i)-5.0,dtsave(i),nfsave(i),csync 1000 format(a1,i5.4,f6.1,f6.2,i6,1x,a1) enddo diff --git a/lib/code426.f90 b/lib/code426.f90 index 0ded58889..7acf03acd 100644 --- a/lib/code426.f90 +++ b/lib/code426.f90 @@ -39,15 +39,18 @@ program code426 do j=5,nmsgs !Find codewords up to j=nmsgs with maximum npk=0 !distance from all the rest do i=1,iters - call random_number(c) - ic(1:MZ,j)=int(4*c) - nd=MZ - do k=1,j-1 !Test candidate against all others in list - nd=min(nd,count(ic(1:MZ,j).ne.ic(1:MZ,k))) - enddo - if(nd.gt.npk) then - npk=nd + call random_number(c) !Generate a random codeword candidate + ic(1:MZ,j)=int(4*c) !Convert real to integer +! nd=MZ +! do k=1,j-1 !Test candidate against all others in list +! n=count(ic(1:MZ,j).ne.ic(1:MZ,k)) +! nd=min(n,nd) +! enddo + call dist426(ic,j,mind) + if(mind.gt.npk) then + npk=mind icsave=ic(1:MZ,j) !Best candidate so far, save it +! if(npk.ge.19) exit !It won't get any better... endif enddo write(*,1000) j,npk,ic(1:MZ,j) diff --git a/lib/constants.f90 b/lib/constants.f90 index 321bd24e7..e9a4c034a 100644 --- a/lib/constants.f90 +++ b/lib/constants.f90 @@ -1,4 +1,4 @@ - parameter (NTMAX=60) + parameter (NTMAX=120) parameter (NMAX=NTMAX*12000) !Total sample intervals (one minute) parameter (NDMAX=NTMAX*1500) !Sample intervals at 1500 Hz rate parameter (NSMAX=6827) !Max length of saved spectra diff --git a/lib/decode4.f90 b/lib/decode4.f90 index 1efdbc547..0ad4e7700 100644 --- a/lib/decode4.f90 +++ b/lib/decode4.f90 @@ -23,7 +23,8 @@ subroutine decode4(dat,npts,dtx,nfreq,flip,mode4,ndepth,neme,minw, & istart=nint((dtx+0.8)/dt) !Start index for synced FFTs if(istart.lt.0) istart=0 nchips=0 - qbest=0.0 + qbest=0. + qtop=0. deepmsg=' ' ichbest=-1 c0=0. diff --git a/lib/decoder.f90 b/lib/decoder.f90 index 3448e36f7..fbaabc0e6 100644 --- a/lib/decoder.f90 +++ b/lib/decoder.f90 @@ -11,7 +11,8 @@ subroutine decoder(ss,id2,nfsample) character datetime*20,mycall*12,mygrid*6,hiscall*12,hisgrid*6 common/npar/nutc,ndiskdat,ntrperiod,nfqso,newdat,npts8,nfa,nfsplit,nfb, & ntol,kin,nzhsym,nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave, & - emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid,hiscall,hisgrid + minsync,emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid, & + hiscall,hisgrid common/tracer/limtrace,lu integer onlevel(0:10) @@ -41,9 +42,9 @@ subroutine decoder(ss,id2,nfsample) if(nfsample.eq.12000) call wav11(id2,jz,dd) if(nfsample.eq.11025) dd(1:jz)=id2(1:jz) endif - call jt4a(dd,jz,nutc,nfqso,newdat,nfa,nfb,ntol,emedelay,dttol, & - nagain,ndepth,nclearave,minw,nsubmode,mycall,mygrid,hiscall, & - hisgrid,nlist,listutc) + call jt4a(dd,jz,nutc,nfqso,ntol,emedelay,dttol,nagain,ndepth, & + nclearave,minsync,minw,nsubmode,mycall,hiscall,hisgrid, & + nlist,listutc) go to 800 endif diff --git a/lib/encode232.f90 b/lib/encode232.f90 index bc904b488..491f204ab 100644 --- a/lib/encode232.f90 +++ b/lib/encode232.f90 @@ -3,7 +3,7 @@ subroutine encode232(dat,nsym,symbol) ! Convolutional encoder for a K=32, r=1/2 code. integer*1 dat(13) !User data, packed 8 bits per byte - integer*1 symbol(500) !Channel symbols, one bit per byte + integer*1 symbol(206) !Channel symbols, one bit per byte integer*1 i1 include 'conv232.f90' diff --git a/lib/fil3c.f90 b/lib/fil3c.f90 new file mode 100644 index 000000000..6000253f8 --- /dev/null +++ b/lib/fil3c.f90 @@ -0,0 +1,72 @@ +subroutine fil3c(c1,n1,c2,n2) + +! FIR complex-to-complex low-pass filter designed with ScopeFIR +! +!----------------------------------------------- +! fsample (Hz) 12000 Input sample rate +! Ntaps 113 Number of filter taps +! fc (Hz) 500 Cutoff frequency +! fstop (Hz) 750 Lower limit of stopband +! Ripple (dB) 0.2 Ripple in passband +! Stop Atten (dB) 50 Stopband attenuation +! fout (Hz) 1500 Output sample rate + +! Suggest calling with n1 = 8*n2 + 105, where n2 is the desired number +! of 1500 Hz output samples. + + parameter (NTAPS=113) + parameter (NH=NTAPS/2) + parameter (NDOWN=8) !Downsample ratio = 1/8 + complex c1(n1) + complex c2(n1/NDOWN) + complex z + +! Filter coefficients: + real a(-NH:NH) + data a/ & + -0.001818142144,-0.000939132050,-0.001044063556,-0.001042685542, & + -0.000908957610,-0.000628132309,-0.000202701465, 0.000346307629, & + 0.000978154552, 0.001634336295, 0.002243121592, 0.002726064379, & + 0.003006201675, 0.003018055983, 0.002717699575, 0.002091546534, & + 0.001162489032,-0.000007904811,-0.001321554806,-0.002649908053, & + -0.003843608784,-0.004747338068,-0.005218967042,-0.005148229529, & + -0.004470167307,-0.003177923811,-0.001335998901, 0.000915924193, & + 0.003386100636, 0.005818719744, 0.007939147967, 0.009465071347, & + 0.010145641899, 0.009787447819, 0.008285915754, 0.005645995244, & + 0.001995842303,-0.002410369720,-0.007202515555,-0.011916811719, & + -0.016028350845,-0.018993391440,-0.020297455955,-0.019503792208, & + -0.016298136197,-0.010526834635,-0.002223837363, 0.008378305829, & + 0.020854478160, 0.034608532659, 0.048909701463, 0.062944127288, & + 0.075874892030, 0.086903764340, 0.095332017649, 0.100619428175, & + 0.102420526192, 0.100619428175, 0.095332017649, 0.086903764340, & + 0.075874892030, 0.062944127288, 0.048909701463, 0.034608532659, & + 0.020854478160, 0.008378305829,-0.002223837363,-0.010526834635, & + -0.016298136197,-0.019503792208,-0.020297455955,-0.018993391440, & + -0.016028350845,-0.011916811719,-0.007202515555,-0.002410369720, & + 0.001995842303, 0.005645995244, 0.008285915754, 0.009787447819, & + 0.010145641899, 0.009465071347, 0.007939147967, 0.005818719744, & + 0.003386100636, 0.000915924193,-0.001335998901,-0.003177923811, & + -0.004470167307,-0.005148229529,-0.005218967042,-0.004747338068, & + -0.003843608784,-0.002649908053,-0.001321554806,-0.000007904811, & + 0.001162489032, 0.002091546534, 0.002717699575, 0.003018055983, & + 0.003006201675, 0.002726064379, 0.002243121592, 0.001634336295, & + 0.000978154552, 0.000346307629,-0.000202701465,-0.000628132309, & + -0.000908957610,-0.001042685542,-0.001044063556,-0.000939132050, & + -0.001818142144/ + save a + + n2=(n1-NTAPS+NDOWN)/NDOWN + k0=NH-NDOWN+1 + +! Loop over all output samples + do i=1,n2 + z=0. + k=k0 + NDOWN*i + do j=-NH,NH + z=z + c1(j+k)*a(j) + enddo + c2(i)=z + enddo + + return +end subroutine fil3c diff --git a/lib/fillcom.f90 b/lib/fillcom.f90 index df7018edc..b5ff419fb 100644 --- a/lib/fillcom.f90 +++ b/lib/fillcom.f90 @@ -5,7 +5,8 @@ subroutine fillcom(nutc0,ndepth0,nrxfreq,mode,tx9,flow,fsplit,fhigh) character datetime*20,mycall*12,mygrid*6,hiscall*12,hisgrid*6 common/npar/nutc,ndiskdat,ntrperiod,nfqso,newdat,npts8,nfa,nfsplit,nfb, & ntol,kin,nzhsym,nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave, & - emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid,hiscall,hisgrid + minsync,emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid, & + hiscall,hisgrid save nutc=nutc0 diff --git a/lib/flat1.f90 b/lib/flat1.f90 index b9dfdd1d5..1de8b95c3 100644 --- a/lib/flat1.f90 +++ b/lib/flat1.f90 @@ -12,14 +12,10 @@ subroutine flat1(savg,iz,nsmo,syellow) call pctile(savg(i-nsmo/2),nsmo,50,x(i)) x(i-nh:i+nh-1)=x(i) enddo - do i=1,ia-1 - x(i)=x(ia) - enddo - do i=ib+1,iz - x(i)=x(ib) - enddo + x(1:ia-1)=x(ia) + x(ib+1:iz)=x(ib) - x0=0.001*maxval(x(1:iz)) + x0=0.001*maxval(x(iz/10:(9*iz)/10)) syellow(1:iz)=savg(1:iz)/(x(1:iz)+x0) return diff --git a/lib/genwspr.f90 b/lib/genwspr.f90 new file mode 100644 index 000000000..8d395dfe2 --- /dev/null +++ b/lib/genwspr.f90 @@ -0,0 +1,31 @@ +subroutine genwspr(message,msgsent,itone) +! Encode a WSPR message and generate the array of channel symbols. + + character*22 message,msgsent + parameter (MAXSYM=176) + integer*1 symbol(MAXSYM) + integer*1 data0(11) + integer*4 itone(162) + integer npr3(162) + data npr3/ & + 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, & + 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, & + 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, & + 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, & + 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, & + 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, & + 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, & + 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, & + 0,0/ + + call wqencode(message,ntype,data0) !Source encoding + nbytes=(50+31+7)/8 + call encode232(data0,162,symbol) !Convolutional encoding + call inter_wspr(symbol,1) !Interleaving + do i=1,162 + itone(i)=npr3(i) + 2*symbol(i) + enddo + msgsent=message !### To be fixed... ?? ### + + return +end subroutine genwspr diff --git a/lib/grayline.f90 b/lib/grayline.f90 new file mode 100644 index 000000000..cc1aeeea2 --- /dev/null +++ b/lib/grayline.f90 @@ -0,0 +1,32 @@ +subroutine grayline(nyear,month,nday,uth,mygrid,nduration,isun) + + character*6 mygrid + real LST + real lat,lon + + call grid2deg(MyGrid,elon,lat) + lon=-elon + + uth0=uth-0.5*nduration/60.0 + uth1=uth+0.5*nduration/60.0 + + call sun(nyear,month,nday,uth0,lon,lat,RASun,DecSun,LST, & + AzSun,ElSun0,mjd,day) + call sun(nyear,month,nday,uth1,lon,lat,RASun,DecSun,LST, & + AzSun,ElSun1,mjd,day) + + elchk=-0.8333 + isun=-1 + if(elsun0.lt.elchk .and. elsun1.ge.elchk) then + isun=0 + else if(elsun0.gt.elchk .and. elsun1.le.elchk) then + isun=2 + else if(elsun1.gt.elchk) then + isun=1 + else + isun=3 + endif + + return +end subroutine grayline + diff --git a/lib/hash.f90 b/lib/hash.f90 new file mode 100644 index 000000000..679e00c0a --- /dev/null +++ b/lib/hash.f90 @@ -0,0 +1,15 @@ +subroutine hash(string,len,ihash) + + parameter (MASK15=32767) + character*(*) string + integer*1 ic(12) + + do i=1,len + ic(i)=ichar(string(i:i)) + enddo + i=nhash(ic,len,146) + ihash=iand(i,MASK15) + +! print*,'C',ihash,len,string + return +end subroutine hash diff --git a/lib/hopping.f90 b/lib/hopping.f90 new file mode 100644 index 000000000..cdca2fc02 --- /dev/null +++ b/lib/hopping.f90 @@ -0,0 +1,81 @@ +subroutine hopping(nyear,month,nday,uth,mygrid,nduration,npctx,isun, & + iband,ntxnext) + +! Determine Rx or Tx in coordinated hopping mode. + + character*6 mygrid + integer tx(10,6) !T/R array for 2 hours: 10 bands, 6 time slots + real r(6) !Random numbers + integer ii(1) + data n2hr0/-999/ + save n2hr0,tx + + call grayline(nyear,month,nday,uth,mygrid,nduration,isun) + + ns0=uth*3600.0 + pctx=npctx + nrx=0 + ntxnext=0 + nsec=(ns0+10)/120 !Round up to start of next 2-min slot + nsec=nsec*120 + n2hr=nsec/7200 !2-hour slot number + + if(n2hr.ne.n2hr0) then +! Compute a new Rx/Tx pattern for this 2-hour interval + n2hr0=n2hr !Mark this one as done + tx=0 !Clear the tx array + do j=1,10 !Loop over all 10 bands + call random_number(r) + do i=1,6,2 !Select one each of 3 pairs of the + if(r(i).gt.r(i+1)) then ! 6 slots for Tx + tx(j,i)=1 + r(i+1)=0. + else + tx(j,i+1)=1 + r(i)=0. + endif + enddo + + if(pctx.lt.50.0) then !If pctx < 50, we may kill one Tx slot + ii=maxloc(r) + i=ii(1) + call random_number(rr) + rrtest=(50.0-pctx)/16.667 + if(rr.lt.rrtest) then + tx(j,i)=0 + r(i)=0. + endif + endif + + if(pctx.lt.33.333) then !If pctx < 33, may kill another + ii=maxloc(r) + i=ii(1) + call random_number(rr) + rrtest=(33.333-pctx)/16.667 + if(rr.lt.rrtest) then + tx(j,i)=0 + r(i)=0. + endif + endif + enddo + +! We now have 1 to 3 Tx periods per band in the 2-hour interval. + endif + + iband=mod(nsec/120,10) + 1 + iseq=mod(nsec/1200,6) + 1 + if(iseq.lt.1) iseq=1 + if(tx(iband,iseq).eq.1) then + ntxnext=1 + else + nrx=1 + endif + iband=iband-1 + +! write(*,3000) iband,iseq,nrx,ntxnext +!3000 format('Fortran iband, iseq,nrx,ntxnext:',4i5) +! write(*,3001) int(tx) +!3001 format(10i2) + + return +end subroutine hopping diff --git a/lib/indexx.f90 b/lib/indexx.f90 index 57c1ec075..f4dd07043 100644 --- a/lib/indexx.f90 +++ b/lib/indexx.f90 @@ -1,19 +1,91 @@ -subroutine indexx(n,arr,indx) +subroutine indexx(arr,n,indx) - parameter (NMAX=3000) - integer indx(n) - real arr(n) - real brr(NMAX) - if(n.gt.NMAX) then - print*,'n=',n,' too big in indexx.' - stop - endif - do i=1,n - brr(i)=arr(i) - indx(i)=i + parameter (M=7,NSTACK=50) + integer n,indx(n) + integer arr(n) + integer i,indxt,ir,itemp,j,jstack,k,l,istack(NSTACK) + real a + + do j=1,n + indx(j)=j enddo - call ssort(brr,indx,n,2) - return + jstack=0 + l=1 + ir=n +1 if(ir-l.lt.M) then + do j=l+1,ir + indxt=indx(j) + a=arr(indxt) + do i=j-1,1,-1 + if(arr(indx(i)).le.a) goto 2 + indx(i+1)=indx(i) + enddo + i=0 +2 indx(i+1)=indxt + enddo + if(jstack.eq.0) return + + ir=istack(jstack) + l=istack(jstack-1) + jstack=jstack-2 + + else + k=(l+ir)/2 + itemp=indx(k) + indx(k)=indx(l+1) + indx(l+1)=itemp + + if(arr(indx(l+1)).gt.arr(indx(ir))) then + itemp=indx(l+1) + indx(l+1)=indx(ir) + indx(ir)=itemp + endif + + if(arr(indx(l)).gt.arr(indx(ir))) then + itemp=indx(l) + indx(l)=indx(ir) + indx(ir)=itemp + endif + + if(arr(indx(l+1)).gt.arr(indx(l))) then + itemp=indx(l+1) + indx(l+1)=indx(l) + indx(l)=itemp + endif + + i=l+1 + j=ir + indxt=indx(l) + a=arr(indxt) +3 continue + i=i+1 + if(arr(indx(i)).lt.a) goto 3 + +4 continue + j=j-1 + if(arr(indx(j)).gt.a) goto 4 + if(j.lt.i) goto 5 + itemp=indx(i) + indx(i)=indx(j) + indx(j)=itemp + goto 3 + +5 indx(l)=indx(j) + indx(j)=indxt + jstack=jstack+2 + if(jstack.gt.NSTACK) stop 'NSTACK too small in indexx' + if(ir-i+1.ge.j-l)then + istack(jstack)=ir + istack(jstack-1)=i + ir=j-1 + else + istack(jstack)=j-1 + istack(jstack-1)=l + l=i + endif + endif + goto 1 + end subroutine indexx diff --git a/lib/inter_wspr.f90 b/lib/inter_wspr.f90 new file mode 100644 index 000000000..9f980454c --- /dev/null +++ b/lib/inter_wspr.f90 @@ -0,0 +1,45 @@ +subroutine inter_wspr(id,ndir) + +! Interleave (ndir=1) or de-interleave (ndir=-1) the array id. + + integer*1 id(0:161),itmp(0:161) + integer j0(0:161) + logical first + data first/.true./ + save + + if(first) then +! Compute the interleave table using bit reversal. + k=-1 + do i=0,255 + n=0 + ii=i + do j=0,7 + n=n+n + if(iand(ii,1).ne.0) n=n+1 + ii=ii/2 + enddo + if(n.le.161) then + k=k+1 + j0(k)=n + endif + enddo + first=.false. + endif + + if(ndir.eq.1) then + do i=0,161 + itmp(j0(i))=id(i) + enddo + else + do i=0,161 + itmp(i)=id(j0(i)) + enddo + endif + + do i=0,161 + id(i)=itmp(i) + enddo + + return +end subroutine inter_wspr diff --git a/lib/jt4a.f90 b/lib/jt4a.f90 index 81dec920a..9fd283f29 100644 --- a/lib/jt4a.f90 +++ b/lib/jt4a.f90 @@ -1,6 +1,5 @@ -subroutine jt4a(dd,jz,nutc,nfqso,newdat,nfa,nfb,ntol0,emedelay,dttol, & - nagain,ndepth,nclearave,minw,nsubmode,mycall,mygrid,hiscall,hisgrid, & - nlist0,listutc0) +subroutine jt4a(dd,jz,nutc,nfqso,ntol0,emedelay,dttol,nagain,ndepth, & + nclearave,minsync,minw,nsubmode,mycall,hiscall,hisgrid,nlist0,listutc0) use jt4 integer listutc0(10) @@ -8,7 +7,7 @@ subroutine jt4a(dd,jz,nutc,nfqso,newdat,nfa,nfb,ntol0,emedelay,dttol, & real*4 dat(30*12000) character*6 cfile6 character*12 mycall,hiscall - character*6 mygrid,hisgrid + character*6 hisgrid mode4=nch(nsubmode+1) ntol=ntol0 @@ -35,7 +34,7 @@ subroutine jt4a(dd,jz,nutc,nfqso,newdat,nfa,nfb,ntol0,emedelay,dttol, & cfile6(5:6)=' ' call timer('wsjt4 ',0) - call wsjt4(dat,jz2,nutc,NClearAve,ntol,emedelay,dttol,mode4,minw, & + call wsjt4(dat,jz2,nutc,NClearAve,minsync,ntol,emedelay,dttol,mode4,minw, & mycall,hiscall,hisgrid,nfqso,NAgain,ndepth,neme) call timer('wsjt4 ',1) diff --git a/lib/jt9.f90 b/lib/jt9.f90 index 5cfa742af..6a791d082 100644 --- a/lib/jt9.f90 +++ b/lib/jt9.f90 @@ -52,8 +52,9 @@ program jt9 character datetime*20,mycall*12,mygrid*6,hiscall*12,hisgrid*6 common/jt9com/ss(184,NSMAX),savg(NSMAX),id2(NMAX),nutc,ndiskdat, & ntr,mousefqso,newdat,npts8a,nfa,nfsplit,nfb,ntol,kin,nzhsym, & - nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave,emedelay, & - dttol,nlist,listutc(10),datetime,mycall,mygrid,hiscall,hisgrid + nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave,minsync, & + emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid, & + hiscall,hisgrid common/tracer/limtrace,lu common/patience/npatience,nthreads @@ -219,7 +220,8 @@ program jt9 ! Compute rough symbol spectra for the JT9 decoder ingain=0 call timer('symspec ',0) - call symspec(k,ntrperiod,nsps,ingain,pxdb,s,df3, & + nminw=1 + call symspec(k,ntrperiod,nsps,ingain,nminw,pxdb,s,df3, & ihsym,npts8) call timer('symspec ',1) endif @@ -227,8 +229,7 @@ program jt9 if(nhsym.ge.181) exit endif enddo - -10 close(10) + close(10) call fillcom(nutc0,ndepth,nrxfreq,mode,tx9,flow,fsplit,fhigh) call decoder(ss,id2,nfsample) enddo diff --git a/lib/jt9c.f90 b/lib/jt9c.f90 index a64629478..305e784da 100644 --- a/lib/jt9c.f90 +++ b/lib/jt9c.f90 @@ -4,11 +4,12 @@ subroutine jt9c(ss,savg,id2,nparams0) real*4 ss(184*NSMAX),savg(NSMAX) integer*2 id2(NTMAX*12000) - integer nparams0(46),nparams(46) + integer nparams0(47),nparams(47) character datetime*20,mycall*12,mygrid*6,hiscall*12,hisgrid*6 common/npar/nutc,ndiskdat,ntrperiod,nfqso,newdat,npts8,nfa,nfsplit,nfb, & ntol,kin,nzhsym,nsave,nagain,ndepth,ntxmode,nmode,minw,nclearave, & - emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid,hiscall,hisgrid + minsync,emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid, & + hiscall,hisgrid common/patience/npatience,nthreads equivalence (nparams,nutc) diff --git a/lib/mixlpf.f90 b/lib/mixlpf.f90 new file mode 100644 index 000000000..cd775ab87 --- /dev/null +++ b/lib/mixlpf.f90 @@ -0,0 +1,25 @@ +subroutine mixlpf(x1,nbfo,c0) + + real*4 x1(512) + real*8 twopi,phi,dphi + complex c1(512),c2(105+512) + complex c0(64) + data phi/0.d0/ + save phi,c2 + + twopi=8.d0*atan(1.d0) + dphi=twopi*nbfo/12000.d0 + + do i=1,512 + phi=phi+dphi + if(phi.gt.twopi) phi=phi-twopi + xphi=phi + c1(i)=x1(i)*cmplx(cos(xphi),sin(xphi)) + enddo + c2(106:105+512)=c1 + + call fil3c(c2,105+512,c0,n2) + c2(1:105)=c1(512-104:512) !Save 105 trailing samples + + return +end subroutine mixlpf diff --git a/lib/packjt.f90 b/lib/packjt.f90 index 4aed6fe31..11090fe2c 100644 --- a/lib/packjt.f90 +++ b/lib/packjt.f90 @@ -4,898 +4,984 @@ module packjt subroutine packbits(dbits,nsymd,m0,sym) -! Pack 0s and 1s from dbits() into sym() with m0 bits per word. -! NB: nsymd is the number of packed output words. + ! Pack 0s and 1s from dbits() into sym() with m0 bits per word. + ! NB: nsymd is the number of packed output words. - integer sym(nsymd) - integer*1 dbits(*) + integer sym(nsymd) + integer*1 dbits(*) - k=0 - do i=1,nsymd - n=0 - do j=1,m0 - k=k+1 - m=dbits(k) - n=ior(ishft(n,1),m) - enddo - sym(i)=n - enddo + k=0 + do i=1,nsymd + n=0 + do j=1,m0 + k=k+1 + m=dbits(k) + n=ior(ishft(n,1),m) + enddo + sym(i)=n + enddo - return -end subroutine packbits + return + end subroutine packbits -subroutine unpackbits(sym,nsymd,m0,dbits) + subroutine unpackbits(sym,nsymd,m0,dbits) -! Unpack bits from sym() into dbits(), one bit per byte. -! NB: nsymd is the number of input words, and m0 their length. -! there will be m0*nsymd output bytes, each 0 or 1. + ! Unpack bits from sym() into dbits(), one bit per byte. + ! NB: nsymd is the number of input words, and m0 their length. + ! there will be m0*nsymd output bytes, each 0 or 1. - integer sym(nsymd) - integer*1 dbits(*) + integer sym(nsymd) + integer*1 dbits(*) - k=0 - do i=1,nsymd - mask=ishft(1,m0-1) - do j=1,m0 - k=k+1 - dbits(k)=0 - if(iand(mask,sym(i)).ne.0) dbits(k)=1 - mask=ishft(mask,-1) - enddo - enddo + k=0 + do i=1,nsymd + mask=ishft(1,m0-1) + do j=1,m0 + k=k+1 + dbits(k)=0 + if(iand(mask,sym(i)).ne.0) dbits(k)=1 + mask=ishft(mask,-1) + enddo + enddo - return -end subroutine unpackbits + return + end subroutine unpackbits -subroutine packcall(callsign,ncall,text) + subroutine packcall(callsign,ncall,text) -! Pack a valid callsign into a 28-bit integer. + ! Pack a valid callsign into a 28-bit integer. - parameter (NBASE=37*36*10*27*27*27) - character callsign*6,c*1,tmp*6 + parameter (NBASE=37*36*10*27*27*27) + character callsign*6,c*1,tmp*6 + logical text + + text=.false. + + ! Work-around for Swaziland prefix: + if(callsign(1:4).eq.'3DA0') callsign='3D0'//callsign(5:6) + + if(callsign(1:3).eq.'CQ ') then + ncall=NBASE + 1 + if(callsign(4:4).ge.'0' .and. callsign(4:4).le.'9' .and. & + callsign(5:5).ge.'0' .and. callsign(5:5).le.'9' .and. & + callsign(6:6).ge.'0' .and. callsign(6:6).le.'9') then + read(callsign(4:6),*) nfreq + ncall=NBASE + 3 + nfreq + endif + return + else if(callsign(1:4).eq.'QRZ ') then + ncall=NBASE + 2 + return + else if(callsign(1:3).eq.'DE ') then + ncall=267796945 + return + endif + + tmp=' ' + if(callsign(3:3).ge.'0' .and. callsign(3:3).le.'9') then + tmp=callsign + else if(callsign(2:2).ge.'0' .and. callsign(2:2).le.'9') then + if(callsign(6:6).ne.' ') then + text=.true. + return + endif + tmp=' '//callsign(:5) + else + text=.true. + return + endif + + do i=1,6 + c=tmp(i:i) + if(c.ge.'a' .and. c.le.'z') & + tmp(i:i)=char(ichar(c)-ichar('a')+ichar('A')) + enddo + + n1=0 + if((tmp(1:1).ge.'A'.and.tmp(1:1).le.'Z').or.tmp(1:1).eq.' ') n1=1 + if(tmp(1:1).ge.'0' .and. tmp(1:1).le.'9') n1=1 + n2=0 + if(tmp(2:2).ge.'A' .and. tmp(2:2).le.'Z') n2=1 + if(tmp(2:2).ge.'0' .and. tmp(2:2).le.'9') n2=1 + n3=0 + if(tmp(3:3).ge.'0' .and. tmp(3:3).le.'9') n3=1 + n4=0 + if((tmp(4:4).ge.'A'.and.tmp(4:4).le.'Z').or.tmp(4:4).eq.' ') n4=1 + n5=0 + if((tmp(5:5).ge.'A'.and.tmp(5:5).le.'Z').or.tmp(5:5).eq.' ') n5=1 + n6=0 + if((tmp(6:6).ge.'A'.and.tmp(6:6).le.'Z').or.tmp(6:6).eq.' ') n6=1 + + if(n1+n2+n3+n4+n5+n6 .ne. 6) then + text=.true. + return + endif + + ncall=nchar(tmp(1:1)) + ncall=36*ncall+nchar(tmp(2:2)) + ncall=10*ncall+nchar(tmp(3:3)) + ncall=27*ncall+nchar(tmp(4:4))-10 + ncall=27*ncall+nchar(tmp(5:5))-10 + ncall=27*ncall+nchar(tmp(6:6))-10 + + return + end subroutine packcall + + subroutine unpackcall(ncall,word,iv2,psfx) + + parameter (NBASE=37*36*10*27*27*27) + character word*12,c*37,psfx*4 + + data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ '/ + + word='......' + psfx=' ' + n=ncall + iv2=0 + if(n.ge.262177560) go to 20 + word='......' + ! if(n.ge.262177560) go to 999 !Plain text message ... + i=mod(n,27)+11 + word(6:6)=c(i:i) + n=n/27 + i=mod(n,27)+11 + word(5:5)=c(i:i) + n=n/27 + i=mod(n,27)+11 + word(4:4)=c(i:i) + n=n/27 + i=mod(n,10)+1 + word(3:3)=c(i:i) + n=n/10 + i=mod(n,36)+1 + word(2:2)=c(i:i) + n=n/36 + i=n+1 + word(1:1)=c(i:i) + do i=1,4 + if(word(i:i).ne.' ') go to 10 + enddo + go to 999 + 10 word=word(i:) + go to 999 + + 20 if(n.ge.267796946) go to 999 + + ! We have a JT65v2 message + if((n.ge.262178563) .and. (n.le.264002071)) then + ! CQ with prefix + iv2=1 + n=n-262178563 + i=mod(n,37)+1 + psfx(4:4)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if((n.ge.264002072) .and. (n.le.265825580)) then + ! QRZ with prefix + iv2=2 + n=n-264002072 + i=mod(n,37)+1 + psfx(4:4)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if((n.ge.265825581) .and. (n.le.267649089)) then + ! DE with prefix + iv2=3 + n=n-265825581 + i=mod(n,37)+1 + psfx(4:4)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if((n.ge.267649090) .and. (n.le.267698374)) then + ! CQ with suffix + iv2=4 + n=n-267649090 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if((n.ge.267698375) .and. (n.le.267747659)) then + ! QRZ with suffix + iv2=5 + n=n-267698375 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if((n.ge.267747660) .and. (n.le.267796944)) then + ! DE with suffix + iv2=6 + n=n-267747660 + i=mod(n,37)+1 + psfx(3:3)=c(i:i) + n=n/37 + i=mod(n,37)+1 + psfx(2:2)=c(i:i) + n=n/37 + i=n+1 + psfx(1:1)=c(i:i) + + else if(n.eq.267796945) then + ! DE with no prefix or suffix + iv2=7 + psfx = ' ' + endif + + 999 if(word(1:3).eq.'3D0') word='3DA0'//word(4:) + + return + end subroutine unpackcall + + subroutine packgrid(grid,ng,text) + + parameter (NGBASE=180*180) + character*4 grid + character*1 c1 + logical text + + text=.false. + if(grid.eq.' ') go to 90 !Blank grid is OK + + ! First, handle signal reports in the original range, -01 to -30 dB + if(grid(1:1).eq.'-') then + read(grid(2:3),*,err=800,end=800) n + if(n.ge.1 .and. n.le.30) then + ng=NGBASE+1+n + go to 900 + endif + go to 10 + else if(grid(1:2).eq.'R-') then + read(grid(3:4),*,err=800,end=800) n + if(n.ge.1 .and. n.le.30) then + ng=NGBASE+31+n + go to 900 + endif + go to 10 + ! Now check for RO, RRR, or 73 in the message field normally used for grid + else if(grid(1:4).eq.'RO ') then + ng=NGBASE+62 + go to 900 + else if(grid(1:4).eq.'RRR ') then + ng=NGBASE+63 + go to 900 + else if(grid(1:4).eq.'73 ') then + ng=NGBASE+64 + go to 900 + endif + + ! Now check for extended-range signal reports: -50 to -31, and 0 to +49. + 10 n=99 + c1=grid(1:1) + read(grid,*,err=20,end=20) n + go to 30 + 20 read(grid(2:4),*,err=30,end=30) n + 30 if(n.ge.-50 .and. n.le.49) then + if(c1.eq.'R') then + write(grid,1002) n+50 + 1002 format('LA',i2.2) + else + write(grid,1003) n+50 + 1003 format('KA',i2.2) + endif + go to 40 + endif + + ! Maybe it's free text ? + if(grid(1:1).lt.'A' .or. grid(1:1).gt.'R') text=.true. + if(grid(2:2).lt.'A' .or. grid(2:2).gt.'R') text=.true. + if(grid(3:3).lt.'0' .or. grid(3:3).gt.'9') text=.true. + if(grid(4:4).lt.'0' .or. grid(4:4).gt.'9') text=.true. + if(text) go to 900 + + ! OK, we have a properly formatted grid locator + 40 call grid2deg(grid//'mm',dlong,dlat) + long=int(dlong) + lat=int(dlat+ 90.0) + ng=((long+180)/2)*180 + lat + go to 900 + + 90 ng=NGBASE + 1 + go to 900 + + 800 text=.true. + 900 continue + + return + end subroutine packgrid + + subroutine unpackgrid(ng,grid) + + parameter (NGBASE=180*180) + character grid*4,grid6*6 + + grid=' ' + if(ng.ge.32400) go to 10 + dlat=mod(ng,180)-90 + dlong=(ng/180)*2 - 180 + 2 + call deg2grid(dlong,dlat,grid6) + grid=grid6(:4) + if(grid(1:2).eq.'KA') then + read(grid(3:4),*) n + n=n-50 + write(grid,1001) n + 1001 format(i3.2) + if(grid(1:1).eq.' ') grid(1:1)='+' + else if(grid(1:2).eq.'LA') then + read(grid(3:4),*) n + n=n-50 + write(grid,1002) n + 1002 format('R',i3.2) + if(grid(2:2).eq.' ') grid(2:2)='+' + endif + go to 900 + + 10 n=ng-NGBASE-1 + if(n.ge.1 .and.n.le.30) then + write(grid,1012) -n + 1012 format(i3.2) + else if(n.ge.31 .and.n.le.60) then + n=n-30 + write(grid,1022) -n + 1022 format('R',i3.2) + else if(n.eq.61) then + grid='RO' + else if(n.eq.62) then + grid='RRR' + else if(n.eq.63) then + grid='73' + endif + + 900 return + end subroutine unpackgrid + + subroutine packmsg(msg,dat,itype) + + ! Packs a JT4/JT9/JT65 message into twelve 6-bit symbols + + ! itype Message Type + !-------------------- + ! 1 Standardd message + ! 2 Type 1 prefix + ! 3 Type 1 suffix + ! 4 Type 2 prefix + ! 5 Type 2 suffix + ! 6 Free text + ! -1 Does not decode correctly + + parameter (NBASE=37*36*10*27*27*27) + parameter (NBASE2=262178562) + character*22 msg + integer dat(12) + character*12 c1,c2 + character*4 c3 + character*6 grid6 + logical text1,text2,text3 + + itype=1 + call fmtmsg(msg,iz) + + if(msg(1:6).eq.'CQ DX ') msg(3:3)='9' + + ! See if it's a CQ message + if(msg(1:3).eq.'CQ ') then + i=3 + ! ... and if so, does it have a reply frequency? + if(msg(4:4).ge.'0' .and. msg(4:4).le.'9' .and. & + msg(5:5).ge.'0' .and. msg(5:5).le.'9' .and. & + msg(6:6).ge.'0' .and. msg(6:6).le.'9') i=7 + go to 1 + endif + + do i=1,22 + if(msg(i:i).eq.' ') go to 1 !Get 1st blank + enddo + go to 10 !Consider msg as plain text + + 1 ia=i + c1=msg(1:ia-1) + do i=ia+1,22 + if(msg(i:i).eq.' ') go to 2 !Get 2nd blank + enddo + go to 10 !Consider msg as plain text + + 2 ib=i + c2=msg(ia+1:ib-1) + + do i=ib+1,22 + if(msg(i:i).eq.' ') go to 3 !Get 3rd blank + enddo + go to 10 !Consider msg as plain text + + 3 ic=i + c3=' ' + if(ic.ge.ib+1) c3=msg(ib+1:ic) + if(c3.eq.'OOO ') c3=' ' !Strip out the OOO flag + call getpfx1(c1,k1,nv2a) + if(nv2a.ge.4) go to 10 + call packcall(c1,nc1,text1) + if(text1) go to 10 + call getpfx1(c2,k2,nv2b) + call packcall(c2,nc2,text2) + if(text2) go to 10 + if(nv2a.eq.2 .or. nv2a.eq.3 .or. nv2b.eq.2 .or. nv2b.eq.3) then + if(k1.lt.0 .or. k2.lt.0 .or. k1*k2.ne.0) go to 10 + if(k2.gt.0) k2=k2+450 + k=max(k1,k2) + if(k.gt.0) then + call k2grid(k,grid6) + c3=grid6(:4) + endif + endif + call packgrid(c3,ng,text3) + + if(nv2a.lt.4 .and. nv2b.lt.4 .and. (.not.text1) .and. (.not.text2) .and. & + (.not.text3)) go to 20 + + nc1=0 + if(nv2b.eq.4) then + if(c1(1:3).eq.'CQ ') nc1=262178563 + k2 + if(c1(1:4).eq.'QRZ ') nc1=264002072 + k2 + if(c1(1:3).eq.'DE ') nc1=265825581 + k2 + else if(nv2b.eq.5) then + if(c1(1:3).eq.'CQ ') nc1=267649090 + k2 + if(c1(1:4).eq.'QRZ ') nc1=267698375 + k2 + if(c1(1:3).eq.'DE ') nc1=267747660 + k2 + endif + if(nc1.ne.0) go to 20 + + ! The message will be treated as plain text. + 10 itype=6 + call packtext(msg,nc1,nc2,ng) + ng=ng+32768 + + ! Encode data into 6-bit words + 20 continue + if(itype.ne.6) itype=max(nv2a,nv2b) + dat(1)=iand(ishft(nc1,-22),63) !6 bits + dat(2)=iand(ishft(nc1,-16),63) !6 bits + dat(3)=iand(ishft(nc1,-10),63) !6 bits + dat(4)=iand(ishft(nc1, -4),63) !6 bits + dat(5)=4*iand(nc1,15)+iand(ishft(nc2,-26),3) !4+2 bits + dat(6)=iand(ishft(nc2,-20),63) !6 bits + dat(7)=iand(ishft(nc2,-14),63) !6 bits + dat(8)=iand(ishft(nc2, -8),63) !6 bits + dat(9)=iand(ishft(nc2, -2),63) !6 bits + dat(10)=16*iand(nc2,3)+iand(ishft(ng,-12),15) !2+4 bits + dat(11)=iand(ishft(ng,-6),63) + dat(12)=iand(ng,63) + + return + end subroutine packmsg + + subroutine unpackmsg(dat,msg) + + parameter (NBASE=37*36*10*27*27*27) + parameter (NGBASE=180*180) + integer dat(12) + character c1*12,c2*12,grid*4,msg*22,grid6*6,psfx*4,junk2*4 + logical cqnnn + + cqnnn=.false. + nc1=ishft(dat(1),22) + ishft(dat(2),16) + ishft(dat(3),10)+ & + ishft(dat(4),4) + iand(ishft(dat(5),-2),15) + + nc2=ishft(iand(dat(5),3),26) + ishft(dat(6),20) + & + ishft(dat(7),14) + ishft(dat(8),8) + ishft(dat(9),2) + & + iand(ishft(dat(10),-4),3) + + ng=ishft(iand(dat(10),15),12) + ishft(dat(11),6) + dat(12) + + if(ng.ge.32768) then + call unpacktext(nc1,nc2,ng,msg) + go to 100 + endif + + call unpackcall(nc1,c1,iv2,psfx) + if(iv2.eq.0) then + ! This is an "original JT65" message + if(nc1.eq.NBASE+1) c1='CQ ' + if(nc1.eq.NBASE+2) c1='QRZ ' + nfreq=nc1-NBASE-3 + if(nfreq.ge.0 .and. nfreq.le.999) then + write(c1,1002) nfreq + 1002 format('CQ ',i3.3) + cqnnn=.true. + endif + endif + + call unpackcall(nc2,c2,junk1,junk2) + call unpackgrid(ng,grid) + + if(iv2.gt.0) then + ! This is a JT65v2 message + do i=1,4 + if(ichar(psfx(i:i)).eq.0) psfx(i:i)=' ' + enddo + + n1=len_trim(psfx) + n2=len_trim(c2) + if(iv2.eq.1) msg='CQ '//psfx(:n1)//'/'//c2(:n2)//' '//grid + if(iv2.eq.2) msg='QRZ '//psfx(:n1)//'/'//c2(:n2)//' '//grid + if(iv2.eq.3) msg='DE '//psfx(:n1)//'/'//c2(:n2)//' '//grid + if(iv2.eq.4) msg='CQ '//c2(:n2)//'/'//psfx(:n1)//' '//grid + if(iv2.eq.5) msg='QRZ '//c2(:n2)//'/'//psfx(:n1)//' '//grid + if(iv2.eq.6) msg='DE '//c2(:n2)//'/'//psfx(:n1)//' '//grid + if(iv2.eq.7) msg='DE '//c2(:n2)//' '//grid + if(iv2.eq.8) msg=' ' + go to 100 + else + + endif + + grid6=grid//'ma' + call grid2k(grid6,k) + if(k.ge.1 .and. k.le.450) call getpfx2(k,c1) + if(k.ge.451 .and. k.le.900) call getpfx2(k,c2) + + i=index(c1,char(0)) + if(i.ge.3) c1=c1(1:i-1)//' ' + i=index(c2,char(0)) + if(i.ge.3) c2=c2(1:i-1)//' ' + + msg=' ' + j=0 + if(cqnnn) then + msg=c1//' ' + j=7 !### ??? ### + go to 10 + endif + + do i=1,12 + j=j+1 + msg(j:j)=c1(i:i) + if(c1(i:i).eq.' ') go to 10 + enddo + j=j+1 + msg(j:j)=' ' + + 10 do i=1,12 + if(j.le.21) j=j+1 + msg(j:j)=c2(i:i) + if(c2(i:i).eq.' ') go to 20 + enddo + if(j.le.21) j=j+1 + msg(j:j)=' ' + + 20 if(k.eq.0) then + do i=1,4 + if(j.le.21) j=j+1 + msg(j:j)=grid(i:i) + enddo + if(j.le.21) j=j+1 + msg(j:j)=' ' + endif + + 100 continue + if(msg(1:6).eq.'CQ9DX ') msg(3:3)=' ' + + return + end subroutine unpackmsg + + subroutine packtext(msg,nc1,nc2,nc3) + + parameter (MASK28=2**28 - 1) + character*13 msg + character*42 c + data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?'/ + + nc1=0 + nc2=0 + nc3=0 + + do i=1,5 !First 5 characters in nc1 + do j=1,42 !Get character code + if(msg(i:i).eq.c(j:j)) go to 10 + enddo + j=37 + 10 j=j-1 !Codes should start at zero + nc1=42*nc1 + j + enddo + + do i=6,10 !Characters 6-10 in nc2 + do j=1,42 !Get character code + if(msg(i:i).eq.c(j:j)) go to 20 + enddo + j=37 + 20 j=j-1 !Codes should start at zero + nc2=42*nc2 + j + enddo + + do i=11,13 !Characters 11-13 in nc3 + do j=1,42 !Get character code + if(msg(i:i).eq.c(j:j)) go to 30 + enddo + j=37 + 30 j=j-1 !Codes should start at zero + nc3=42*nc3 + j + enddo + + ! We now have used 17 bits in nc3. Must move one each to nc1 and nc2. + nc1=nc1+nc1 + if(iand(nc3,32768).ne.0) nc1=nc1+1 + nc2=nc2+nc2 + if(iand(nc3,65536).ne.0) nc2=nc2+1 + nc3=iand(nc3,32767) + + return + end subroutine packtext + + subroutine unpacktext(nc1,nc2,nc3,msg) + + character*22 msg + character*44 c + data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?'/ + + nc3=iand(nc3,32767) !Remove the "plain text" bit + if(iand(nc1,1).ne.0) nc3=nc3+32768 + nc1=nc1/2 + if(iand(nc2,1).ne.0) nc3=nc3+65536 + nc2=nc2/2 + + do i=5,1,-1 + j=mod(nc1,42)+1 + msg(i:i)=c(j:j) + nc1=nc1/42 + enddo + + do i=10,6,-1 + j=mod(nc2,42)+1 + msg(i:i)=c(j:j) + nc2=nc2/42 + enddo + + do i=13,11,-1 + j=mod(nc3,42)+1 + msg(i:i)=c(j:j) + nc3=nc3/42 + enddo + msg(14:22) = ' ' + + return + end subroutine unpacktext + + subroutine getpfx1(callsign,k,nv2) + + character*12 callsign0,callsign,lof,rof + character*8 c + character addpfx*8,tpfx*4,tsfx*3 + logical ispfx,issfx,invalid + common/pfxcom/addpfx + include 'pfx.f90' + + callsign0=callsign + nv2=1 + iz=index(callsign,' ') - 1 + if(iz.lt.0) iz=12 + islash=index(callsign(1:iz),'/') + k=0 + ! if(k.eq.0) go to 10 !Tnx to DL9RDZ for reminder:this was for tests only! + c=' ' + if(islash.gt.0 .and. islash.le.(iz-4)) then + ! Add-on prefix + c=callsign(1:islash-1) + callsign=callsign(islash+1:iz) + do i=1,NZ + if(pfx(i)(1:4).eq.c) then + k=i + nv2=2 + go to 10 + endif + enddo + if(addpfx.eq.c) then + k=449 + nv2=2 + go to 10 + endif + + else if(islash.eq.(iz-1)) then + ! Add-on suffix + c=callsign(islash+1:iz) + callsign=callsign(1:islash-1) + do i=1,NZ2 + if(sfx(i).eq.c(1:1)) then + k=400+i + nv2=3 + go to 10 + endif + enddo + endif + + 10 if(islash.ne.0 .and.k.eq.0) then + ! Original JT65 would force this compound callsign to be treated as + ! plain text. In JT65v2, we will encode the prefix or suffix into nc1. + ! The task here is to compute the proper value of k. + lof=callsign0(:islash-1) + rof=callsign0(islash+1:) + llof=len_trim(lof) + lrof=len_trim(rof) + ispfx=(llof.gt.0 .and. llof.le.4) + issfx=(lrof.gt.0 .and. lrof.le.3) + invalid=.not.(ispfx.or.issfx) + if(ispfx.and.issfx) then + if(llof.lt.3) issfx=.false. + if(lrof.lt.3) ispfx=.false. + if(ispfx.and.issfx) then + i=ichar(callsign0(islash-1:islash-1)) + if(i.ge.ichar('0') .and. i.le.ichar('9')) then + issfx=.false. + else + ispfx=.false. + endif + endif + endif + + if(invalid) then + k=-1 + else + if(ispfx) then + tpfx=lof(1:4) + k=nchar(tpfx(1:1)) + k=37*k + nchar(tpfx(2:2)) + k=37*k + nchar(tpfx(3:3)) + k=37*k + nchar(tpfx(4:4)) + nv2=4 + i=index(callsign0,'/') + callsign=callsign0(:i-1) + callsign=callsign0(i+1:) + endif + if(issfx) then + tsfx=rof(1:3) + k=nchar(tsfx(1:1)) + k=37*k + nchar(tsfx(2:2)) + k=37*k + nchar(tsfx(3:3)) + nv2=5 + i=index(callsign0,'/') + callsign=callsign0(:i-1) + endif + endif + endif + + return + end subroutine getpfx1 + + subroutine getpfx2(k0,callsign) + + character callsign*12 + include 'pfx.f90' + character addpfx*8 + common/pfxcom/addpfx + + k=k0 + if(k.gt.450) k=k-450 + if(k.ge.1 .and. k.le.NZ) then + iz=index(pfx(k),' ') - 1 + callsign=pfx(k)(1:iz)//'/'//callsign + else if(k.ge.401 .and. k.le.400+NZ2) then + iz=index(callsign,' ') - 1 + callsign=callsign(1:iz)//'/'//sfx(k-400) + else if(k.eq.449) then + iz=index(addpfx,' ') - 1 + if(iz.lt.1) iz=8 + callsign=addpfx(1:iz)//'/'//callsign + endif + + return + end subroutine getpfx2 + + subroutine grid2k(grid,k) + + character*6 grid + + call grid2deg(grid,xlong,xlat) + nlong=nint(xlong) + nlat=nint(xlat) + k=0 + if(nlat.ge.85) k=5*(nlong+179)/2 + nlat-84 + + return + end subroutine grid2k + + subroutine k2grid(k,grid) + character grid*6 + + nlong=2*mod((k-1)/5,90)-179 + if(k.gt.450) nlong=nlong+180 + nlat=mod(k-1,5)+ 85 + dlat=nlat + dlong=nlong + call deg2grid(dlong,dlat,grid) + + return + end subroutine k2grid + + subroutine grid2n(grid,n) + character*4 grid + + i1=ichar(grid(1:1))-ichar('A') + i2=ichar(grid(3:3))-ichar('0') + i=10*i1 + i2 + n=-i - 31 + + return + end subroutine grid2n + + subroutine n2grid(n,grid) + character*4 grid + + if(n.gt.-31 .or. n.lt.-70) stop 'Error in n2grid' + i=-(n+31) !NB: 0 <= i <= 39 + i1=i/10 + i2=mod(i,10) + grid(1:1)=char(ichar('A')+i1) + grid(2:2)='A' + grid(3:3)=char(ichar('0')+i2) + grid(4:4)='0' + + return + end subroutine n2grid + + function nchar(c) + + ! Convert ascii number, letter, or space to 0-36 for callsign packing. + + character c*1 + + n=0 !Silence compiler warning + if(c.ge.'0' .and. c.le.'9') then + n=ichar(c)-ichar('0') + else if(c.ge.'A' .and. c.le.'Z') then + n=ichar(c)-ichar('A') + 10 + else if(c.ge.'a' .and. c.le.'z') then + n=ichar(c)-ichar('a') + 10 + else if(c.ge.' ') then + n=36 + else + Print*,'Invalid character in callsign ',c,' ',ichar(c) + stop + endif + nchar=n + + return + end function nchar + + subroutine pack50(n1,n2,dat) + + integer*1 dat(11),i1 + + i1=iand(ishft(n1,-20),255) !8 bits + dat(1)=i1 + i1=iand(ishft(n1,-12),255) !8 bits + dat(2)=i1 + i1=iand(ishft(n1, -4),255) !8 bits + dat(3)=i1 + i1=16*iand(n1,15)+iand(ishft(n2,-18),15) !4+4 bits + dat(4)=i1 + i1=iand(ishft(n2,-10),255) !8 bits + dat(5)=i1 + i1=iand(ishft(n2, -2),255) !8 bits + dat(6)=i1 + i1=64*iand(n2,3) !2 bits + dat(7)=i1 + dat(8)=0 + dat(9)=0 + dat(10)=0 + dat(11)=0 + + return + end subroutine pack50 + +subroutine packpfx(call1,n1,ng,nadd) + + character*12 call1,call0 + character*3 pfx logical text - text=.false. - -! Work-around for Swaziland prefix: - if(callsign(1:4).eq.'3DA0') callsign='3D0'//callsign(5:6) - - if(callsign(1:3).eq.'CQ ') then - ncall=NBASE + 1 - if(callsign(4:4).ge.'0' .and. callsign(4:4).le.'9' .and. & - callsign(5:5).ge.'0' .and. callsign(5:5).le.'9' .and. & - callsign(6:6).ge.'0' .and. callsign(6:6).le.'9') then - read(callsign(4:6),*) nfreq - ncall=NBASE + 3 + nfreq - endif - return - else if(callsign(1:4).eq.'QRZ ') then - ncall=NBASE + 2 - return - else if(callsign(1:3).eq.'DE ') then - ncall=267796945 - return - endif - - tmp=' ' - if(callsign(3:3).ge.'0' .and. callsign(3:3).le.'9') then - tmp=callsign - else if(callsign(2:2).ge.'0' .and. callsign(2:2).le.'9') then - if(callsign(6:6).ne.' ') then - text=.true. - return - endif - tmp=' '//callsign(:5) - else - text=.true. - return - endif - - do i=1,6 - c=tmp(i:i) - if(c.ge.'a' .and. c.le.'z') & - tmp(i:i)=char(ichar(c)-ichar('a')+ichar('A')) - enddo - - n1=0 - if((tmp(1:1).ge.'A'.and.tmp(1:1).le.'Z').or.tmp(1:1).eq.' ') n1=1 - if(tmp(1:1).ge.'0' .and. tmp(1:1).le.'9') n1=1 - n2=0 - if(tmp(2:2).ge.'A' .and. tmp(2:2).le.'Z') n2=1 - if(tmp(2:2).ge.'0' .and. tmp(2:2).le.'9') n2=1 - n3=0 - if(tmp(3:3).ge.'0' .and. tmp(3:3).le.'9') n3=1 - n4=0 - if((tmp(4:4).ge.'A'.and.tmp(4:4).le.'Z').or.tmp(4:4).eq.' ') n4=1 - n5=0 - if((tmp(5:5).ge.'A'.and.tmp(5:5).le.'Z').or.tmp(5:5).eq.' ') n5=1 - n6=0 - if((tmp(6:6).ge.'A'.and.tmp(6:6).le.'Z').or.tmp(6:6).eq.' ') n6=1 - - if(n1+n2+n3+n4+n5+n6 .ne. 6) then - text=.true. - return - endif - - ncall=nchar(tmp(1:1)) - ncall=36*ncall+nchar(tmp(2:2)) - ncall=10*ncall+nchar(tmp(3:3)) - ncall=27*ncall+nchar(tmp(4:4))-10 - ncall=27*ncall+nchar(tmp(5:5))-10 - ncall=27*ncall+nchar(tmp(6:6))-10 - - return -end subroutine packcall - -subroutine unpackcall(ncall,word,iv2,psfx) - - parameter (NBASE=37*36*10*27*27*27) - character word*12,c*37,psfx*4 - - data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ '/ - - word='......' - psfx=' ' - n=ncall - iv2=0 - if(n.ge.262177560) go to 20 - word='......' -! if(n.ge.262177560) go to 999 !Plain text message ... - i=mod(n,27)+11 - word(6:6)=c(i:i) - n=n/27 - i=mod(n,27)+11 - word(5:5)=c(i:i) - n=n/27 - i=mod(n,27)+11 - word(4:4)=c(i:i) - n=n/27 - i=mod(n,10)+1 - word(3:3)=c(i:i) - n=n/10 - i=mod(n,36)+1 - word(2:2)=c(i:i) - n=n/36 - i=n+1 - word(1:1)=c(i:i) - do i=1,4 - if(word(i:i).ne.' ') go to 10 - enddo - go to 999 -10 word=word(i:) - go to 999 - -20 if(n.ge.267796946) go to 999 - -! We have a JT65v2 message - if((n.ge.262178563) .and. (n.le.264002071)) then -! CQ with prefix - iv2=1 - n=n-262178563 - i=mod(n,37)+1 - psfx(4:4)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if((n.ge.264002072) .and. (n.le.265825580)) then -! QRZ with prefix - iv2=2 - n=n-264002072 - i=mod(n,37)+1 - psfx(4:4)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if((n.ge.265825581) .and. (n.le.267649089)) then -! DE with prefix - iv2=3 - n=n-265825581 - i=mod(n,37)+1 - psfx(4:4)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if((n.ge.267649090) .and. (n.le.267698374)) then -! CQ with suffix - iv2=4 - n=n-267649090 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if((n.ge.267698375) .and. (n.le.267747659)) then -! QRZ with suffix - iv2=5 - n=n-267698375 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if((n.ge.267747660) .and. (n.le.267796944)) then -! DE with suffix - iv2=6 - n=n-267747660 - i=mod(n,37)+1 - psfx(3:3)=c(i:i) - n=n/37 - i=mod(n,37)+1 - psfx(2:2)=c(i:i) - n=n/37 - i=n+1 - psfx(1:1)=c(i:i) - - else if(n.eq.267796945) then -! DE with no prefix or suffix - iv2=7 - psfx = ' ' - endif - -999 if(word(1:3).eq.'3D0') word='3DA0'//word(4:) - - return -end subroutine unpackcall - -subroutine packgrid(grid,ng,text) - - parameter (NGBASE=180*180) - character*4 grid - character*1 c1 - logical text - - text=.false. - if(grid.eq.' ') go to 90 !Blank grid is OK - -! First, handle signal reports in the original range, -01 to -30 dB - if(grid(1:1).eq.'-') then - read(grid(2:3),*,err=800,end=800) n - if(n.ge.1 .and. n.le.30) then - ng=NGBASE+1+n - go to 900 - endif - go to 10 - else if(grid(1:2).eq.'R-') then - read(grid(3:4),*,err=800,end=800) n - if(n.ge.1 .and. n.le.30) then - ng=NGBASE+31+n - go to 900 - endif - go to 10 -! Now check for RO, RRR, or 73 in the message field normally used for grid - else if(grid(1:4).eq.'RO ') then - ng=NGBASE+62 - go to 900 - else if(grid(1:4).eq.'RRR ') then - ng=NGBASE+63 - go to 900 - else if(grid(1:4).eq.'73 ') then - ng=NGBASE+64 - go to 900 - endif - -! Now check for extended-range signal reports: -50 to -31, and 0 to +49. -10 n=99 - c1=grid(1:1) - read(grid,*,err=20,end=20) n - go to 30 -20 read(grid(2:4),*,err=30,end=30) n -30 if(n.ge.-50 .and. n.le.49) then - if(c1.eq.'R') then - write(grid,1002) n+50 -1002 format('LA',i2.2) + i1=index(call1,'/') + if(call1(i1+2:i1+2).eq.' ') then +! Single-character add-on suffix (maybe also fourth suffix letter?) + call0=call1(:i1-1) + call packcall(call0,n1,text) + nadd=1 + nc=ichar(call1(i1+1:i1+1)) + if(nc.ge.48 .and. nc.le.57) then + n=nc-48 + else if(nc.ge.65 .and. nc.le.90) then + n=nc-65+10 else - write(grid,1003) n+50 -1003 format('KA',i2.2) + n=38 endif - go to 40 - endif - -! Maybe it's free text ? - if(grid(1:1).lt.'A' .or. grid(1:1).gt.'R') text=.true. - if(grid(2:2).lt.'A' .or. grid(2:2).gt.'R') text=.true. - if(grid(3:3).lt.'0' .or. grid(3:3).gt.'9') text=.true. - if(grid(4:4).lt.'0' .or. grid(4:4).gt.'9') text=.true. - if(text) go to 900 - -! OK, we have a properly formatted grid locator -40 call grid2deg(grid//'mm',dlong,dlat) - long=int(dlong) - lat=int(dlat+ 90.0) - ng=((long+180)/2)*180 + lat - go to 900 - -90 ng=NGBASE + 1 - go to 900 - -800 text=.true. -900 continue - - return -end subroutine packgrid - -subroutine unpackgrid(ng,grid) - - parameter (NGBASE=180*180) - character grid*4,grid6*6 - - grid=' ' - if(ng.ge.32400) go to 10 - dlat=mod(ng,180)-90 - dlong=(ng/180)*2 - 180 + 2 - call deg2grid(dlong,dlat,grid6) - grid=grid6(:4) - if(grid(1:2).eq.'KA') then - read(grid(3:4),*) n - n=n-50 - write(grid,1001) n -1001 format(i3.2) - if(grid(1:1).eq.' ') grid(1:1)='+' - else if(grid(1:2).eq.'LA') then - read(grid(3:4),*) n - n=n-50 - write(grid,1002) n -1002 format('R',i3.2) - if(grid(2:2).eq.' ') grid(2:2)='+' - endif - go to 900 - -10 n=ng-NGBASE-1 - if(n.ge.1 .and.n.le.30) then - write(grid,1012) -n -1012 format(i3.2) - else if(n.ge.31 .and.n.le.60) then - n=n-30 - write(grid,1022) -n -1022 format('R',i3.2) - else if(n.eq.61) then - grid='RO' - else if(n.eq.62) then - grid='RRR' - else if(n.eq.63) then - grid='73' - endif - -900 return -end subroutine unpackgrid - -subroutine packmsg(msg,dat,itype) - -! Packs a JT4/JT9/JT65 message into twelve 6-bit symbols - -! itype Message Type -!-------------------- -! 1 Standardd message -! 2 Type 1 prefix -! 3 Type 1 suffix -! 4 Type 2 prefix -! 5 Type 2 suffix -! 6 Free text -! -1 Does not decode correctly - - parameter (NBASE=37*36*10*27*27*27) - parameter (NBASE2=262178562) - character*22 msg - integer dat(12) - character*12 c1,c2 - character*4 c3 - character*6 grid6 - logical text1,text2,text3 - - itype=1 - call fmtmsg(msg,iz) - - if(msg(1:6).eq.'CQ DX ') msg(3:3)='9' - -! See if it's a CQ message - if(msg(1:3).eq.'CQ ') then - i=3 -! ... and if so, does it have a reply frequency? - if(msg(4:4).ge.'0' .and. msg(4:4).le.'9' .and. & - msg(5:5).ge.'0' .and. msg(5:5).le.'9' .and. & - msg(6:6).ge.'0' .and. msg(6:6).le.'9') i=7 - go to 1 - endif - - do i=1,22 - if(msg(i:i).eq.' ') go to 1 !Get 1st blank - enddo - go to 10 !Consider msg as plain text - -1 ia=i - c1=msg(1:ia-1) - do i=ia+1,22 - if(msg(i:i).eq.' ') go to 2 !Get 2nd blank - enddo - go to 10 !Consider msg as plain text - -2 ib=i - c2=msg(ia+1:ib-1) - - do i=ib+1,22 - if(msg(i:i).eq.' ') go to 3 !Get 3rd blank - enddo - go to 10 !Consider msg as plain text - -3 ic=i - c3=' ' - if(ic.ge.ib+1) c3=msg(ib+1:ic) - if(c3.eq.'OOO ') c3=' ' !Strip out the OOO flag - call getpfx1(c1,k1,nv2a) - if(nv2a.ge.4) go to 10 - call packcall(c1,nc1,text1) - if(text1) go to 10 - call getpfx1(c2,k2,nv2b) - call packcall(c2,nc2,text2) - if(text2) go to 10 - if(nv2a.eq.2 .or. nv2a.eq.3 .or. nv2b.eq.2 .or. nv2b.eq.3) then - if(k1.lt.0 .or. k2.lt.0 .or. k1*k2.ne.0) go to 10 - if(k2.gt.0) k2=k2+450 - k=max(k1,k2) - if(k.gt.0) then - call k2grid(k,grid6) - c3=grid6(:4) - endif - endif - call packgrid(c3,ng,text3) - - if(nv2a.lt.4 .and. nv2b.lt.4 .and. (.not.text1) .and. (.not.text2) .and. & - (.not.text3)) go to 20 - - nc1=0 - if(nv2b.eq.4) then - if(c1(1:3).eq.'CQ ') nc1=262178563 + k2 - if(c1(1:4).eq.'QRZ ') nc1=264002072 + k2 - if(c1(1:3).eq.'DE ') nc1=265825581 + k2 - else if(nv2b.eq.5) then - if(c1(1:3).eq.'CQ ') nc1=267649090 + k2 - if(c1(1:4).eq.'QRZ ') nc1=267698375 + k2 - if(c1(1:3).eq.'DE ') nc1=267747660 + k2 - endif - if(nc1.ne.0) go to 20 - -! The message will be treated as plain text. -10 itype=6 - call packtext(msg,nc1,nc2,ng) - ng=ng+32768 - -! Encode data into 6-bit words -20 continue - if(itype.ne.6) itype=max(nv2a,nv2b) - dat(1)=iand(ishft(nc1,-22),63) !6 bits - dat(2)=iand(ishft(nc1,-16),63) !6 bits - dat(3)=iand(ishft(nc1,-10),63) !6 bits - dat(4)=iand(ishft(nc1, -4),63) !6 bits - dat(5)=4*iand(nc1,15)+iand(ishft(nc2,-26),3) !4+2 bits - dat(6)=iand(ishft(nc2,-20),63) !6 bits - dat(7)=iand(ishft(nc2,-14),63) !6 bits - dat(8)=iand(ishft(nc2, -8),63) !6 bits - dat(9)=iand(ishft(nc2, -2),63) !6 bits - dat(10)=16*iand(nc2,3)+iand(ishft(ng,-12),15) !2+4 bits - dat(11)=iand(ishft(ng,-6),63) - dat(12)=iand(ng,63) - - return -end subroutine packmsg - -subroutine unpackmsg(dat,msg) - - parameter (NBASE=37*36*10*27*27*27) - parameter (NGBASE=180*180) - integer dat(12) - character c1*12,c2*12,grid*4,msg*22,grid6*6,psfx*4,junk2*4 - logical cqnnn - - cqnnn=.false. - nc1=ishft(dat(1),22) + ishft(dat(2),16) + ishft(dat(3),10)+ & - ishft(dat(4),4) + iand(ishft(dat(5),-2),15) - - nc2=ishft(iand(dat(5),3),26) + ishft(dat(6),20) + & - ishft(dat(7),14) + ishft(dat(8),8) + ishft(dat(9),2) + & - iand(ishft(dat(10),-4),3) - - ng=ishft(iand(dat(10),15),12) + ishft(dat(11),6) + dat(12) - - if(ng.ge.32768) then - call unpacktext(nc1,nc2,ng,msg) - go to 100 - endif - - call unpackcall(nc1,c1,iv2,psfx) - if(iv2.eq.0) then -! This is an "original JT65" message - if(nc1.eq.NBASE+1) c1='CQ ' - if(nc1.eq.NBASE+2) c1='QRZ ' - nfreq=nc1-NBASE-3 - if(nfreq.ge.0 .and. nfreq.le.999) then - write(c1,1002) nfreq -1002 format('CQ ',i3.3) - cqnnn=.true. - endif - endif - - call unpackcall(nc2,c2,junk1,junk2) - call unpackgrid(ng,grid) - - if(iv2.gt.0) then -! This is a JT65v2 message - do i=1,4 - if(ichar(psfx(i:i)).eq.0) psfx(i:i)=' ' - enddo - - n1=len_trim(psfx) - n2=len_trim(c2) - if(iv2.eq.1) msg='CQ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.2) msg='QRZ '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.3) msg='DE '//psfx(:n1)//'/'//c2(:n2)//' '//grid - if(iv2.eq.4) msg='CQ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.5) msg='QRZ '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.6) msg='DE '//c2(:n2)//'/'//psfx(:n1)//' '//grid - if(iv2.eq.7) msg='DE '//c2(:n2)//' '//grid - if(iv2.eq.8) msg=' ' - go to 100 + nadd=1 + ng=60000-32768+n + else if(call1(i1+3:i1+3).eq.' ') then +! Two-character numerical suffix, /10 to /99 + call0=call1(:i1-1) + call packcall(call0,n1,text) + nadd=1 + n=10*(ichar(call1(i1+1:i1+1))-48) + ichar(call1(i1+2:i1+2)) - 48 + nadd=1 + ng=60000 + 26 + n else - - endif +! Prefix of 1 to 3 characters + pfx=call1(:i1-1) + if(pfx(3:3).eq.' ') pfx=' '//pfx(1:2) + if(pfx(3:3).eq.' ') pfx=' '//pfx(1:2) + call0=call1(i1+1:) + call packcall(call0,n1,text) - grid6=grid//'ma' - call grid2k(grid6,k) - if(k.ge.1 .and. k.le.450) call getpfx2(k,c1) - if(k.ge.451 .and. k.le.900) call getpfx2(k,c2) - - i=index(c1,char(0)) - if(i.ge.3) c1=c1(1:i-1)//' ' - i=index(c2,char(0)) - if(i.ge.3) c2=c2(1:i-1)//' ' - - msg=' ' - j=0 - if(cqnnn) then - msg=c1//' ' - j=7 !### ??? ### - go to 10 - endif - - do i=1,12 - j=j+1 - msg(j:j)=c1(i:i) - if(c1(i:i).eq.' ') go to 10 - enddo - j=j+1 - msg(j:j)=' ' - -10 do i=1,12 - if(j.le.21) j=j+1 - msg(j:j)=c2(i:i) - if(c2(i:i).eq.' ') go to 20 - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - -20 if(k.eq.0) then - do i=1,4 - if(j.le.21) j=j+1 - msg(j:j)=grid(i:i) - enddo - if(j.le.21) j=j+1 - msg(j:j)=' ' - endif - -100 continue - if(msg(1:6).eq.'CQ9DX ') msg(3:3)=' ' - - return -end subroutine unpackmsg - -subroutine packtext(msg,nc1,nc2,nc3) - - parameter (MASK28=2**28 - 1) - character*13 msg - character*42 c - data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?'/ - - nc1=0 - nc2=0 - nc3=0 - - do i=1,5 !First 5 characters in nc1 - do j=1,42 !Get character code - if(msg(i:i).eq.c(j:j)) go to 10 - enddo - j=37 -10 j=j-1 !Codes should start at zero - nc1=42*nc1 + j - enddo - - do i=6,10 !Characters 6-10 in nc2 - do j=1,42 !Get character code - if(msg(i:i).eq.c(j:j)) go to 20 - enddo - j=37 -20 j=j-1 !Codes should start at zero - nc2=42*nc2 + j - enddo - - do i=11,13 !Characters 11-13 in nc3 - do j=1,42 !Get character code - if(msg(i:i).eq.c(j:j)) go to 30 - enddo - j=37 -30 j=j-1 !Codes should start at zero - nc3=42*nc3 + j - enddo - -! We now have used 17 bits in nc3. Must move one each to nc1 and nc2. - nc1=nc1+nc1 - if(iand(nc3,32768).ne.0) nc1=nc1+1 - nc2=nc2+nc2 - if(iand(nc3,65536).ne.0) nc2=nc2+1 - nc3=iand(nc3,32767) - - return -end subroutine packtext - -subroutine unpacktext(nc1,nc2,nc3,msg) - - character*22 msg - character*44 c - data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?'/ - - nc3=iand(nc3,32767) !Remove the "plain text" bit - if(iand(nc1,1).ne.0) nc3=nc3+32768 - nc1=nc1/2 - if(iand(nc2,1).ne.0) nc3=nc3+65536 - nc2=nc2/2 - - do i=5,1,-1 - j=mod(nc1,42)+1 - msg(i:i)=c(j:j) - nc1=nc1/42 - enddo - - do i=10,6,-1 - j=mod(nc2,42)+1 - msg(i:i)=c(j:j) - nc2=nc2/42 - enddo - - do i=13,11,-1 - j=mod(nc3,42)+1 - msg(i:i)=c(j:j) - nc3=nc3/42 - enddo - msg(14:22) = ' ' - - return -end subroutine unpacktext - -subroutine getpfx1(callsign,k,nv2) - - character*12 callsign0,callsign,lof,rof - character*8 c - character addpfx*8,tpfx*4,tsfx*3 - logical ispfx,issfx,invalid - common/pfxcom/addpfx - include 'pfx.f90' - - callsign0=callsign - nv2=1 - iz=index(callsign,' ') - 1 - if(iz.lt.0) iz=12 - islash=index(callsign(1:iz),'/') - k=0 -! if(k.eq.0) go to 10 !Tnx to DL9RDZ for reminder:this was for tests only! - c=' ' - if(islash.gt.0 .and. islash.le.(iz-4)) then -! Add-on prefix - c=callsign(1:islash-1) - callsign=callsign(islash+1:iz) - do i=1,NZ - if(pfx(i)(1:4).eq.c) then - k=i - nv2=2 - go to 10 + ng=0 + do i=1,3 + nc=ichar(pfx(i:i)) + if(nc.ge.48 .and. nc.le.57) then + n=nc-48 + else if(nc.ge.65 .and. nc.le.90) then + n=nc-65+10 + else + n=36 endif + ng=37*ng + n enddo - if(addpfx.eq.c) then - k=449 - nv2=2 - go to 10 - endif - - else if(islash.eq.(iz-1)) then -! Add-on suffix - c=callsign(islash+1:iz) - callsign=callsign(1:islash-1) - do i=1,NZ2 - if(sfx(i).eq.c(1:1)) then - k=400+i - nv2=3 - go to 10 - endif - enddo - endif - -10 if(islash.ne.0 .and.k.eq.0) then -! Original JT65 would force this compound callsign to be treated as -! plain text. In JT65v2, we will encode the prefix or suffix into nc1. -! The task here is to compute the proper value of k. - lof=callsign0(:islash-1) - rof=callsign0(islash+1:) - llof=len_trim(lof) - lrof=len_trim(rof) - ispfx=(llof.gt.0 .and. llof.le.4) - issfx=(lrof.gt.0 .and. lrof.le.3) - invalid=.not.(ispfx.or.issfx) - if(ispfx.and.issfx) then - if(llof.lt.3) issfx=.false. - if(lrof.lt.3) ispfx=.false. - if(ispfx.and.issfx) then - i=ichar(callsign0(islash-1:islash-1)) - if(i.ge.ichar('0') .and. i.le.ichar('9')) then - issfx=.false. - else - ispfx=.false. - endif - endif - endif - - if(invalid) then - k=-1 - else - if(ispfx) then - tpfx=lof(1:4) - k=nchar(tpfx(1:1)) - k=37*k + nchar(tpfx(2:2)) - k=37*k + nchar(tpfx(3:3)) - k=37*k + nchar(tpfx(4:4)) - nv2=4 - i=index(callsign0,'/') - callsign=callsign0(:i-1) - callsign=callsign0(i+1:) - endif - if(issfx) then - tsfx=rof(1:3) - k=nchar(tsfx(1:1)) - k=37*k + nchar(tsfx(2:2)) - k=37*k + nchar(tsfx(3:3)) - nv2=5 - i=index(callsign0,'/') - callsign=callsign0(:i-1) - endif + nadd=0 + if(ng.ge.32768) then + ng=ng-32768 + nadd=1 endif endif return -end subroutine getpfx1 - -subroutine getpfx2(k0,callsign) - - character callsign*12 - include 'pfx.f90' - character addpfx*8 - common/pfxcom/addpfx - - k=k0 - if(k.gt.450) k=k-450 - if(k.ge.1 .and. k.le.NZ) then - iz=index(pfx(k),' ') - 1 - callsign=pfx(k)(1:iz)//'/'//callsign - else if(k.ge.401 .and. k.le.400+NZ2) then - iz=index(callsign,' ') - 1 - callsign=callsign(1:iz)//'/'//sfx(k-400) - else if(k.eq.449) then - iz=index(addpfx,' ') - 1 - if(iz.lt.1) iz=8 - callsign=addpfx(1:iz)//'/'//callsign - endif - - return -end subroutine getpfx2 - -subroutine grid2k(grid,k) - - character*6 grid - - call grid2deg(grid,xlong,xlat) - nlong=nint(xlong) - nlat=nint(xlat) - k=0 - if(nlat.ge.85) k=5*(nlong+179)/2 + nlat-84 - - return -end subroutine grid2k - -subroutine k2grid(k,grid) - character grid*6 - - nlong=2*mod((k-1)/5,90)-179 - if(k.gt.450) nlong=nlong+180 - nlat=mod(k-1,5)+ 85 - dlat=nlat - dlong=nlong - call deg2grid(dlong,dlat,grid) - - return -end subroutine k2grid - -subroutine grid2n(grid,n) - character*4 grid - - i1=ichar(grid(1:1))-ichar('A') - i2=ichar(grid(3:3))-ichar('0') - i=10*i1 + i2 - n=-i - 31 - - return -end subroutine grid2n - -subroutine n2grid(n,grid) - character*4 grid - - if(n.gt.-31 .or. n.lt.-70) stop 'Error in n2grid' - i=-(n+31) !NB: 0 <= i <= 39 - i1=i/10 - i2=mod(i,10) - grid(1:1)=char(ichar('A')+i1) - grid(2:2)='A' - grid(3:3)=char(ichar('0')+i2) - grid(4:4)='0' - - return -end subroutine n2grid - -function nchar(c) - -! Convert ascii number, letter, or space to 0-36 for callsign packing. - - character c*1 - - n=0 !Silence compiler warning - if(c.ge.'0' .and. c.le.'9') then - n=ichar(c)-ichar('0') - else if(c.ge.'A' .and. c.le.'Z') then - n=ichar(c)-ichar('A') + 10 - else if(c.ge.'a' .and. c.le.'z') then - n=ichar(c)-ichar('a') + 10 - else if(c.ge.' ') then - n=36 - else - Print*,'Invalid character in callsign ',c,' ',ichar(c) - stop - endif - nchar=n - - return -end function nchar +end subroutine packpfx end module packjt diff --git a/lib/savec2.f90 b/lib/savec2.f90 new file mode 100644 index 000000000..55b4ff476 --- /dev/null +++ b/lib/savec2.f90 @@ -0,0 +1,54 @@ +subroutine savec2(c2name,ntrseconds,f0m1500) + +! Array c0() has complex samples at 1500 Hz sample rate. +! WSPR-2: downsample by 1/4 to produce c2, centered at 1500 Hz +! WSPR-15: downsample by 1/32 to produce c2, centered at 1612.5 Hz + + parameter (NDMAX=120*1500) !Sample intervals at 1500 Hz rate + parameter (MAXFFT=256*1024) + + character*(*) c2name + character*14 outfile + real*8 f0m1500 + complex c0 + complex c1(0:MAXFFT-1) + complex c2(0:65535) + common/c0com/c0(0:NDMAX-1) + + ntrminutes=ntrseconds/60 + npts=114*1500 + nfft1=262144 + if(ntrminutes.eq.15) then + npts=890*1500 + nfft1=MAXFFT + endif + df1=1500.0/nfft1 + fac=1.0/nfft1 + c1(0:npts-1)=fac*c0(0:npts-1) + c1(npts:nfft1-1)=0. + + call four2a(c1,nfft1,1,1,1) !Complex FFT to frequency domain + +! Select the desired frequency range + nfft2=65536 + nh2=nfft2/2 + if(ntrminutes.eq.2) then + c2(0:nh2)=c1(0:nh2) + c2(nh2+1:nfft2-1)=c1(nfft1-nh2+1:nfft1-1) + else + i0=nint(112.5/df1) + c2(0:nh2)=c1(i0:i0+nh2) + c2(nh2+1:nfft2-1)=c1(i0-nh2+1:i0-1) + endif + + call four2a(c2,nfft2,1,-1,1) !Shorter complex FFT, back to time domain + +! Write complex time-domain data to disk. + i1=index(c2name,'.c2') + outfile=c2name(i1-11:i1+2) + open(18,file=c2name,status='unknown',access='stream') + write(18) outfile,ntrminutes,f0m1500,c2(0:45000-1) + close(18) + + return +end subroutine savec2 diff --git a/lib/smo.f90 b/lib/smo.f90 index 94a337848..c42de7c60 100644 --- a/lib/smo.f90 +++ b/lib/smo.f90 @@ -11,13 +11,9 @@ subroutine smo(x,npts,y,nadd) enddo y(i)=sum enddo - y(:nh)=0. - y(npts-nh+1:)=0. - - fac=1.0/nadd - do i=1,npts - x(i)=fac*y(i) - enddo + x=y + x(:nh)=0. + x(npts-nh+1:)=0. return end subroutine smo diff --git a/lib/symspec.f90 b/lib/symspec.f90 index 1b342a202..b00ef6ae1 100644 --- a/lib/symspec.f90 +++ b/lib/symspec.f90 @@ -1,4 +1,4 @@ -subroutine symspec(k,ntrperiod,nsps,ingain,pxdb,s,df3,ihsym,npts8) +subroutine symspec(k,ntrperiod,nsps,ingain,nminw,pxdb,s,df3,ihsym,npts8) ! Input: ! k pointer to the most recent new data @@ -11,7 +11,7 @@ subroutine symspec(k,ntrperiod,nsps,ingain,pxdb,s,df3,ihsym,npts8) ! Output: ! pxdb power (0-60 dB) ! s() current spectrum for waterfall display -! ihsym index number of this half-symbol (1-184) +! ihsym index number of this half-symbol (1-184) for 60 s modes ! jt9com ! ss() JT9 symbol spectra at half-symbol steps @@ -25,15 +25,18 @@ subroutine symspec(k,ntrperiod,nsps,ingain,pxdb,s,df3,ihsym,npts8) real*4 tmp(NSMAX) complex cx(0:MAXFFT3/2) integer*2 id2 + integer nch(7) character datetime*20,mycall*12,mygrid*6,hiscall*12,hisgrid*6 common/jt9com/ss(184,NSMAX),savg(NSMAX),id2(NMAX),nutc,ndiskdat, & ntr,mousefqso,newdat,npts8a,nfa,nfsplit,nfb,ntol,kin,nzhsym, & - nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave,emedelay, & - dttol,nlist,listutc(10),datetime,mycall,mygrid,hiscall,hisgrid + nsubmode,nagain,ndepth,ntxmode,nmode,minw,nclearave,minsync, & + emedelay,dttol,nlist,listutc(10),datetime,mycall,mygrid, & + hiscall,hisgrid common/jt9w/syellow(NSMAX) data rms/999.0/,k0/99999999/,nfft3z/0/ + data nch/1,2,4,9,18,36,72/ equivalence (xc,cx) save @@ -84,34 +87,33 @@ subroutine symspec(k,ntrperiod,nsps,ingain,pxdb,s,df3,ihsym,npts8) xc(i)=0. if(j.ge.1 .and.j.le.NMAX) xc(i)=fac0*id2(j) enddo - if(ihsym.lt.184) ihsym=ihsym+1 + ihsym=ihsym+1 xc(0:nfft3-1)=w3(1:nfft3)*xc(0:nfft3-1) !Apply window w3 call four2a(xc,nfft3,1,-1,0) !Real-to-complex FFT - n=min(184,ihsym) df3=12000.0/nfft3 !JT9-1: 0.732 Hz = 0.42 * tone spacing -! i0=nint(1000.0/df3) - i0=0 iz=min(NSMAX,nint(5000.0/df3)) fac=(1.0/nfft3)**2 do i=1,iz - j=i0+i-1 + j=i-1 if(j.lt.0) j=j+nfft3 sx=fac*(real(cx(j))**2 + aimag(cx(j))**2) - ss(n,i)=sx + if(ihsym.le.184) ss(ihsym,i)=sx ssum(i)=ssum(i) + sx s(i)=1000.0*gain*sx enddo savg=ssum/ihsym - if(mod(n,10).eq.0) then - mode4=36 + if(mod(ihsym,10).eq.0) then + mode4=nch(nminw+1) nsmo=min(10*mode4,150) nsmo=4*nsmo call flat1(savg,iz,nsmo,syellow) - if(mode4.ge.9) call smo(syellow,iz,tmp,mode4) + if(mode4.ge.2) call smo(syellow,iz,tmp,mode4) + if(mode4.ge.2) call smo(syellow,iz,tmp,mode4) + syellow(1:250)=0. ia=500./df3 ib=2700.0/df3 smin=minval(syellow(ia:ib)) diff --git a/lib/sync4.f90 b/lib/sync4.f90 index 38623e072..68de1a345 100644 --- a/lib/sync4.f90 +++ b/lib/sync4.f90 @@ -9,7 +9,6 @@ subroutine sync4(dat,jz,mode4,minw) real dat(jz) real psavg(NHMAX) !Average spectrum of whole record real s2(NHMAX,NSMAX) !2d spectrum, stepped by half-symbols - real ccfblue(65) !CCF with pseudorandom sequence real tmp(1260) save diff --git a/lib/timf2.f90 b/lib/timf2.f90 new file mode 100644 index 000000000..07f509831 --- /dev/null +++ b/lib/timf2.f90 @@ -0,0 +1,142 @@ +subroutine timf2(x0,k,nfft,nwindow,nb,peaklimit,x1, & + slimit,lstrong,px,nzap) + +! Sequential processing of time-domain I/Q data, using Linrad-like +! "first FFT" and "first backward FFT", treating frequencies with +! strong signals differently. Noise blanking is applied to weak +! signals only. + +! x0 - real input data +! nfft - length of FFTs +! nwindow - 0 for no window, 2 for sin^2 window +! x1 - real output data + +! Non-windowed processing means no overlap, so kstep=nfft. +! Sin^2 window has 50% overlap, kstep=nfft/2. + +! Frequencies with strong signals are identified and separated. Back +! transforms are done separately for weak and strong signals, so that +! noise blanking can be applied to the weak-signal portion. Strong and +! weak are finally re-combined, in the time domain. + + parameter (MAXFFT=1024,MAXNH=MAXFFT/2) + parameter (MAXSIGS=100) + real x0(0:nfft-1),x1(0:nfft-1) + real x(0:MAXFFT-1),xw(0:MAXFFT-1),xs(0:MAXFFT-1) + real xwov(0:MAXNH-1),xsov(0:MAXNH-1) + complex cx(0:MAXFFT-1),cxt(0:MAXFFT-1) + complex cxs(0:MAXFFT-1) !Strong signals + complex cxw(0:MAXFFT-1) !Weak signals + real*4 w(0:MAXFFT-1) + real*4 s(0:MAXNH) + logical*1 lstrong(0:MAXNH),lprev + integer ia(MAXSIGS),ib(MAXSIGS) + logical first + equivalence (x,cx),(xw,cxw),(xs,cxs) + data first/.true./ + data k0/99999999/ + save + + if(first) then + pi=4.0*atan(1.0) + do i=0,nfft-1 + w(i)=(sin(i*pi/nfft))**2 + enddo + s=0. + nh=nfft/2 + kstep=nfft + if(nwindow.eq.2) kstep=nh + fac=1.0/nfft + slimit=1.e30 + first=.false. + endif + + if(k.lt.k0) then + xsov=0. + xwov=0. + endif + k0=k + + x(0:nfft-1)=x0 + if(nwindow.eq.2) x(0:nfft-1)=w(0:nfft-1)*x(0:nfft-1) + call four2a(x,nfft,1,-1,0) !First forward FFT, r2c + cxt(0:nh)=cx(0:nh) + +! Identify frequencies with strong signals. + do i=0,nh + p=real(cxt(i))**2 + aimag(cxt(i))**2 + s(i)=p + enddo + ave=sum(s(0:nh))/nh + lstrong(0:nh)=s(0:nh).gt.10.0*ave + + nsigs=0 + lprev=.false. + iwid=1 + ib=-99 + do i=0,nh + if(lstrong(i) .and. (.not.lprev)) then + if(nsigs.lt.MAXSIGS) nsigs=nsigs+1 + ia(nsigs)=i-iwid + if(ia(nsigs).lt.0) ia(nsigs)=0 + endif + if(.not.lstrong(i) .and. lprev) then + ib(nsigs)=i-1+iwid + if(ib(nsigs).gt.nh) ib(nsigs)=nh + endif + lprev=lstrong(i) + enddo + + if(nsigs.gt.0) then + do i=1,nsigs + ja=ia(i) + jb=ib(i) + if(ja.lt.0 .or. ja.gt.nh .or. jb.lt.0 .or. jb.gt.nh) then + cycle + endif + if(jb.eq.-99) jb=ja + min(2*iwid,nh) + lstrong(ja:jb)=.true. + enddo + endif + +! Copy frequency-domain data into array cs (strong) or cw (weak). + do i=0,nh + if(lstrong(i)) then + cxs(i)=fac*cxt(i) + cxw(i)=0. + else + cxw(i)=fac*cxt(i) + cxs(i)=0. + endif + enddo + + call four2a(cxw,nfft,1,1,-1) !Transform weak and strong back + call four2a(cxs,nfft,1,1,-1) !to time domain, separately (c2r) + + if(nwindow.eq.2) then + xw(0:nh-1)=xw(0:nh-1)+xwov(0:nh-1) !Add previous segment's 2nd half + xwov(0:nh-1)=xw(nh:nfft-1) !Save 2nd half + xs(0:nh-1)=xs(0:nh-1)+xsov(0:nh-1) !Ditto for strong signals + xsov(0:nh-1)=xs(nh:nfft-1) + endif + +! Apply noise blanking to weak data + if(nb.ne.0) then + do i=0,kstep-1 + peak=abs(xw(i)) + if(peak.gt.peaklimit) then + xw(i)=0. + nzap=nzap+1 + endif + enddo + endif + +! Compute power levels from weak data only + do i=0,kstep-1 + px=px + xw(i)**2 + enddo + + x1(0:kstep-1)=xw(0:kstep-1) + xs(0:kstep-1) !Recombine weak + strong + + return +end subroutine timf2 diff --git a/lib/wqencode.f90 b/lib/wqencode.f90 new file mode 100644 index 000000000..b5f453c6a --- /dev/null +++ b/lib/wqencode.f90 @@ -0,0 +1,65 @@ +subroutine wqencode(msg,ntype,data0) + +! Parse and encode a WSPR message. + + use packjt + parameter (MASK15=32767) + character*22 msg + character*12 call1,call2 + character grid4*4,grid6*6 + logical lbad1,lbad2 + integer*1 data0(11) + integer nu(0:9) + data nu/0,-1,1,0,-1,2,1,0,-1,1/ + +! Standard WSPR message (types 0 3 7 10 13 17 ... 60) + i1=index(msg,' ') + i2=index(msg,'/') + i3=index(msg,'<') + call1=msg(:i1-1) + if(i1.lt.3 .or. i1.gt.7 .or. i2.gt.0 .or. i3.gt.0) go to 10 + grid4=msg(i1+1:i1+4) + call packcall(call1,n1,lbad1) + call packgrid(grid4,ng,lbad2) + if(lbad1 .or. lbad2) go to 10 + ndbm=0 + read(msg(i1+5:),*) ndbm + if(ndbm.lt.0) ndbm=0 + if(ndbm.gt.60) ndbm=60 + ndbm=ndbm+nu(mod(ndbm,10)) + n2=128*ng + (ndbm+64) + call pack50(n1,n2,data0) + ntype=ndbm + go to 900 + +10 if(i2.ge.2 .and. i3.lt.1) then + call packpfx(call1,n1,ng,nadd) + ndbm=0 + read(msg(i1+1:),*) ndbm + if(ndbm.lt.0) ndbm=0 + if(ndbm.gt.60) ndbm=60 + ndbm=ndbm+nu(mod(ndbm,10)) + ntype=ndbm + 1 + nadd + n2=128*ng + ntype + 64 + call pack50(n1,n2,data0) + else if(i3.eq.1) then + i4=index(msg,'>') + call1=msg(2:i4-1) + call hash(call1,i4-2,ih) + grid6=msg(i1+1:i1+6) + call2=grid6(2:6)//grid6(1:1)//' ' + call packcall(call2,n1,lbad1) + ndbm=0 + read(msg(i1+8:),*) ndbm + if(ndbm.lt.0) ndbm=0 + if(ndbm.gt.60) ndbm=60 + ndbm=ndbm+nu(mod(ndbm,10)) + ntype=-(ndbm+1) + n2=128*ih + ntype + 64 + call pack50(n1,n2,data0) + endif + go to 900 + +900 continue + return +end subroutine wqencode diff --git a/lib/wsjt4.f90 b/lib/wsjt4.f90 index 7d7119392..12726ff91 100644 --- a/lib/wsjt4.f90 +++ b/lib/wsjt4.f90 @@ -1,4 +1,4 @@ -subroutine wsjt4(dat,npts,nutc,NClearAve,ntol,emedelay,dttol, & +subroutine wsjt4(dat,npts,nutc,NClearAve,minsync,ntol,emedelay,dttol, & mode4,minw,mycall,hiscall,hisgrid,nfqso,NAgain,ndepth,neme) ! Orchestrates the process of decoding JT4 messages, using data that @@ -30,8 +30,7 @@ subroutine wsjt4(dat,npts,nutc,NClearAve,ntol,emedelay,dttol, & endif zz=0. -! syncmin=1.0 - syncmin=7.0 + syncmin=5.0 + minsync naggressive=0 if(ndepth.ge.2) naggressive=1 nq1=3 @@ -102,7 +101,7 @@ subroutine wsjt4(dat,npts,nutc,NClearAve,ntol,emedelay,dttol, & ! Fano succeeded: display the message and return FANO OK write(*,1010) nutc,nsnr,dtx,nfreq,csync,decoded,' *', & char(ichar('A')+ich-1) -1010 format(i4.4,i4,f5.2,i5,a1,1x,a22,a2,1x,a1,i3) +1010 format(i4.4,i4,f5.2,i5,1x,a1,1x,a22,a2,1x,a1,i3) nsave=0 go to 990 @@ -174,6 +173,5 @@ subroutine wsjt4(dat,npts,nutc,NClearAve,ntol,emedelay,dttol, & deepave,cqual,char(ichar('A')+ich-1),ndeepave endif -990 return +990 return end subroutine wsjt4 - diff --git a/lib/wspr_downsample.f90 b/lib/wspr_downsample.f90 new file mode 100644 index 000000000..c2691866f --- /dev/null +++ b/lib/wspr_downsample.f90 @@ -0,0 +1,76 @@ +subroutine wspr_downsample(id2,k) + +! Input: +! id2 raw 16-bit integer data, 12000 Hz sample rate +! k pointer to the most recent new data + +! Output (in common/c0com) +! c0 complex data downsampled to 1500 Hz + + parameter (NMAX=120*12000) !Total sample intervals per 30 minutes + parameter (NDMAX=120*1500) !Sample intervals at 1500 Hz rate + parameter (NSMAX=1366) !Max length of saved spectra + parameter (NFFT1=1024) + parameter (MAXFFT3=32768) + real*4 w3(MAXFFT3) + real*4 x0(NFFT1),x1(NFFT1) + real*4 x2(NFFT1+105) + real*4 ssum(NSMAX) + logical*1 lstrong(0:1023) !Should be (0:512) + integer*2 id2(NMAX) + complex c0 + common/c0com/c0(NDMAX) + data rms/999.0/,k0/99999999/,nfft3z/0/,nsps/8192/,nbfo/1500/ + save + + nfft3=nsps/4 + jstep=nsps/16 + if(k.gt.NMAX) go to 999 + if(k.lt.nfft3) go to 999 !Wait for enough samples to start + if(nfft3.ne.nfft3z) then + pi=4.0*atan(1.0) + do i=1,nfft3 + w3(i)=2.0*(sin(i*pi/nfft3))**2 !Window for nfft3 + enddo + nfft3z=nfft3 + endif + + if(k.lt.k0) then + ja=0 + ssum=0. + k1=0 + k8=0 + x2=0. +! if(ndiskdat.eq.0) then +! id2(k+1:)=0 +! c0=0. !This is necessary to prevent "ghosts". Not sure why. +! endif + endif + k0=k + + nzap=0 + nbslider=0 + sigmas=1.0*(10.0**(0.01*nbslider)) + 0.7 + peaklimit=sigmas*max(10.0,rms) + px=0. + + nwindow=2 + kstep1=NFFT1 + if(nwindow.ne.0) kstep1=NFFT1/2 + fac=2.0/NFFT1 + nblks=(k-k1)/kstep1 + gain=1.0 + do nblk=1,nblks + do i=1,NFFT1 + x0(i)=gain*id2(k1+i) + enddo + call timf2(x0,k,NFFT1,nwindow,nb,peaklimit,x1, & + slimit,lstrong,px,nzap) +! Mix at nbfo Hz, lowpass at +/-750 Hz, and downsample to 1500 Hz complex. + call mixlpf(x1,nbfo,c0(k8+1)) + k1=k1+kstep1 + k8=k8+kstep1/8 + enddo + +999 return +end subroutine wspr_downsample diff --git a/lib/wsprd/Makefile b/lib/wsprd/Makefile new file mode 100644 index 000000000..8095bbcbe --- /dev/null +++ b/lib/wsprd/Makefile @@ -0,0 +1,39 @@ +#CC = gcc +CC = clang +FC = gfortran + +FFLAGS = -O2 -Wall -Wno-conversion +CFLAGS= -I/usr/include -Wall -Wno-missing-braces -O2 +LDFLAGS = -L/usr/lib +LIBS = -lfftw3 -lm + +# Default rules +%.o: %.c $(DEPS) + ${CC} ${CFLAGS} -c $< +%.o: %.f + ${FC} ${FFLAGS} -c $< +%.o: %.F + ${FC} ${FFLAGS} -c $< +%.o: %.f90 + ${FC} ${FFLAGS} -c $< +%.o: %.F90 + ${FC} ${FFLAGS} -c $< + +all: wsprd WSPRcode test_wspr + +DEPS = fano.h +OBJS1 = wsprd.o wsprd_utils.o fano.o tab.o nhash.o +wsprd: $(OBJS1) + $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS) + +OBJS2 = test_wspr.o unpk.o wsprd_utils.o nhash.o +test_wspr: $(OBJS2) libwspr.a + $(FC) -o test_wspr $(FFLAGS) $(OBJS2) libwspr.a + + +OBJS3 = WSPRcode.o +WSPRcode: $(OBJS3) libwspr.a + $(FC) -o WSPRcode $(FFLAGS) $(OBJS3) libwspr.a + +clean: + rm *.o wsprd diff --git a/lib/wsprd/Makefile.MinGW b/lib/wsprd/Makefile.MinGW new file mode 100644 index 000000000..d9fd6b83c --- /dev/null +++ b/lib/wsprd/Makefile.MinGW @@ -0,0 +1,30 @@ +CC = gcc +#CC = clang +FC = gfortran + +FFLAGS = -O2 -Wall -Wno-conversion +CFLAGS= -Wall -Wno-missing-braces -O2 +#LDFLAGS = -L/JTSDK/fftw3f +LIBS = c:/JTSDK/fftw3f/libfftw3-3.dll -lm + +# Default rules +%.o: %.c $(DEPS) + ${CC} ${CFLAGS} -c $< +%.o: %.f + ${FC} ${FFLAGS} -c $< +%.o: %.F + ${FC} ${FFLAGS} -c $< +%.o: %.f90 + ${FC} ${FFLAGS} -c $< +%.o: %.F90 + ${FC} ${FFLAGS} -c $< + +all: wsprd + +DEPS = fano.h +OBJS1 = wsprd.o wsprd_utils.o fano.o tab.o nhash.o +wsprd: $(OBJS1) + $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) $(LIBS) + +clean: + rm *.o wsprd diff --git a/lib/wsprd/WSPRcode.f90 b/lib/wsprd/WSPRcode.f90 new file mode 100644 index 000000000..1ec91712b --- /dev/null +++ b/lib/wsprd/WSPRcode.f90 @@ -0,0 +1,132 @@ +program wsprcode + +! This program provides examples of the source encoding, convolutional +! error-control coding, bit and symbol ordering, and synchronizing +! information contained in WSPR messages. + + parameter (NSYM=162) + parameter (MAXSYM=176) + character*22 msg,msg2 + integer*1 data0(7) + integer*1 data1(7) + integer*1 dat(NSYM) + integer*1 softsym(NSYM) + +! Define the sync vector: + integer*1 sync(NSYM) + data sync/ & + 1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, & + 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, & + 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, & + 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, & + 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, & + 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, & + 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, & + 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, & + 0,0/ + +! Metric table for decoding from soft symbols + integer mettab(0:255,0:1) + data mettab/ & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, & + 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, & + -1, -1, -1, -2, -2, -3, -4, -4, -5, -6, & + -7, -7, -8, -9, -10, -11, -12, -12, -13, -14, & + -15, -16, -17, -17, -18, -19, -20, -21, -22, -22, & + -23, -24, -25, -26, -26, -27, -28, -29, -30, -30, & + -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, & + -38, -39, -40, -41, -41, -42, -43, -43, -44, -45, & + -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, & + -52, -53, -53, -54, -54, -55, -56, -56, -57, -57, & + -58, -59, -59, -60, -60, -61, -62, -62, -62, -63, & + -64, -64, -65, -65, -66, -67, -67, -67, -68, -69, & + -69, -70, -70, -71, -72, -72, -72, -72, -73, -74, & + -75, -75, -75, -77, -76, -76, -78, -78, -80, -81, & + -80, -79, -83, -82, -81, -82, -82, -83, -84, -84, & + -84, -87, -86, -87, -88, -89, -89, -89, -88, -87, & + -86, -87, -84, -84, -84, -83, -82, -82, -81, -82, & + -83, -79, -80, -81, -80, -78, -78, -76, -76, -77, & + -75, -75, -75, -74, -73, -72, -72, -72, -72, -71, & + -70, -70, -69, -69, -68, -67, -67, -67, -66, -65, & + -65, -64, -64, -63, -62, -62, -62, -61, -60, -60, & + -59, -59, -58, -57, -57, -56, -56, -55, -54, -54, & + -53, -53, -52, -51, -51, -50, -49, -49, -48, -47, & + -47, -46, -45, -45, -44, -43, -43, -42, -41, -41, & + -40, -39, -38, -38, -37, -36, -36, -35, -34, -33, & + -33, -32, -31, -30, -30, -29, -28, -27, -26, -26, & + -25, -24, -23, -22, -22, -21, -20, -19, -18, -17, & + -17, -16, -15, -14, -13, -12, -12, -11, -10, -9, & + -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, & + -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, & + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, & + 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5/ + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: WSPRcode "message"' + go to 999 + endif + call getarg(1,msg) !Get message from command line + write(*,1000) msg +1000 format('Message: ',a22) + + nbits=50+31 !User bits=50, constraint length=32 + nbytes=(nbits+7)/8 + ndelta=50 + limit=20000 + + data0=0 + call wqencode(msg,ntype0,data0) !Source encoding + write(*,1002) data0 +1002 format(/'Source-encoded message (50 bits, hex):',7z3.2) + + call encode232(data0,nbytes,dat,MAXSYM) !Convolutional encoding + call inter_mept(dat,1) !Interleaving + + write(*,1004) +1004 format(/'Data symbols:') + write(*,1006) (dat(i),i=1,NSYM) +1006 format(5x,30i2) + + write(*,1008) +1008 format(/'Sync symbols:') + write(*,1006) (sync(i),i=1,NSYM) + + write(*,1010) +1010 format(/'Channel symbols:') + write(*,1006) (2*dat(i)+sync(i),i=1,NSYM) + + call inter_mept(dat,-1) !Remove interleaving + softsym=-dat !Simulate soft symbols + +! Call the sequential (Fano algorithm) decoder + call fano232(softsym,nbits,mettab,ndelta,limit,data1,ncycles,metric,nerr) + call wqdecode(data1,msg2,ntype1) + + write(*,1020) ntype1 +1020 format(/'Message type: ',i7) + write(*,1030) msg2 +1030 format('Decoded message: ',a22) + +999 end program wsprcode diff --git a/lib/wsprd/fano.c b/lib/wsprd/fano.c new file mode 100644 index 000000000..e68305065 --- /dev/null +++ b/lib/wsprd/fano.c @@ -0,0 +1,255 @@ +/* + This file is part of wsprd. + + File name: fano.c + + Description: Soft decision Fano sequential decoder for K=32 r=1/2 + convolutional code. + + Copyright 1994, Phil Karn, KA9Q + Minor modifications by Joe Taylor, K1JT +*/ + +#define LL 1 // Select Layland-Lushbaugh code +#include +#include +#include +#include "fano.h" + +struct node { + unsigned long encstate; // Encoder state of next node + long gamma; // Cumulative metric to this node + int metrics[4]; // Metrics indexed by all possible tx syms + int tm[2]; // Sorted metrics for current hypotheses + int i; // Current branch being tested +}; + +// Convolutional coding polynomials. All are rate 1/2, K=32 +#ifdef NASA_STANDARD +/* "NASA standard" code by Massey & Costello + * Nonsystematic, quick look-in, dmin=11, dfree=23 + * used on Pioneer 10-12, Helios A,B + */ +#define POLY1 0xbbef6bb7 +#define POLY2 0xbbef6bb5 +#endif + +#ifdef MJ +/* Massey-Johannesson code + * Nonsystematic, quick look-in, dmin=13, dfree>=23 + * Purported to be more computationally efficient than Massey-Costello + */ +#define POLY1 0xb840a20f +#define POLY2 0xb840a20d +#endif + +#ifdef LL +/* Layland-Lushbaugh code + * Nonsystematic, non-quick look-in, dmin=?, dfree=? + */ +#define POLY1 0xf2d05351 +#define POLY2 0xe4613c47 +#endif + +/* Convolutional encoder macro. Takes the encoder state, generates + * a rate 1/2 symbol pair and stores it in 'sym'. The symbol generated from + * POLY1 goes into the 2-bit of sym, and the symbol generated from POLY2 + * goes into the 1-bit. + */ +#define ENCODE(sym,encstate) {\ + unsigned long _tmp;\ +\ + _tmp = (encstate) & POLY1;\ + _tmp ^= _tmp >> 16;\ + (sym) = Partab[(_tmp ^ (_tmp >> 8)) & 0xff] << 1;\ + _tmp = (encstate) & POLY2;\ + _tmp ^= _tmp >> 16;\ + (sym) |= Partab[(_tmp ^ (_tmp >> 8)) & 0xff];\ +} + + +/* Convolutionally encode a packet. The input data bytes are read + * high bit first and the encoded packet is written into 'symbols', + * one symbol per byte. The first symbol is generated from POLY1, + * the second from POLY2. + * + * Storing only one symbol per byte uses more space, but it is faster + * and easier than trying to pack them more compactly. + */ +int encode( + unsigned char *symbols, // Output buffer, 2*nbytes + unsigned char *data, // Input buffer, nbytes + unsigned int nbytes) // Number of bytes in data +{ + unsigned long encstate; + int sym; + int i; + + encstate = 0; + while(nbytes-- != 0) { + for(i=7;i>=0;i--) { + encstate = (encstate << 1) | ((*data >> i) & 1); + ENCODE(sym,encstate); + *symbols++ = sym >> 1; + *symbols++ = sym & 1; + } + data++; + } + return 0; +} + +/* Decode packet with the Fano algorithm. + * Return 0 on success, -1 on timeout + */ +int fano( + unsigned int *metric, // Final path metric (returned value) + unsigned int *cycles, // Cycle count (returned value) + unsigned int *maxnp, // Progress before timeout (returned value) + unsigned char *data, // Decoded output data + unsigned char *symbols, // Raw deinterleaved input symbols + unsigned int nbits, // Number of output bits + int mettab[2][256], // Metric table, [sent sym][rx symbol] + int delta, // Threshold adjust parameter + unsigned int maxcycles) // Decoding timeout in cycles per bit +{ + struct node *nodes; // First node + struct node *np; // Current node + struct node *lastnode; // Last node + struct node *tail; // First node of tail + int t; // Threshold + int m0,m1; + int ngamma; + unsigned int lsym; + unsigned int i; + + if((nodes = (struct node *)malloc(nbits*sizeof(struct node))) == NULL) { + printf("malloc failed\n"); + return 0; + } + lastnode = &nodes[nbits-1]; + tail = &nodes[nbits-31]; + *maxnp = 0; + +/* Compute all possible branch metrics for each symbol pair + * This is the only place we actually look at the raw input symbols + */ + for(np=nodes;np <= lastnode;np++) { + np->metrics[0] = mettab[0][symbols[0]] + mettab[0][symbols[1]]; + np->metrics[1] = mettab[0][symbols[0]] + mettab[1][symbols[1]]; + np->metrics[2] = mettab[1][symbols[0]] + mettab[0][symbols[1]]; + np->metrics[3] = mettab[1][symbols[0]] + mettab[1][symbols[1]]; + symbols += 2; + } + np = nodes; + np->encstate = 0; + +// Compute and sort branch metrics from root node */ + ENCODE(lsym,np->encstate); // 0-branch (LSB is 0) + m0 = np->metrics[lsym]; + +/* Now do the 1-branch. To save another ENCODE call here and + * inside the loop, we assume that both polynomials are odd, + * providing complementary pairs of branch symbols. + + * This code should be modified if a systematic code were used. + */ + + m1 = np->metrics[3^lsym]; + if(m0 > m1) { + np->tm[0] = m0; // 0-branch has better metric + np->tm[1] = m1; + } else { + np->tm[0] = m1; // 1-branch is better + np->tm[1] = m0; + np->encstate++; // Set low bit + } + np->i = 0; // Start with best branch + maxcycles *= nbits; + np->gamma = t = 0; + + // Start the Fano decoder + for(i=1;i <= maxcycles;i++) { + if((int)(np-nodes) > (int)*maxnp) *maxnp=(int)(np-nodes); +#ifdef debug + printf("k=%ld, g=%ld, t=%d, m[%d]=%d, maxnp=%d\n", + np-nodes,np->gamma,t,np->i,np->tm[np->i],*maxnp); +#endif +// Look forward */ + ngamma = np->gamma + np->tm[np->i]; + if(ngamma >= t) { + if(np->gamma < t + delta) { // Node is acceptable + /* First time we've visited this node; + * Tighten threshold. + * + * This loop could be replaced with + * t += delta * ((ngamma - t)/delta); + * but the multiply and divide are slower. + */ + while(ngamma >= t + delta) t += delta; + } + np[1].gamma = ngamma; // Move forward + np[1].encstate = np->encstate << 1; + if(++np == lastnode) { + break; // Done! + } + + /* Compute and sort metrics, starting with the + * zero branch + */ + ENCODE(lsym,np->encstate); + if(np >= tail) { + /* The tail must be all zeroes, so don't + * bother computing the 1-branches here. + */ + np->tm[0] = np->metrics[lsym]; + } else { + m0 = np->metrics[lsym]; + m1 = np->metrics[3^lsym]; + if(m0 > m1) { + np->tm[0] = m0; // 0-branch is better + np->tm[1] = m1; + } else { + np->tm[0] = m1; // 1-branch is better + np->tm[1] = m0; + np->encstate++; // Set low bit + } + } + np->i = 0; // Start with best branch + continue; + } + // Threshold violated, can't go forward + for(;;) { // Look backward + if(np == nodes || np[-1].gamma < t) { + /* Can't back up either. + * Relax threshold and and look + * forward again to better branch. + */ + t -= delta; + if(np->i != 0) { + np->i = 0; + np->encstate ^= 1; + } + break; + } + // Back up + if(--np < tail && np->i != 1) { + np->i++; // Search next best branch + np->encstate ^= 1; + break; + } // else keep looking back + } + } + *metric = np->gamma; // Return the final path metric + + // Copy decoded data to user's buffer + nbits >>= 3; + np = &nodes[7]; + while(nbits-- != 0) { + *data++ = np->encstate; + np += 8; + } + *cycles = i+1; + free(nodes); + if(i >= maxcycles) return -1; // Decoder timed out + return 0; // Successful completion +} diff --git a/lib/wsprd/fano.h b/lib/wsprd/fano.h new file mode 100644 index 000000000..76001626b --- /dev/null +++ b/lib/wsprd/fano.h @@ -0,0 +1,21 @@ +/* + This file is part of wsprd. + + File name: fano.h + + Description: Header file for sequential Fano decoder. + + Copyright 1994, Phil Karn, KA9Q + Minor modifications by Joe Taylor, K1JT +*/ + +int fano(unsigned int *metric, unsigned int *cycles, unsigned int *maxnp, + unsigned char *data,unsigned char *symbols, unsigned int nbits, + int mettab[2][256],int delta,unsigned int maxcycles); + +int encode(unsigned char *symbols,unsigned char *data,unsigned int nbytes); + +extern unsigned char Partab[]; + + + diff --git a/lib/wsprd/fftw3.h b/lib/wsprd/fftw3.h new file mode 100644 index 000000000..58a2c73df --- /dev/null +++ b/lib/wsprd/fftw3.h @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2003, 2007-11 Matteo Frigo + * Copyright (c) 2003, 2007-11 Massachusetts Institute of Technology + * + * The following statement of license applies *only* to this header file, + * and *not* to the other files distributed with FFTW or derived therefrom: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/***************************** NOTE TO USERS ********************************* + * + * THIS IS A HEADER FILE, NOT A MANUAL + * + * If you want to know how to use FFTW, please read the manual, + * online at http://www.fftw.org/doc/ and also included with FFTW. + * For a quick start, see the manual's tutorial section. + * + * (Reading header files to learn how to use a library is a habit + * stemming from code lacking a proper manual. Arguably, it's a + * *bad* habit in most cases, because header files can contain + * interfaces that are not part of the public, stable API.) + * + ****************************************************************************/ + +#ifndef FFTW3_H +#define FFTW3_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* If is included, use the C99 complex type. Otherwise + define a type bit-compatible with C99 complex */ +#if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) +# define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C +#else +# define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2] +#endif + +#define FFTW_CONCAT(prefix, name) prefix ## name +#define FFTW_MANGLE_DOUBLE(name) FFTW_CONCAT(fftw_, name) +#define FFTW_MANGLE_FLOAT(name) FFTW_CONCAT(fftwf_, name) +#define FFTW_MANGLE_LONG_DOUBLE(name) FFTW_CONCAT(fftwl_, name) +#define FFTW_MANGLE_QUAD(name) FFTW_CONCAT(fftwq_, name) + +/* IMPORTANT: for Windows compilers, you should add a line + #define FFTW_DLL + here and in kernel/ifftw.h if you are compiling/using FFTW as a + DLL, in order to do the proper importing/exporting, or + alternatively compile with -DFFTW_DLL or the equivalent + command-line flag. This is not necessary under MinGW/Cygwin, where + libtool does the imports/exports automatically. */ +#if defined(FFTW_DLL) && (defined(_WIN32) || defined(__WIN32__)) + /* annoying Windows syntax for shared-library declarations */ +# if defined(COMPILING_FFTW) /* defined in api.h when compiling FFTW */ +# define FFTW_EXTERN extern __declspec(dllexport) +# else /* user is calling FFTW; import symbol */ +# define FFTW_EXTERN extern __declspec(dllimport) +# endif +#else +# define FFTW_EXTERN extern +#endif + +enum fftw_r2r_kind_do_not_use_me { + FFTW_R2HC=0, FFTW_HC2R=1, FFTW_DHT=2, + FFTW_REDFT00=3, FFTW_REDFT01=4, FFTW_REDFT10=5, FFTW_REDFT11=6, + FFTW_RODFT00=7, FFTW_RODFT01=8, FFTW_RODFT10=9, FFTW_RODFT11=10 +}; + +struct fftw_iodim_do_not_use_me { + int n; /* dimension size */ + int is; /* input stride */ + int os; /* output stride */ +}; + +#include /* for ptrdiff_t */ +struct fftw_iodim64_do_not_use_me { + ptrdiff_t n; /* dimension size */ + ptrdiff_t is; /* input stride */ + ptrdiff_t os; /* output stride */ +}; + +typedef void (*fftw_write_char_func_do_not_use_me)(char c, void *); +typedef int (*fftw_read_char_func_do_not_use_me)(void *); + +/* + huge second-order macro that defines prototypes for all API + functions. We expand this macro for each supported precision + + X: name-mangling macro + R: real data type + C: complex data type +*/ + +#define FFTW_DEFINE_API(X, R, C) \ + \ +FFTW_DEFINE_COMPLEX(R, C); \ + \ +typedef struct X(plan_s) *X(plan); \ + \ +typedef struct fftw_iodim_do_not_use_me X(iodim); \ +typedef struct fftw_iodim64_do_not_use_me X(iodim64); \ + \ +typedef enum fftw_r2r_kind_do_not_use_me X(r2r_kind); \ + \ +typedef fftw_write_char_func_do_not_use_me X(write_char_func); \ +typedef fftw_read_char_func_do_not_use_me X(read_char_func); \ + \ +FFTW_EXTERN void X(execute)(const X(plan) p); \ + \ +FFTW_EXTERN X(plan) X(plan_dft)(int rank, const int *n, \ + C *in, C *out, int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_1d)(int n, C *in, C *out, int sign, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_2d)(int n0, int n1, \ + C *in, C *out, int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_3d)(int n0, int n1, int n2, \ + C *in, C *out, int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft)(int rank, const int *n, \ + int howmany, \ + C *in, const int *inembed, \ + int istride, int idist, \ + C *out, const int *onembed, \ + int ostride, int odist, \ + int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_dft)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + C *in, C *out, \ + int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *ri, R *ii, R *ro, R *io, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_dft)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + C *in, C *out, \ + int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *ri, R *ii, R *ro, R *io, \ + unsigned flags); \ + \ +FFTW_EXTERN void X(execute_dft)(const X(plan) p, C *in, C *out); \ +FFTW_EXTERN void X(execute_split_dft)(const X(plan) p, R *ri, R *ii, \ + R *ro, R *io); \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft_r2c)(int rank, const int *n, \ + int howmany, \ + R *in, const int *inembed, \ + int istride, int idist, \ + C *out, const int *onembed, \ + int ostride, int odist, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_r2c)(int rank, const int *n, \ + R *in, C *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_1d)(int n,R *in,C *out,unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_2d)(int n0, int n1, \ + R *in, C *out, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_3d)(int n0, int n1, \ + int n2, \ + R *in, C *out, unsigned flags); \ + \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft_c2r)(int rank, const int *n, \ + int howmany, \ + C *in, const int *inembed, \ + int istride, int idist, \ + R *out, const int *onembed, \ + int ostride, int odist, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_c2r)(int rank, const int *n, \ + C *in, R *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_1d)(int n,C *in,R *out,unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_2d)(int n0, int n1, \ + C *in, R *out, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_3d)(int n0, int n1, \ + int n2, \ + C *in, R *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_dft_r2c)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, C *out, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_dft_c2r)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + C *in, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft_r2c)( \ + int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, R *ro, R *io, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft_c2r)( \ + int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *ri, R *ii, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_dft_r2c)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, C *out, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_dft_c2r)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + C *in, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft_r2c)( \ + int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, R *ro, R *io, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft_c2r)( \ + int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *ri, R *ii, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN void X(execute_dft_r2c)(const X(plan) p, R *in, C *out); \ +FFTW_EXTERN void X(execute_dft_c2r)(const X(plan) p, C *in, R *out); \ + \ +FFTW_EXTERN void X(execute_split_dft_r2c)(const X(plan) p, \ + R *in, R *ro, R *io); \ +FFTW_EXTERN void X(execute_split_dft_c2r)(const X(plan) p, \ + R *ri, R *ii, R *out); \ + \ +FFTW_EXTERN X(plan) X(plan_many_r2r)(int rank, const int *n, \ + int howmany, \ + R *in, const int *inembed, \ + int istride, int idist, \ + R *out, const int *onembed, \ + int ostride, int odist, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_r2r)(int rank, const int *n, R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_r2r_1d)(int n, R *in, R *out, \ + X(r2r_kind) kind, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_r2r_2d)(int n0, int n1, R *in, R *out, \ + X(r2r_kind) kind0, X(r2r_kind) kind1, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_r2r_3d)(int n0, int n1, int n2, \ + R *in, R *out, X(r2r_kind) kind0, \ + X(r2r_kind) kind1, X(r2r_kind) kind2, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_r2r)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_r2r)(int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN void X(execute_r2r)(const X(plan) p, R *in, R *out); \ + \ +FFTW_EXTERN void X(destroy_plan)(X(plan) p); \ +FFTW_EXTERN void X(forget_wisdom)(void); \ +FFTW_EXTERN void X(cleanup)(void); \ + \ +FFTW_EXTERN void X(set_timelimit)(double t); \ + \ +FFTW_EXTERN void X(plan_with_nthreads)(int nthreads); \ +FFTW_EXTERN int X(init_threads)(void); \ +FFTW_EXTERN void X(cleanup_threads)(void); \ + \ +FFTW_EXTERN int X(export_wisdom_to_filename)(const char *filename); \ +FFTW_EXTERN void X(export_wisdom_to_file)(FILE *output_file); \ +FFTW_EXTERN char *X(export_wisdom_to_string)(void); \ +FFTW_EXTERN void X(export_wisdom)(X(write_char_func) write_char, \ + void *data); \ +FFTW_EXTERN int X(import_system_wisdom)(void); \ +FFTW_EXTERN int X(import_wisdom_from_filename)(const char *filename); \ +FFTW_EXTERN int X(import_wisdom_from_file)(FILE *input_file); \ +FFTW_EXTERN int X(import_wisdom_from_string)(const char *input_string); \ +FFTW_EXTERN int X(import_wisdom)(X(read_char_func) read_char, void *data); \ + \ +FFTW_EXTERN void X(fprint_plan)(const X(plan) p, FILE *output_file); \ +FFTW_EXTERN void X(print_plan)(const X(plan) p); \ + \ +FFTW_EXTERN void *X(malloc)(size_t n); \ +FFTW_EXTERN R *X(alloc_real)(size_t n); \ +FFTW_EXTERN C *X(alloc_complex)(size_t n); \ +FFTW_EXTERN void X(free)(void *p); \ + \ +FFTW_EXTERN void X(flops)(const X(plan) p, \ + double *add, double *mul, double *fmas); \ +FFTW_EXTERN double X(estimate_cost)(const X(plan) p); \ +FFTW_EXTERN double X(cost)(const X(plan) p); \ + \ +FFTW_EXTERN const char X(version)[]; \ +FFTW_EXTERN const char X(cc)[]; \ +FFTW_EXTERN const char X(codelet_optim)[]; + + +/* end of FFTW_DEFINE_API macro */ + +FFTW_DEFINE_API(FFTW_MANGLE_DOUBLE, double, fftw_complex) +FFTW_DEFINE_API(FFTW_MANGLE_FLOAT, float, fftwf_complex) +FFTW_DEFINE_API(FFTW_MANGLE_LONG_DOUBLE, long double, fftwl_complex) + +/* __float128 (quad precision) is a gcc extension on i386, x86_64, and ia64 + for gcc >= 4.6 (compiled in FFTW with --enable-quad-precision) */ +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) \ + && !(defined(__ICC) || defined(__INTEL_COMPILER)) \ + && (defined(__i386__) || defined(__x86_64__) || defined(__ia64__)) +# if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) +/* note: __float128 is a typedef, which is not supported with the _Complex + keyword in gcc, so instead we use this ugly __attribute__ version. + However, we can't simply pass the __attribute__ version to + FFTW_DEFINE_API because the __attribute__ confuses gcc in pointer + types. Hence redefining FFTW_DEFINE_COMPLEX. Ugh. */ +# undef FFTW_DEFINE_COMPLEX +# define FFTW_DEFINE_COMPLEX(R, C) typedef _Complex float __attribute__((mode(TC))) C +# endif +FFTW_DEFINE_API(FFTW_MANGLE_QUAD, __float128, fftwq_complex) +#endif + +#define FFTW_FORWARD (-1) +#define FFTW_BACKWARD (+1) + +#define FFTW_NO_TIMELIMIT (-1.0) + +/* documented flags */ +#define FFTW_MEASURE (0U) +#define FFTW_DESTROY_INPUT (1U << 0) +#define FFTW_UNALIGNED (1U << 1) +#define FFTW_CONSERVE_MEMORY (1U << 2) +#define FFTW_EXHAUSTIVE (1U << 3) /* NO_EXHAUSTIVE is default */ +#define FFTW_PRESERVE_INPUT (1U << 4) /* cancels FFTW_DESTROY_INPUT */ +#define FFTW_PATIENT (1U << 5) /* IMPATIENT is default */ +#define FFTW_ESTIMATE (1U << 6) +#define FFTW_WISDOM_ONLY (1U << 21) + +/* undocumented beyond-guru flags */ +#define FFTW_ESTIMATE_PATIENT (1U << 7) +#define FFTW_BELIEVE_PCOST (1U << 8) +#define FFTW_NO_DFT_R2HC (1U << 9) +#define FFTW_NO_NONTHREADED (1U << 10) +#define FFTW_NO_BUFFERING (1U << 11) +#define FFTW_NO_INDIRECT_OP (1U << 12) +#define FFTW_ALLOW_LARGE_GENERIC (1U << 13) /* NO_LARGE_GENERIC is default */ +#define FFTW_NO_RANK_SPLITS (1U << 14) +#define FFTW_NO_VRANK_SPLITS (1U << 15) +#define FFTW_NO_VRECURSE (1U << 16) +#define FFTW_NO_SIMD (1U << 17) +#define FFTW_NO_SLOW (1U << 18) +#define FFTW_NO_FIXED_RADIX_LARGE_N (1U << 19) +#define FFTW_ALLOW_PRUNING (1U << 20) + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* FFTW3_H */ diff --git a/lib/wsprd/genmet.f90 b/lib/wsprd/genmet.f90 new file mode 100644 index 000000000..e5dcdabaf --- /dev/null +++ b/lib/wsprd/genmet.f90 @@ -0,0 +1,50 @@ +program genmet + + character*12 arg + integer hist(-128:128) + lim(x)=min(127,max(-128,nint(scale*x))) + + nargs=iargc() + if(nargs.ne.4) then + print*,'Usage: genmet bw scale snr iters' + print*,'Example: genmet 1.46 20 -24 1000000' + go to 999 + endif + call getarg(1,arg) + read(arg,*) bw + call getarg(2,arg) + read(arg,*) scale + call getarg(3,arg) + read(arg,*) snr + call getarg(4,arg) + read(arg,*) iters + + hist=0 + s=sqrt(2500.0/bw) * 10.0**(0.05*snr) + fac=1.0/sqrt(2.0) + do iter=1,iters + x1=fac*gran() + y1=fac*gran() + x0=fac*gran() + y0=fac*gran() + r=(x1+s)**2 + y1*y1 - x0*x0 - y0*y0 + hist(lim(r))=hist(lim(r))+1 + enddo + + xln2=log(2.0) + do i=-128,127 + p1=hist(i)/dfloat(iters) + j=-i + if(j.gt.127) j=127 + p0=hist(j)/dfloat(iters) + xlhd0=log(max(0.001,2.0*p0/(p0+p1)))/xln2 + xlhd1=log(max(0.001,2.0*p1/(p0+p1)))/xln2 + write(13,1010) i/scale,hist(i)/dfloat(iters) +1010 format(f8.3,f12.9) + write(14,1012) i+128,xlhd0,xlhd1 +1012 format(i4,2f8.3) + enddo + +999 end program genmet + + diff --git a/lib/wsprd/gran.c b/lib/wsprd/gran.c new file mode 100644 index 000000000..24b986503 --- /dev/null +++ b/lib/wsprd/gran.c @@ -0,0 +1,28 @@ +#include +#include + +/* Generate gaussian random float with mean=0 and std_dev=1 */ +float gran_() +{ + float fac,rsq,v1,v2; + static float gset; + static int iset; + + if(iset){ + /* Already got one */ + iset = 0; + return gset; + } + /* Generate two evenly distributed numbers between -1 and +1 + * that are inside the unit circle + */ + do { + v1 = 2.0 * (float)rand() / RAND_MAX - 1; + v2 = 2.0 * (float)rand() / RAND_MAX - 1; + rsq = v1*v1 + v2*v2; + } while(rsq >= 1.0 || rsq == 0.0); + fac = sqrt(-2.0*log(rsq)/rsq); + gset = v1*fac; + iset++; + return v2*fac; +} diff --git a/lib/wsprd/metric_tables.c b/lib/wsprd/metric_tables.c new file mode 100644 index 000000000..5be334edb --- /dev/null +++ b/lib/wsprd/metric_tables.c @@ -0,0 +1,111 @@ +/******************************************************************************* +* 4 metric tables calculated via simulation for 2-FSK with Es/No=0,3,6,9 dB +* tables were calculated for constant rms noise level of 50. The symbol vector +* should be normalized to have rms amplitude equal to "symbol_scale". +********************************************************************************/ +//float symbol_scale[4]={42.6, 53.3, 72.7, 100.2}; +float metric_tables[4][256]={ + 0.9782, 0.9695, 0.9689, 0.9669, 0.9666, 0.9653, 0.9638, 0.9618, 0.9599, 0.9601, + 0.9592, 0.9570, 0.9556, 0.9540, 0.9525, 0.9527, 0.9486, 0.9477, 0.9450, 0.9436, + 0.9424, 0.9400, 0.9381, 0.9360, 0.9340, 0.9316, 0.9301, 0.9272, 0.9254, 0.9224, + 0.9196, 0.9171, 0.9154, 0.9123, 0.9076, 0.9061, 0.9030, 0.9000, 0.8965, 0.8934, + 0.8903, 0.8874, 0.8834, 0.8792, 0.8760, 0.8726, 0.8685, 0.8639, 0.8599, 0.8550, + 0.8504, 0.8459, 0.8422, 0.8364, 0.8320, 0.8262, 0.8215, 0.8159, 0.8111, 0.8052, + 0.7996, 0.7932, 0.7878, 0.7812, 0.7745, 0.7685, 0.7616, 0.7550, 0.7479, 0.7405, + 0.7336, 0.7255, 0.7184, 0.7102, 0.7016, 0.6946, 0.6860, 0.6769, 0.6687, 0.6598, + 0.6503, 0.6416, 0.6325, 0.6219, 0.6122, 0.6016, 0.5920, 0.5818, 0.5711, 0.5606, + 0.5487, 0.5374, 0.5266, 0.5142, 0.5020, 0.4908, 0.4784, 0.4663, 0.4532, 0.4405, + 0.4271, 0.4144, 0.4006, 0.3865, 0.3731, 0.3594, 0.3455, 0.3304, 0.3158, 0.3009, + 0.2858, 0.2708, 0.2560, 0.2399, 0.2233, 0.2074, 0.1919, 0.1756, 0.1590, 0.1427, + 0.1251, 0.1074, 0.0905, 0.0722, 0.0550, 0.0381, 0.0183, 0.0000, -0.0185, -0.0391, + -0.0571, -0.0760, -0.0966, -0.1160, -0.1370, -0.1584, -0.1787, -0.1999, -0.2214, -0.2423, + -0.2643, -0.2879, -0.3114, -0.3336, -0.3568, -0.3806, -0.4050, -0.4293, -0.4552, -0.4798, + -0.5046, -0.5296, -0.5564, -0.5836, -0.6093, -0.6372, -0.6645, -0.6933, -0.7208, -0.7495, + -0.7763, -0.8065, -0.8378, -0.8660, -0.8964, -0.9293, -0.9592, -0.9907, -1.0214, -1.0509, + -1.0850, -1.1168, -1.1528, -1.1847, -1.2157, -1.2511, -1.2850, -1.3174, -1.3540, -1.3900, + -1.4201, -1.4580, -1.4956, -1.5292, -1.5683, -1.6030, -1.6411, -1.6789, -1.7147, -1.7539, + -1.7887, -1.8289, -1.8699, -1.9043, -1.9469, -1.9849, -2.0267, -2.0610, -2.1028, -2.1391, + -2.1855, -2.2215, -2.2712, -2.3033, -2.3440, -2.3870, -2.4342, -2.4738, -2.5209, -2.5646, + -2.6016, -2.6385, -2.6868, -2.7356, -2.7723, -2.8111, -2.8524, -2.9009, -2.9428, -2.9879, + -3.0103, -3.0832, -3.1340, -3.1628, -3.2049, -3.2557, -3.3101, -3.3453, -3.4025, -3.4317, + -3.4828, -3.5270, -3.5745, -3.6181, -3.6765, -3.7044, -3.7410, -3.8118, -3.8368, -3.9549, + -3.9488, -3.9941, -4.0428, -4.0892, -4.1648, -4.1965, -4.1892, -4.2565, -4.3356, -4.3948, + -4.4481, -4.4607, -4.5533, -4.5809, -4.5927, -5.1047, + 0.9978, 0.9962, 0.9961, 0.9959, 0.9958, 0.9954, 0.9949, 0.9950, 0.9947, 0.9942, + 0.9940, 0.9939, 0.9933, 0.9931, 0.9928, 0.9924, 0.9921, 0.9916, 0.9911, 0.9909, + 0.9903, 0.9900, 0.9892, 0.9887, 0.9883, 0.9877, 0.9869, 0.9863, 0.9857, 0.9848, + 0.9842, 0.9835, 0.9825, 0.9817, 0.9808, 0.9799, 0.9791, 0.9777, 0.9767, 0.9757, + 0.9744, 0.9729, 0.9716, 0.9704, 0.9690, 0.9674, 0.9656, 0.9641, 0.9625, 0.9609, + 0.9587, 0.9567, 0.9548, 0.9524, 0.9501, 0.9478, 0.9453, 0.9426, 0.9398, 0.9371, + 0.9339, 0.9311, 0.9277, 0.9242, 0.9206, 0.9168, 0.9131, 0.9087, 0.9043, 0.8999, + 0.8953, 0.8907, 0.8857, 0.8803, 0.8747, 0.8690, 0.8632, 0.8572, 0.8507, 0.8439, + 0.8368, 0.8295, 0.8217, 0.8138, 0.8058, 0.7972, 0.7883, 0.7784, 0.7694, 0.7597, + 0.7489, 0.7378, 0.7269, 0.7152, 0.7030, 0.6911, 0.6782, 0.6643, 0.6506, 0.6371, + 0.6211, 0.6054, 0.5897, 0.5740, 0.5565, 0.5393, 0.5214, 0.5027, 0.4838, 0.4643, + 0.4436, 0.4225, 0.4004, 0.3787, 0.3562, 0.3324, 0.3089, 0.2839, 0.2584, 0.2321, + 0.2047, 0.1784, 0.1499, 0.1213, 0.0915, 0.0628, 0.0314, 0.0000, -0.0321, -0.0657, + -0.0977, -0.1324, -0.1673, -0.2036, -0.2387, -0.2768, -0.3150, -0.3538, -0.3936, -0.4327, + -0.4739, -0.5148, -0.5561, -0.6000, -0.6438, -0.6889, -0.7331, -0.7781, -0.8247, -0.8712, + -0.9177, -0.9677, -1.0142, -1.0631, -1.1143, -1.1686, -1.2169, -1.2680, -1.3223, -1.3752, + -1.4261, -1.4806, -1.5356, -1.5890, -1.6462, -1.7041, -1.7591, -1.8124, -1.8735, -1.9311, + -1.9891, -2.0459, -2.1048, -2.1653, -2.2248, -2.2855, -2.3466, -2.4079, -2.4668, -2.5263, + -2.5876, -2.6507, -2.7142, -2.7761, -2.8366, -2.8995, -2.9620, -3.0279, -3.0973, -3.1576, + -3.2238, -3.2890, -3.3554, -3.4215, -3.4805, -3.5518, -3.6133, -3.6812, -3.7473, -3.8140, + -3.8781, -3.9450, -4.0184, -4.0794, -4.1478, -4.2241, -4.2853, -4.3473, -4.4062, -4.4839, + -4.5539, -4.6202, -4.6794, -4.7478, -4.8309, -4.9048, -4.9669, -5.0294, -5.1194, -5.1732, + -5.2378, -5.3094, -5.3742, -5.4573, -5.5190, -5.5728, -5.6637, -5.7259, -5.7843, -5.8854, + -5.9553, -6.0054, -6.0656, -6.1707, -6.2241, -6.3139, -6.3393, -6.4356, -6.5153, -6.5758, + -6.6506, -6.7193, -6.7542, -6.8942, -6.9219, -6.9605, -7.1013, -7.1895, -7.1549, -7.2799, + -7.4119, -7.4608, -7.5256, -7.5879, -7.7598, -8.4120, + 0.9999, 0.9998, 0.9998, 0.9998, 0.9998, 0.9998, 0.9997, 0.9997, 0.9997, 0.9997, + 0.9997, 0.9996, 0.9996, 0.9996, 0.9995, 0.9995, 0.9994, 0.9994, 0.9994, 0.9993, + 0.9993, 0.9992, 0.9991, 0.9991, 0.9990, 0.9989, 0.9988, 0.9988, 0.9988, 0.9986, + 0.9985, 0.9984, 0.9983, 0.9982, 0.9980, 0.9979, 0.9977, 0.9976, 0.9974, 0.9971, + 0.9969, 0.9968, 0.9965, 0.9962, 0.9960, 0.9957, 0.9953, 0.9950, 0.9947, 0.9941, + 0.9937, 0.9933, 0.9928, 0.9922, 0.9917, 0.9911, 0.9904, 0.9897, 0.9890, 0.9882, + 0.9874, 0.9863, 0.9855, 0.9843, 0.9832, 0.9819, 0.9806, 0.9792, 0.9777, 0.9760, + 0.9743, 0.9724, 0.9704, 0.9683, 0.9659, 0.9634, 0.9609, 0.9581, 0.9550, 0.9516, + 0.9481, 0.9446, 0.9406, 0.9363, 0.9317, 0.9270, 0.9218, 0.9160, 0.9103, 0.9038, + 0.8972, 0.8898, 0.8822, 0.8739, 0.8647, 0.8554, 0.8457, 0.8357, 0.8231, 0.8115, + 0.7984, 0.7854, 0.7704, 0.7556, 0.7391, 0.7210, 0.7038, 0.6840, 0.6633, 0.6408, + 0.6174, 0.5939, 0.5678, 0.5410, 0.5137, 0.4836, 0.4524, 0.4193, 0.3850, 0.3482, + 0.3132, 0.2733, 0.2315, 0.1891, 0.1435, 0.0980, 0.0493, 0.0000, -0.0510, -0.1052, + -0.1593, -0.2177, -0.2759, -0.3374, -0.4005, -0.4599, -0.5266, -0.5935, -0.6626, -0.7328, + -0.8051, -0.8757, -0.9498, -1.0271, -1.1019, -1.1816, -1.2642, -1.3459, -1.4295, -1.5077, + -1.5958, -1.6818, -1.7647, -1.8548, -1.9387, -2.0295, -2.1152, -2.2154, -2.3011, -2.3904, + -2.4820, -2.5786, -2.6730, -2.7652, -2.8616, -2.9546, -3.0526, -3.1445, -3.2445, -3.3416, + -3.4357, -3.5325, -3.6324, -3.7313, -3.8225, -3.9209, -4.0248, -4.1278, -4.2261, -4.3193, + -4.4220, -4.5262, -4.6214, -4.7242, -4.8234, -4.9245, -5.0298, -5.1250, -5.2232, -5.3267, + -5.4332, -5.5342, -5.6431, -5.7270, -5.8401, -5.9350, -6.0407, -6.1418, -6.2363, -6.3384, + -6.4536, -6.5429, -6.6582, -6.7433, -6.8438, -6.9478, -7.0789, -7.1894, -7.2714, -7.3815, + -7.4810, -7.5575, -7.6852, -7.8071, -7.8580, -7.9724, -8.1000, -8.2207, -8.2867, -8.4017, + -8.5287, -8.6347, -8.7082, -8.8319, -8.9448, -9.0355, -9.1885, -9.2095, -9.2863, -9.4186, + -9.5064, -9.6386, -9.7207, -9.8286, -9.9453, -10.0701, -10.1735, -10.3001, -10.2858, -10.5427, + -10.5982, -10.7361, -10.7042, -10.9212, -11.0097, -11.0469, -11.1155, -11.2812, -11.3472, -11.4988, + -11.5327, -11.6692, -11.9376, -11.8606, -12.1372, -13.2539, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, 1.0000, + 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, 0.9999, + 0.9999, 0.9998, 0.9998, 0.9998, 0.9998, 0.9997, 0.9997, 0.9997, 0.9997, 0.9996, + 0.9996, 0.9995, 0.9995, 0.9994, 0.9994, 0.9993, 0.9992, 0.9991, 0.9991, 0.9989, + 0.9988, 0.9986, 0.9985, 0.9983, 0.9981, 0.9980, 0.9977, 0.9974, 0.9971, 0.9968, + 0.9965, 0.9962, 0.9956, 0.9950, 0.9948, 0.9941, 0.9933, 0.9926, 0.9919, 0.9910, + 0.9899, 0.9889, 0.9877, 0.9863, 0.9845, 0.9829, 0.9811, 0.9791, 0.9769, 0.9741, + 0.9716, 0.9684, 0.9645, 0.9611, 0.9563, 0.9519, 0.9463, 0.9406, 0.9344, 0.9272, + 0.9197, 0.9107, 0.9016, 0.8903, 0.8791, 0.8653, 0.8523, 0.8357, 0.8179, 0.7988, + 0.7779, 0.7562, 0.7318, 0.7024, 0.6753, 0.6435, 0.6089, 0.5700, 0.5296, 0.4860, + 0.4366, 0.3855, 0.3301, 0.2735, 0.2114, 0.1443, 0.0682, 0.0000, -0.0715, -0.1604, + -0.2478, -0.3377, -0.4287, -0.5277, -0.6291, -0.7384, -0.8457, -0.9559, -1.0742, -1.1913, + -1.3110, -1.4238, -1.5594, -1.6854, -1.8093, -1.9414, -2.0763, -2.2160, -2.3611, -2.4876, + -2.6374, -2.7710, -2.9225, -3.0591, -3.2077, -3.3452, -3.4916, -3.6316, -3.7735, -3.9296, + -4.0682, -4.2334, -4.3607, -4.5270, -4.6807, -4.8108, -4.9753, -5.1212, -5.2631, -5.4042, + -5.5510, -5.7227, -5.8794, -6.0244, -6.1677, -6.3271, -6.4862, -6.6130, -6.7449, -6.9250, + -7.1232, -7.1736, -7.3628, -7.5596, -7.6906, -7.8129, -7.9817, -8.1440, -8.3016, -8.4797, + -8.5734, -8.7692, -8.9198, -9.0610, -9.1746, -9.3536, -9.5939, -9.6957, -9.8475, -9.9639, + -10.1730, -10.2427, -10.4573, -10.5413, -10.7303, -10.9339, -11.0215, -11.2047, -11.2894, -11.4572, + -11.6256, -11.7794, -11.8801, -12.1717, -12.2354, -12.3686, -12.6195, -12.6527, -12.8247, -12.9560, + -13.3265, -13.1667, -13.4274, -13.6064, -13.5515, -13.9501, -13.9926, -14.4049, -14.1653, -14.4348, + -14.7983, -14.7807, -15.2349, -15.3536, -15.3026, -15.2739, -15.7170, -16.2161, -15.9185, -15.9490, + -16.6258, -16.5568, -16.4318, -16.7999, -16.4101, -17.6393, -17.7643, -17.2644, -17.5973, -17.0403, + -17.7039, -18.0073, -18.1840, -18.3848, -18.6286, -20.7063}; diff --git a/lib/wsprd/mettab.c b/lib/wsprd/mettab.c new file mode 100644 index 000000000..f188c0cc7 --- /dev/null +++ b/lib/wsprd/mettab.c @@ -0,0 +1,76 @@ +/* + This file is part of wsprd. + + File name: mettab.c + Description: Metric table for sequential Fano decoder. + + Copyright 2008-2015, Joseph Taylor, K1JT + License: GNU GPL v3 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +int mettab[2][256]={ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, + -1, -1, -1, -2, -2, -3, -4, -4, -5, -6, + -7, -7, -8, -9, -10, -11, -12, -12, -13, -14, + -15, -16, -17, -17, -18, -19, -20, -21, -22, -22, + -23, -24, -25, -26, -26, -27, -28, -29, -30, -30, + -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, + -38, -39, -40, -41, -41, -42, -43, -43, -44, -45, + -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, + -52, -53, -53, -54, -54, -55, -56, -56, -57, -57, + -58, -59, -59, -60, -60, -61, -62, -62, -62, -63, + -64, -64, -65, -65, -66, -67, -67, -67, -68, -69, + -69, -70, -70, -71, -72, -72, -72, -72, -73, -74, + -75, -75, -75, -77, -76, -76, -78, -78, -80, -81, + -80, -79, -83, -82, -81, -82, -82, -83, -84, -84, + -84, -87, -86, -87, -88,-105, -94,-105, -88, -87, + -86, -87, -84, -84, -84, -83, -82, -82, -81, -82, + -83, -79, -80, -81, -80, -78, -78, -76, -76, -77, + -75, -75, -75, -74, -73, -72, -72, -72, -72, -71, + -70, -70, -69, -69, -68, -67, -67, -67, -66, -65, + -65, -64, -64, -63, -62, -62, -62, -61, -60, -60, + -59, -59, -58, -57, -57, -56, -56, -55, -54, -54, + -53, -53, -52, -51, -51, -50, -49, -49, -48, -47, + -47, -46, -45, -45, -44, -43, -43, -42, -41, -41, + -40, -39, -38, -38, -37, -36, -36, -35, -34, -33, + -33, -32, -31, -30, -30, -29, -28, -27, -26, -26, + -25, -24, -23, -22, -22, -21, -20, -19, -18, -17, + -17, -16, -15, -14, -13, -12, -12, -11, -10, -9, + -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, + -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5 }; diff --git a/lib/wsprd/nhash.c b/lib/wsprd/nhash.c new file mode 100644 index 000000000..1985da9ef --- /dev/null +++ b/lib/wsprd/nhash.c @@ -0,0 +1,376 @@ +/* + This file is part of wsprd. + + File name: nhash.c + + *------------------------------------------------------------------------------ + * + * This file is part of the WSPR application, Weak Signal Propogation Reporter + * + * File Name: nhash.c + * Description: Functions to produce 32-bit hashes for hash table lookup + * + * Copyright (C) 2008-2014 Joseph Taylor, K1JT + * License: GNU GPL v3+ + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin + * Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Files: lookup3.c + * Copyright: Copyright (C) 2006 Bob Jenkins + * License: public-domain + * You may use this code any way you wish, private, educational, or commercial. + * It's free. + * + *------------------------------------------------------------------------------- +*/ + +/* +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +*/ + +#define SELF_TEST 1 + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#ifdef Win32 +#include "win_stdint.h" /* defines uint32_t etc */ +#else +#include /* defines uint32_t etc */ +#endif +//#include /* attempt to define endianness */ +//#ifdef linux +//# include /* attempt to define endianness */ +//#endif + +#define HASH_LITTLE_ENDIAN 1 + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + c=(32767&c); + + return c; +} diff --git a/lib/wsprd/t1.f90 b/lib/wsprd/t1.f90 new file mode 100644 index 000000000..a56260e8e --- /dev/null +++ b/lib/wsprd/t1.f90 @@ -0,0 +1,63 @@ +program t1 + + integer mettab(0:255,0:1) + data mettab/ & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, & + 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, & + -1, -1, -1, -2, -2, -3, -4, -4, -5, -6, & + -7, -7, -8, -9, -10, -11, -12, -12, -13, -14, & + -15, -16, -17, -17, -18, -19, -20, -21, -22, -22, & + -23, -24, -25, -26, -26, -27, -28, -29, -30, -30, & + -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, & + -38, -39, -40, -41, -41, -42, -43, -43, -44, -45, & + -45, -46, -47, -47, -48, -49, -49, -50, -51, -51, & + -52, -53, -53, -54, -54, -55, -56, -56, -57, -57, & + -58, -59, -59, -60, -60, -61, -62, -62, -62, -63, & + -64, -64, -65, -65, -66, -67, -67, -67, -68, -69, & + -69, -70, -70, -71, -72, -72, -72, -72, -73, -74, & + -75, -75, -75, -77, -76, -76, -78, -78, -80, -81, & + -80, -79, -83, -82, -81, -82, -82, -83, -84, -84, & + -84, -87, -86, -87, -88, -89, -89, -89, -88, -87, & + -86, -87, -84, -84, -84, -83, -82, -82, -81, -82, & + -83, -79, -80, -81, -80, -78, -78, -76, -76, -77, & + -75, -75, -75, -74, -73, -72, -72, -72, -72, -71, & + -70, -70, -69, -69, -68, -67, -67, -67, -66, -65, & + -65, -64, -64, -63, -62, -62, -62, -61, -60, -60, & + -59, -59, -58, -57, -57, -56, -56, -55, -54, -54, & + -53, -53, -52, -51, -51, -50, -49, -49, -48, -47, & + -47, -46, -45, -45, -44, -43, -43, -42, -41, -41, & + -40, -39, -38, -38, -37, -36, -36, -35, -34, -33, & + -33, -32, -31, -30, -30, -29, -28, -27, -26, -26, & + -25, -24, -23, -22, -22, -21, -20, -19, -18, -17, & + -17, -16, -15, -14, -13, -12, -12, -11, -10, -9, & + -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, & + -1, -1, -1, 0, 0, 1, 1, 1, 1, 2, & + 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, & + 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, & + 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, & + 5, 5/ + + do i=0,255 + write(*,1010) i,mettab(i,0),mettab(i,1) +1010 format(3i6) + enddo + +end program t1 diff --git a/lib/wsprd/t2.f90 b/lib/wsprd/t2.f90 new file mode 100644 index 000000000..0f214db41 --- /dev/null +++ b/lib/wsprd/t2.f90 @@ -0,0 +1,11 @@ +program t2 + + df=375.0/65536.0 + do i=1,65536 + w=1.0/(1.0 + ((i-32768)/26214.0)**20) + f=(i-32768)*df + write(13,1010) f,w +1010 format(2f15.6) + enddo + +end program t2 diff --git a/lib/wsprd/tab.c b/lib/wsprd/tab.c new file mode 100644 index 000000000..e330c7576 --- /dev/null +++ b/lib/wsprd/tab.c @@ -0,0 +1,41 @@ +/* + This file is part of wsprd. + + File name: tab.c + Description: 8-bit parity lookup table. +*/ +unsigned char Partab[] = { + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, +}; + diff --git a/lib/wsprd/test_wspr.f90 b/lib/wsprd/test_wspr.f90 new file mode 100644 index 000000000..86e291868 --- /dev/null +++ b/lib/wsprd/test_wspr.f90 @@ -0,0 +1,61 @@ +program test_wspr + +! This program provides examples of the source encoding, convolutional +! error-control coding, bit and symbol ordering, and synchronizing +! information contained in WSPR messages. + + character*22 msg,msg2 + character*23 msg3 + character*1 err2,err3 + integer*1 data0(11) + logical lfile + +! Get command-line argument(s) + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: test_wspr "message"' + go to 999 + endif + call getarg(1,msg) !Get message from command line + call unpk(data0,1,msg3) !Read the C hashtable + lfile=msg(1:2).eq."-t" + if(lfile) open(10,file="messages.txt",status="old") + + do imsg=1,999 + if(lfile) read(10,1001,end=900) msg +1001 format(a22) + + data0=0 + call wqencode(msg,ntype0,data0) !Source encoding +! write(*,1002) data0(1:7) +!1002 format('Source-encoded message (50 bits, hex):',7z3.2) +! data0(8:11)=0 + + call wqdecode(data0,msg2,ntype1) + +! write(*,1020) ntype1 +!1020 format('Message type: ',i7) +! write(*,1030) msg2 +!1030 format('Decoded message: ',a22) + + call unpk(data0,0,msg3) + do i=1,23 + if(ichar(msg3(i:i)).eq.0) then + msg3(i:)=" " + exit + endif + enddo + + err2=' ' + err3=' ' + if(msg2.ne.msg) err2='*' + if(msg3.ne.msg) err3='*' + + write(*,1040) msg,err2,msg2,err3,msg3 +1040 format(a22,1x,a1,1x,a22,1x,a1,1x,a22) + if(.not.lfile) exit + enddo +900 call unpk(data0,2,msg3) + + +999 end program test_wspr diff --git a/lib/wsprd/unpk.c b/lib/wsprd/unpk.c new file mode 100644 index 000000000..51207cb98 --- /dev/null +++ b/lib/wsprd/unpk.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "wsprd_utils.h" + +unsigned int nhash_( const void *key, size_t length, uint32_t initval); + +void unpk_(signed char message[], int *nhashtab, char call_loc_pow[]) +{ + int i,n1,n2,n3,ndbm,ihash,nadd,noprint,nh; + char callsign[13],grid[5],grid6[7],cdbm[3]; + static char hashtab[32768][13]; + FILE *fhash; + + if(*nhashtab==1) { + char line[80], hcall[12]; + if( (fhash=fopen("hashtable.txt","r+")) ) { + while (fgets(line, sizeof(line), fhash) != NULL) { + sscanf(line,"%d %s",&nh,hcall); + strcpy(*hashtab+nh*13,hcall); + } + } else { + fhash=fopen("hashtable.txt","w+"); + } + fclose(fhash); + return; + } + + if(*nhashtab==2) { + fhash=fopen("hashtable.txt","w"); + for (i=0; i<32768; i++) { + if( strncmp(hashtab[i],"\0",1) != 0 ) { + fprintf(fhash,"%5d %s\n",i,*hashtab+i*13); + } + } + fclose(fhash); + return; + } + + unpack50(message,&n1,&n2); + unpackcall(n1,callsign); + unpackgrid(n2, grid); + int ntype = (n2&127) - 64; + callsign[12]=0; + grid[4]=0; + +/* + Based on the value of ntype, decide whether this is a Type 1, 2, or + 3 message. + + * Type 1: 6 digit call, grid, power - ntype is positive and is a member + of the set {0,3,7,10,13,17,20...60} + + * Type 2: extended callsign, power - ntype is positive but not + a member of the set of allowed powers + + * Type 3: hash, 6 digit grid, power - ntype is negative. +*/ + + if( (ntype >= 0) && (ntype <= 62) ) { + int nu=ntype%10; + if( nu == 0 || nu == 3 || nu == 7 ) { + ndbm=ntype; + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,grid,4); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + ihash=nhash_(callsign,strlen(callsign),(uint32_t)146); + strcpy(*hashtab+ihash*13,callsign); + } else { + nadd=nu; + if( nu > 3 ) nadd=nu-3; + if( nu > 7 ) nadd=nu-7; + n3=n2/128+32768*(nadd-1); + unpackpfx(n3,callsign); + ndbm=ntype-nadd; + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + ihash=nhash_(callsign,strlen(callsign),(uint32_t)146); + strcpy(*hashtab+ihash*13,callsign); + noprint=0; + } + } else if ( ntype < 0 ) { + ndbm=-(ntype+1); + memset(grid6,0,sizeof(char)*7); + strncat(grid6,callsign+5,1); + strncat(grid6,callsign,5); + ihash=(n2-ntype-64)/128; + if( strncmp(hashtab[ihash],"\0",1) != 0 ) { + sprintf(callsign,"<%s>",hashtab[ihash]); + } else { + sprintf(callsign,"%5s","<...>"); + } + + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,grid6,strlen(grid6)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + + noprint=0; + +// I don't know what to do with these... They show up as "A000AA" grids. + if( ntype == -64 ) noprint=1; + } + // printf("\nUnpacked in C: %s\n",call_loc_pow); +} diff --git a/lib/wsprd/wsprd.c b/lib/wsprd/wsprd.c new file mode 100644 index 000000000..db0c29ecf --- /dev/null +++ b/lib/wsprd/wsprd.c @@ -0,0 +1,935 @@ +/* + This file is part of program wsprd, a detector/demodulator/decoder + for the Weak Signal Propagation Reporter (WSPR) mode. + + File name: wsprd.c + + Copyright 2001-2015, Joe Taylor, K1JT + + Much of the present code is based on work by Steven Franke, K9AN, + which in turn was based on earlier work by K1JT. + + Copyright 2014-2015, Steven Franke, K9AN + + License: GNU GPL v3 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fano.h" +#include "wsprd_utils.h" + +#define max(x,y) ((x) > (y) ? (x) : (y)) +// Possible PATIENCE options: FFTW_ESTIMATE, FFTW_ESTIMATE_PATIENT, +// FFTW_MEASURE, FFTW_PATIENT, FFTW_EXHAUSTIVE +#define PATIENCE FFTW_ESTIMATE +fftw_plan PLAN1,PLAN2,PLAN3; + +unsigned char pr3[162]= +{1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, + 0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, + 0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, + 1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, + 0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, + 0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, + 0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, + 0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, + 0,0}; + +unsigned long nr; + +//*************************************************************************** +unsigned long readc2file(char *ptr_to_infile, double *idat, double *qdat, + double *freq, int *wspr_type) +{ + float buffer[2*65536]; + double dfreq; + int i,ntrmin; + char *c2file[15]; + FILE* fp; + + fp = fopen(ptr_to_infile,"rb"); + if (fp == NULL) { + fprintf(stderr, "Cannot open data file '%s'\n", ptr_to_infile); + return 1; + } + unsigned long nread=fread(c2file,sizeof(char),14,fp); + nread=fread(&ntrmin,sizeof(int),1,fp); + nread=fread(&dfreq,sizeof(double),1,fp); + *freq=dfreq; + nread=fread(buffer,sizeof(float),2*45000,fp); + + *wspr_type=ntrmin; + + for(i=0; i<45000; i++) { + idat[i]=buffer[2*i]; + qdat[i]=-buffer[2*i+1]; + } + + if( nread == 2*45000 ) { + return nread/2; + } else { + return 1; + } +} + +//*************************************************************************** +unsigned long readwavfile(char *ptr_to_infile, int ntrmin, double *idat, double *qdat ) +{ + unsigned long i, j, npoints; + int nfft1, nfft2, nh2, i0; + double df; + + nfft2=46080; //this is the number of downsampled points that will be returned + nh2=nfft2/2; + + if( ntrmin == 2 ) { + nfft1=nfft2*32; //need to downsample by a factor of 32 + df=12000.0/nfft1; + i0=1500.0/df+0.5; + npoints=114*12000; + } else if ( ntrmin == 15 ) { + nfft1=nfft2*8*32; + df=12000.0/nfft1; + i0=(1500.0+112.5)/df+0.5; + npoints=8*114*12000; + } else { + fprintf(stderr,"This should not happen\n"); + return 1; + } + + double *realin; + fftw_complex *fftin, *fftout; + + FILE *fp; + short int *buf2; + buf2 = malloc(npoints*sizeof(short int)); + + fp = fopen(ptr_to_infile,"rb"); + if (fp == NULL) { + fprintf(stderr, "Cannot open data file '%s'\n", ptr_to_infile); + return 1; + } + nr=fread(buf2,2,22,fp); //Read and ignore header + nr=fread(buf2,2,npoints,fp); //Read raw data + fclose(fp); + + realin=(double*) fftw_malloc(sizeof(double)*nfft1); + fftout=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*nfft1); + PLAN1 = fftw_plan_dft_r2c_1d(nfft1, realin, fftout, PATIENCE); + + for (i=0; inh2 ) j=j-nfft2; + fftin[i][0]=fftout[j][0]; + fftin[i][1]=fftout[j][1]; + } + + fftw_free(fftout); + fftout=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*nfft2); + PLAN2 = fftw_plan_dft_1d(nfft2, fftin, fftout, FFTW_BACKWARD, PATIENCE); + fftw_execute(PLAN2); + + for (i=0; i0) & (k syncmax ) { //Save best parameters + syncmax=ss/totp; + best_shift=lag; + fbest=f0; + } + } // lag loop + } //freq loop + + if( mode <=1 ) { //Send best params back to caller + *sync=syncmax; + *shift1=best_shift; + *f1=fbest; + return; + } + + if( mode == 2 ) { + *sync=syncmax; + for (i=0; i<162; i++) { //Normalize the soft symbols + fsum=fsum+fsymb[i]/162.0; + f2sum=f2sum+fsymb[i]*fsymb[i]/162.0; + } + fac=sqrt(f2sum-fsum*fsum); + for (i=0; i<162; i++) { + fsymb[i]=symfac*fsymb[i]/fac; + if( fsymb[i] > 127) fsymb[i]=127.0; + if( fsymb[i] < -128 ) fsymb[i]=-128.0; + symbols[i]=fsymb[i] + 128; + } + return; + } + return; +} + +//*************************************************************************** +void usage(void) +{ + printf("Usage: wsprd [options...] infile\n"); + printf(" infile must have suffix .wav or .c2\n"); + printf("\n"); + printf("Options:\n"); + printf(" -a path to writeable data files, default=\".\"\n"); + printf(" -e x (x is transceiver dial frequency error in Hz)\n"); + printf(" -f x (x is transceiver dial frequency in MHz)\n"); + // blanking is not yet implemented. The options are accepted for compatibility + // with development version of wsprd. + // printf(" -t n (n is blanking duration in milliseconds)\n"); + // printf(" -b n (n is pct of time that is blanked)\n"); + printf(" -H do not use (or update) the hash table\n"); + printf(" -m decode wspr-15 .wav file\n"); + printf(" -n write noise estimates to file noise.dat\n"); + printf(" -q quick mode - doesn't dig deep for weak signals\n"); + printf(" -s slow mode - much slower, yields a few more decodes\n"); + printf(" -v verbose mode\n"); + printf(" -w wideband mode - decode signals within +/- 150 Hz of center\n"); + printf(" -z x (x is fano metric table bias, default is 0.42)\n"); +} + +//*************************************************************************** +int main(int argc, char *argv[]) +{ + extern char *optarg; + extern int optind; + int i,j,k; + unsigned char *symbols, *decdata; + signed char message[]={-9,13,-35,123,57,-39,64,0,0,0,0}; + char *callsign,*grid,*grid6, *call_loc_pow, *cdbm; + char *ptr_to_infile,*ptr_to_infile_suffix; + char *data_dir=NULL; + char wisdom_fname[200],all_fname[200],spots_fname[200]; + char timer_fname[200],hash_fname[200]; + char uttime[5],date[7]; + int c,delta,maxpts=65536,verbose=0,quickmode=0,writenoise=0,usehashtable=1,wspr_type=2; + int shift1, lagmin, lagmax, lagstep, worth_a_try, not_decoded; + unsigned int nbits; + unsigned int npoints, metric, maxcycles, cycles, maxnp; + float df=375.0/256.0/2; + float freq0[200],snr0[200],drift0[200],sync0[200]; + int shift0[200]; + float dt=1.0/375.0, dt_print; + double dialfreq_cmdline=0.0, dialfreq, freq_print; + float dialfreq_error=0.0; + float fmin=-110, fmax=110; + float f1, fstep, sync1, drift1, tblank=0, fblank=0; + double *idat, *qdat; + clock_t t0,t00; + double tfano=0.0,treadwav=0.0,tcandidates=0.0,tsync0=0.0; + double tsync1=0.0,tsync2=0.0,ttotal=0.0; + + // Parameters used for performance-tuning: + maxcycles=10000; //Fano timeout limit + double minsync1=0.10; //First sync limit + double minsync2=0.12; //Second sync limit + int iifac=3; //Step size in final DT peakup + int symfac=50; //Soft-symbol normalizing factor + int maxdrift=4; //Maximum (+/-) drift + double minrms=52.0 * (symfac/64.0); //Final test for plausible decoding + delta=60; //Fano threshold step + + t00=clock(); + fftw_complex *fftin, *fftout; +#include "./metric_tables.c" + + int mettab[2][256]; + float bias=0.42; + + idat=malloc(sizeof(double)*maxpts); + qdat=malloc(sizeof(double)*maxpts); + + while ( (c = getopt(argc, argv, "a:b:e:f:Hmnqst:wvz:")) !=-1 ) { + switch (c) { + case 'a': + data_dir = optarg; + break; + case 'b': + fblank = strtof(optarg,NULL); + break; + case 'e': + dialfreq_error = strtof(optarg,NULL); // units of Hz + // dialfreq_error = dial reading - actual, correct frequency + break; + case 'f': + dialfreq_cmdline = strtod(optarg,NULL); // units of MHz + break; + case 'H': + usehashtable = 0; + break; + case 'm': + wspr_type = 15; + break; + case 'n': + writenoise = 1; + break; + case 'q': + quickmode = 1; + break; + case 's': + maxcycles=20000; + iifac=1; + break; + case 't': + tblank = strtof(optarg,NULL); + break; + case 'v': + verbose = 1; + break; + case 'w': + fmin=-150.0; + fmax=150.0; + break; + case 'z': + bias=strtof(optarg,NULL); //fano metric bias (default is 0.42) + break; + case '?': + usage(); + return 1; + } + } + + if( optind+1 > argc) { + usage(); + return 1; + } else { + ptr_to_infile=argv[optind]; + } + + // setup metric table + for(i=0; i<256; i++) { + mettab[0][i]=round( 10*(metric_tables[2][i]-bias) ); + mettab[1][i]=round( 10*(metric_tables[2][255-i]-bias) ); + } + + FILE *fp_fftw_wisdom_file, *fall_wspr, *fwsprd, *fhash, *ftimer; + strcpy(wisdom_fname,"."); + strcpy(all_fname,"."); + strcpy(spots_fname,"."); + strcpy(timer_fname,"."); + strcpy(hash_fname,"."); + if(data_dir != NULL) { + strcpy(wisdom_fname,data_dir); + strcpy(all_fname,data_dir); + strcpy(spots_fname,data_dir); + strcpy(timer_fname,data_dir); + strcpy(hash_fname,data_dir); + } + strncat(wisdom_fname,"/wspr_wisdom.dat",20); + strncat(all_fname,"/ALL_WSPR.TXT",20); + strncat(spots_fname,"/wspr_spots.txt",20); + strncat(timer_fname,"/wspr_timer.out",20); + strncat(hash_fname,"/hashtable.txt",20); + if ((fp_fftw_wisdom_file = fopen(wisdom_fname, "r"))) { //Open FFTW wisdom + fftw_import_wisdom_from_file(fp_fftw_wisdom_file); + fclose(fp_fftw_wisdom_file); + } + + fall_wspr=fopen(all_fname,"a"); + fwsprd=fopen(spots_fname,"w"); + // FILE *fdiag; + // fdiag=fopen("wsprd_diag","a"); + + if((ftimer=fopen(timer_fname,"r"))) { + //Accumulate timing data + nr=fscanf(ftimer,"%lf %lf %lf %lf %lf %lf %lf", + &treadwav,&tcandidates,&tsync0,&tsync1,&tsync2,&tfano,&ttotal); + fclose(ftimer); + } + ftimer=fopen(timer_fname,"w"); + + if( strstr(ptr_to_infile,".wav") ) { + ptr_to_infile_suffix=strstr(ptr_to_infile,".wav"); + + t0 = clock(); + npoints=readwavfile(ptr_to_infile, wspr_type, idat, qdat); + treadwav += (double)(clock()-t0)/CLOCKS_PER_SEC; + + if( npoints == 1 ) { + return 1; + } + dialfreq=dialfreq_cmdline - (dialfreq_error*1.0e-06); + } else if ( strstr(ptr_to_infile,".c2") !=0 ) { + ptr_to_infile_suffix=strstr(ptr_to_infile,".c2"); + npoints=readc2file(ptr_to_infile, idat, qdat, &dialfreq, &wspr_type); + if( npoints == 1 ) { + return 1; + } + dialfreq -= (dialfreq_error*1.0e-06); + } else { + printf("Error: Failed to open %s\n",ptr_to_infile); + printf("WSPR file must have suffix .wav or .c2\n"); + return 1; + } + + // Parse date and time from given filename + strncpy(date,ptr_to_infile_suffix-11,6); + strncpy(uttime,ptr_to_infile_suffix-4,4); + date[6]='\0'; + uttime[4]='\0'; + + // Do windowed ffts over 2 symbols, stepped by half symbols + int nffts=4*floor(npoints/512)-1; + fftin=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*512); + fftout=(fftw_complex*) fftw_malloc(sizeof(fftw_complex)*512); + PLAN3 = fftw_plan_dft_1d(512, fftin, fftout, FFTW_FORWARD, PATIENCE); + + float ps[512][nffts]; + float w[512]; + for(i=0; i<512; i++) { + w[i]=sin(0.006147931*i); + } + + memset(ps,0.0, sizeof(float)*512*nffts); + for (i=0; i511 ) + k=k-512; + ps[j][i]=fftout[k][0]*fftout[k][0]+fftout[k][1]*fftout[k][1]; + } + } + + fftw_free(fftin); + fftw_free(fftout); + + // Compute average spectrum + float psavg[512]; + memset(psavg,0.0, sizeof(float)*512); + for (i=0; ismspec[j-1]) && (smspec[j]>smspec[j+1]) && (npk<200)) { + freq0[npk]=(j-205)*df; + snr0[npk]=10*log10(smspec[j])-snr_scaling_factor; + npk++; + } + } + + // Compute corrected fmin, fmax, accounting for dial frequency error + fmin += dialfreq_error; // dialfreq_error is in units of Hz + fmax += dialfreq_error; + + // Don't waste time on signals outside of the range [fmin,fmax]. + i=0; + for( j=0; j= fmin && freq0[j] <= fmax ) { + freq0[i]=freq0[j]; + snr0[i]=snr0[j]; + i++; + } + } + npk=i; + + t0=clock(); + /* Make coarse estimates of shift (DT), freq, and drift + + * Look for time offsets up to +/- 8 symbols (about +/- 5.4 s) relative + to nominal start time, which is 2 seconds into the file + + * Calculates shift relative to the beginning of the file + + * Negative shifts mean that signal started before start of file + + * The program prints DT = shift-2 s + + * Shifts that cause sync vector to fall off of either end of the data + vector are accommodated by "partial decoding", such that missing + symbols produce a soft-decision symbol value of 128 + + * The frequency drift model is linear, deviation of +/- drift/2 over the + span of 162 symbols, with deviation equal to 0 at the center of the + signal vector. + */ + + int idrift,ifr,if0,ifd,k0; + int kindex; + float smax,ss,pow,p0,p1,p2,p3; + for(j=0; j smax ) { //Save coarse parameters + smax=sync1; + shift0[j]=128*(k0+1); + drift0[j]=idrift; + freq0[j]=(ifr-256)*df; + sync0[j]=sync1; + } + } + } + } + } + tcandidates += (double)(clock()-t0)/CLOCKS_PER_SEC; + + nbits=81; + symbols=malloc(sizeof(char)*nbits*2); + memset(symbols,0,sizeof(char)*nbits*2); + decdata=malloc((nbits+7)/8); + grid=malloc(sizeof(char)*5); + grid6=malloc(sizeof(char)*7); + callsign=malloc(sizeof(char)*13); + call_loc_pow=malloc(sizeof(char)*23); + cdbm=malloc(sizeof(char)*3); + float allfreqs[npk]; + memset(allfreqs,0,sizeof(float)*npk); + char allcalls[npk][13]; + memset(allcalls,0,sizeof(char)*npk*13); + memset(grid,0,sizeof(char)*5); + memset(grid6,0,sizeof(char)*7); + memset(callsign,0,sizeof(char)*13); + memset(call_loc_pow,0,sizeof(char)*23); + memset(cdbm,0,sizeof(char)*3); + char hashtab[32768][13]; + memset(hashtab,0,sizeof(char)*32768*13); + uint32_t nhash_( const void *, size_t, uint32_t); + int nh; + + if( usehashtable ) { + char line[80], hcall[12]; + if( (fhash=fopen(hash_fname,"r+")) ) { + while (fgets(line, sizeof(line), fhash) != NULL) { + sscanf(line,"%d %s",&nh,hcall); + strcpy(*hashtab+nh*13,hcall); + } + } else { + fhash=fopen(hash_fname,"w+"); + } + fclose(fhash); + } + + int uniques=0, noprint=0; + /* + Refine the estimates of freq, shift using sync as a metric. + Sync is calculated such that it is a float taking values in the range + [0.0,1.0]. + + Function sync_and_demodulate has three modes of operation + mode is the last argument: + + 0 = no frequency or drift search. find best time lag. + 1 = no time lag or drift search. find best frequency. + 2 = no frequency or time lag search. Calculate soft-decision + symbols using passed frequency and shift. + + NB: best possibility for OpenMP may be here: several worker threads + could each work on one candidate at a time. + */ + + for (j=0; j minsync1 ) { + worth_a_try = 1; + } else { + worth_a_try = 0; + } + + int idt=0, ii=0, jiggered_shift; + double y,sq,rms; + not_decoded=1; + + while ( worth_a_try && not_decoded && idt<=(128/iifac)) { + ii=(idt+1)/2; + if( idt%2 == 1 ) ii=-ii; + ii=iifac*ii; + jiggered_shift=shift1+ii; + + // Use mode 2 to get soft-decision symbols + t0 = clock(); + sync_and_demodulate(idat, qdat, npoints, symbols, &f1, fstep, + &jiggered_shift, lagmin, lagmax, lagstep, &drift1, symfac, + &sync1, 2); + tsync2 += (double)(clock()-t0)/CLOCKS_PER_SEC; + + sq=0.0; + for(i=0; i<162; i++) { + y=(double)symbols[i] - 128.0; + sq += y*y; + } + rms=sqrt(sq/162.0); + + if((sync1 > minsync2) && (rms > minrms)) { + deinterleave(symbols); + t0 = clock(); + not_decoded = fano(&metric,&cycles,&maxnp,decdata,symbols,nbits, + mettab,delta,maxcycles); + tfano += (double)(clock()-t0)/CLOCKS_PER_SEC; + + /* ### Used for timing tests: + if(not_decoded) fprintf(fdiag, + "%6s %4s %4.1f %3.0f %4.1f %10.7f %-18s %2d %5u %4d %6.1f %2d\n", + date,uttime,sync1*10,snr0[j], shift1*dt-2.0, dialfreq+(1500+f1)/1e6, + "@ ", (int)drift1, cycles/81, ii, rms, maxnp); + */ + } + idt++; + if( quickmode ) break; + } + + if( worth_a_try && !not_decoded ) { + for(i=0; i<11; i++) { + if( decdata[i]>127 ) { + message[i]=decdata[i]-256; + } else { + message[i]=decdata[i]; + } + } + + // Unpack the decoded message, update the hashtable, apply + // sanity checks on grid and power, and return + // call_loc_pow string and also callsign (for de-duping). + noprint=unpk_(message,hashtab,call_loc_pow,callsign); + + // Remove dupes (same callsign and freq within 1 Hz) + int dupe=0; + for (i=0; i\n"); + + if ((fp_fftw_wisdom_file = fopen(wisdom_fname, "w"))) { + fftw_export_wisdom_to_file(fp_fftw_wisdom_file); + fclose(fp_fftw_wisdom_file); + } + + ttotal += (double)(clock()-t00)/CLOCKS_PER_SEC; + + fprintf(ftimer,"%7.2f %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n\n", + treadwav,tcandidates,tsync0,tsync1,tsync2,tfano,ttotal); + + fprintf(ftimer,"Code segment Seconds Frac\n"); + fprintf(ftimer,"-----------------------------------\n"); + fprintf(ftimer,"readwavfile %7.2f %7.2f\n",treadwav,treadwav/ttotal); + fprintf(ftimer,"Coarse DT f0 f1 %7.2f %7.2f\n",tcandidates, + tcandidates/ttotal); + fprintf(ftimer,"sync_and_demod(0) %7.2f %7.2f\n",tsync0,tsync0/ttotal); + fprintf(ftimer,"sync_and_demod(1) %7.2f %7.2f\n",tsync1,tsync1/ttotal); + fprintf(ftimer,"sync_and_demod(2) %7.2f %7.2f\n",tsync2,tsync2/ttotal); + fprintf(ftimer,"Fano decoder %7.2f %7.2f\n",tfano,tfano/ttotal); + fprintf(ftimer,"-----------------------------------\n"); + fprintf(ftimer,"Total %7.2f %7.2f\n",ttotal,1.0); + + fclose(fall_wspr); + fclose(fwsprd); + // fclose(fdiag); + fclose(ftimer); + fftw_destroy_plan(PLAN1); + fftw_destroy_plan(PLAN2); + fftw_destroy_plan(PLAN3); + + if( usehashtable ) { + fhash=fopen(hash_fname,"w"); + for (i=0; i<32768; i++) { + if( strncmp(hashtab[i],"\0",1) != 0 ) { + fprintf(fhash,"%5d %s\n",i,*hashtab+i*13); + } + } + fclose(fhash); + } + if(fblank+tblank+writenoise == 999) return -1; //Silence compiler warning + return 0; +} diff --git a/lib/wsprd/wsprd_stats.txt b/lib/wsprd/wsprd_stats.txt new file mode 100644 index 000000000..0d925aa66 --- /dev/null +++ b/lib/wsprd/wsprd_stats.txt @@ -0,0 +1,24 @@ + Linux Windows +Program Time Decodes Time Decodes +------------------------------------------------- +wsprd (Mar 2013) 2413 1451 2718 1451 + +k9an-wsprd 1800 2122 +k9an_wsprd -q 354 1939 + +wsprd 399 2190 356 2190 +wsprd -q 214 2034 192 2034 + +wsprd* 1240 2215 +wsprd# 1599 2220 + +------------------------------------------------- +* maxcycles=30000 +# maxcycles=20000, iifac=1 +------------------------------------------------- +Test data: 638 *.wav files (recorded by WSJT-X) +------------------------------------------------- +Linux machine: Core 2 Duo, E6750 CPU +Windows machine: 4-Core i5-2500 CPU +wsprd git commit: eecc274 +------------------------------------------------- diff --git a/lib/wsprd/wsprd_utils.c b/lib/wsprd/wsprd_utils.c new file mode 100644 index 000000000..37f6b8351 --- /dev/null +++ b/lib/wsprd/wsprd_utils.c @@ -0,0 +1,322 @@ +/* + This file is part of program wsprd, a detector/demodulator/decoder + for the Weak Signal Propagation Reporter (WSPR) mode. + + File name: wsprd_utils.c + + Copyright 2001-2015, Joe Taylor, K1JT + + Most of the code is based on work by Steven Franke, K9AN, which + in turn was based on earlier work by K1JT. + + Copyright 2014-2015, Steven Franke, K9AN + + License: GNU GPL v3 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include "wsprd_utils.h" + +#ifndef int32_t +#define int32_t int +#endif + +void unpack50( signed char *dat, int32_t *n1, int32_t *n2 ) +{ + int32_t i,i4; + + i=dat[0]; + i4=i&255; + *n1=i4<<20; + + i=dat[1]; + i4=i&255; + *n1=*n1+(i4<<12); + + i=dat[2]; + i4=i&255; + *n1=*n1+(i4<<4); + + i=dat[3]; + i4=i&255; + *n1=*n1+((i4>>4)&15); + *n2=(i4&15)<<18; + + i=dat[4]; + i4=i&255; + *n2=*n2+(i4<<10); + + i=dat[5]; + i4=i&255; + *n2=*n2+(i4<<2); + + i=dat[6]; + i4=i&255; + *n2=*n2+((i4>>6)&3); +} + +void unpackcall( int32_t ncall, char *call ) +{ + char c[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E', + 'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', + 'U','V','W','X','Y','Z',' '}; + int32_t n; + int i; + char tmp[7]; + + n=ncall; + strcpy(call,"......"); + if (n < 262177560 ) { + i=n%27+10; + tmp[5]=c[i]; + n=n/27; + i=n%27+10; + tmp[4]=c[i]; + n=n/27; + i=n%27+10; + tmp[3]=c[i]; + n=n/27; + i=n%10; + tmp[2]=c[i]; + n=n/10; + i=n%36; + tmp[1]=c[i]; + n=n/36; + i=n; + tmp[0]=c[i]; + tmp[6]='\0'; + // remove leading whitespace + for(i=0; i<5; i++) { + if( tmp[i] != c[36] ) + break; + } + sprintf(call,"%-6s",&tmp[i]); + // remove trailing whitespace + for(i=0; i<6; i++) { + if( call[i] == c[36] ) { + call[i]='\0'; + } + } + } +} + +void unpackgrid( int32_t ngrid, char *grid) +{ + char c[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E', + 'F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T', + 'U','V','W','X','Y','Z',' '}; + int dlat, dlong; + + ngrid=ngrid>>7; + if( ngrid < 32400 ) { + dlat=(ngrid%180)-90; + dlong=(ngrid/180)*2 - 180 + 2; + if( dlong < -180 ) + dlong=dlong+360; + if( dlong > 180 ) + dlong=dlong+360; + int nlong = 60.0*(180.0-dlong)/5.0; + int n1 = nlong/240; + int n2 = (nlong - 240*n1)/24; + grid[0] = c[10+n1]; + grid[2]= c[n2]; + + int nlat = 60.0*(dlat+90)/2.5; + n1 = nlat/240; + n2 = (nlat-240*n1)/24; + grid[1]=c[10+n1]; + grid[3]=c[n2]; + } else { + strcpy(grid,"XXXX"); + } +} + +void unpackpfx( int32_t nprefix, char *call) +{ + char nc, pfx[4]="", tmpcall[7]=""; + int i; + int32_t n; + + strcpy(tmpcall,call); + + if( nprefix < 60000 ) { + // add a prefix of 1 to 3 characters + n=nprefix; + for (i=2; i>=0; i--) { + nc=n%37; + if( (nc >= 0) & (nc <= 9) ) { + pfx[i]=nc+48; + } + else if( (nc >= 10) & (nc <= 35) ) { + pfx[i]=nc+55; + } + else { + pfx[i]=' '; + } + n=n/37; + } + + strcpy(call,pfx); + strncat(call,"/",1); + strncat(call,tmpcall,strlen(tmpcall)); + + } else { + // add a suffix of 1 or 2 characters + nc=nprefix-60000; + if( (nc >= 0) & (nc <= 9) ) { + pfx[0]=nc+48; + strcpy(call,tmpcall); + strncat(call,"/",1); + strncat(call,pfx,1); + } + else if( (nc >= 10) & (nc <= 35) ) { + pfx[0]=nc+55; + strcpy(call,tmpcall); + strncat(call,"/",1); + strncat(call,pfx,1); + } + else if( (nc >= 36) & (nc <= 125) ) { + pfx[0]=(nc-26)/10+48; + pfx[1]=(nc-26)%10+48; + strcpy(call,tmpcall); + strncat(call,"/",1); + strncat(call,pfx,2); + } + } +} + +void deinterleave(unsigned char *sym) +{ + unsigned char tmp[162]; + unsigned char p, i, j; + + p=0; + i=0; + while (p<162) { + j=((i * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32; + if (j < 162 ) { + tmp[p]=sym[j]; + p=p+1; + } + i=i+1; + } + for (i=0; i<162; i++) { + sym[i]=tmp[i]; + } +} + +// used by qsort +int floatcomp(const void* elem1, const void* elem2) +{ + if(*(const float*)elem1 < *(const float*)elem2) + return -1; + return *(const float*)elem1 > *(const float*)elem2; +} + +int unpk_(signed char *message, char hashtab[32768][13], char *call_loc_pow, char *callsign) +{ + int n1,n2,n3,ndbm,ihash,nadd,noprint=0; + char grid[5],grid6[7],cdbm[3]; + + unpack50(message,&n1,&n2); + unpackcall(n1,callsign); + unpackgrid(n2, grid); + int ntype = (n2&127) - 64; + callsign[12]=0; + grid[4]=0; + + /* + Based on the value of ntype, decide whether this is a Type 1, 2, or + 3 message. + + * Type 1: 6 digit call, grid, power - ntype is positive and is a member + of the set {0,3,7,10,13,17,20...60} + + * Type 2: extended callsign, power - ntype is positive but not + a member of the set of allowed powers + + * Type 3: hash, 6 digit grid, power - ntype is negative. + */ + + if( (ntype >= 0) && (ntype <= 62) ) { + int nu=ntype%10; + if( nu == 0 || nu == 3 || nu == 7 ) { + ndbm=ntype; + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,grid,4); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + ihash=nhash_(callsign,strlen(callsign),(uint32_t)146); + strcpy(*hashtab+ihash*13,callsign); + } else { + nadd=nu; + if( nu > 3 ) nadd=nu-3; + if( nu > 7 ) nadd=nu-7; + n3=n2/128+32768*(nadd-1); + unpackpfx(n3,callsign); + ndbm=ntype-nadd; + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + int nu=ndbm%10; + if( nu == 0 || nu == 3 || nu == 7 || nu == 10 ) { //make sure power is OK + ihash=nhash_(callsign,strlen(callsign),(uint32_t)146); + strcpy(*hashtab+ihash*13,callsign); + } else noprint=1; + } + } else if ( ntype < 0 ) { + ndbm=-(ntype+1); + memset(grid6,0,sizeof(char)*7); + strncat(grid6,callsign+5,1); + strncat(grid6,callsign,5); + int nu=ndbm%10; + if( (nu == 0 || nu == 3 || nu == 7 || nu == 10) && \ + (isalpha(grid6[0]) && isalpha(grid6[1]) && \ + isdigit(grid6[2]) && isdigit(grid6[3]) ) ) { + // not testing 4'th and 5'th chars because of this case: JO33 40 + // grid is only 4 chars even though this is a hashed callsign... + // isalpha(grid6[4]) && isalpha(grid6[5]) ) ) { + ihash=nhash_(callsign,strlen(callsign),(uint32_t)146); + strcpy(*hashtab+ihash*13,callsign); + } else noprint=1; + + ihash=(n2-ntype-64)/128; + if( strncmp(hashtab[ihash],"\0",1) != 0 ) { + sprintf(callsign,"<%s>",hashtab[ihash]); + } else { + sprintf(callsign,"%5s","<...>"); + } + + memset(call_loc_pow,0,sizeof(char)*23); + sprintf(cdbm,"%2d",ndbm); + strncat(call_loc_pow,callsign,strlen(callsign)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,grid6,strlen(grid6)); + strncat(call_loc_pow," ",1); + strncat(call_loc_pow,cdbm,2); + strncat(call_loc_pow,"\0",1); + + + // I don't know what to do with these... They show up as "A000AA" grids. + if( ntype == -64 ) noprint=1; + } + return noprint; +} diff --git a/lib/wsprd/wsprd_utils.h b/lib/wsprd/wsprd_utils.h new file mode 100644 index 000000000..b24e9044d --- /dev/null +++ b/lib/wsprd/wsprd_utils.h @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include +#include +#include + +void unpack50( signed char *dat, int32_t *n1, int32_t *n2 ); + +void unpackcall( int32_t ncall, char *call ); + +void unpackgrid( int32_t ngrid, char *grid); + +void unpackpfx( int32_t nprefix, char *call); + +void deinterleave(unsigned char *sym); + +// used by qsort +int floatcomp(const void* elem1, const void* elem2); + +unsigned int nhash_( const void *key, size_t length, uint32_t initval); + +int unpk_( signed char *message, char hashtab[32768][13], char *call_loc_pow, char *callsign); \ No newline at end of file diff --git a/mainwindow.cpp b/mainwindow.cpp index 32718a1f8..5bdda3d82 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -111,6 +111,7 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme m_bandEdited {false}, m_splitMode {false}, m_monitoring {false}, + m_transmitting {false}, m_tune {false}, m_lastMonitoredFrequency {default_frequency}, @@ -222,6 +223,8 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme ui->actionJT65->setActionGroup(modeGroup); ui->actionJT9_JT65->setActionGroup(modeGroup); ui->actionJT4->setActionGroup(modeGroup); + ui->actionWSPR_2->setActionGroup(modeGroup); + ui->actionWSPR_15->setActionGroup(modeGroup); QActionGroup* saveGroup = new QActionGroup(this); ui->actionNone->setActionGroup(saveGroup); @@ -257,14 +260,17 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme setWindowTitle (program_title ()); createStatusBar(); - connect(&proc_jt9, SIGNAL(readyReadStandardOutput()), - this, SLOT(readFromStdout())); + connect(&proc_jt9, SIGNAL(readyReadStandardOutput()),this, SLOT(readFromStdout())); + connect(&proc_jt9, SIGNAL(error(QProcess::ProcessError)),this, SLOT(jt9_error(QProcess::ProcessError))); + connect(&proc_jt9, SIGNAL(readyReadStandardError()),this, SLOT(readFromStderr())); - connect(&proc_jt9, SIGNAL(error(QProcess::ProcessError)), - this, SLOT(jt9_error(QProcess::ProcessError))); + connect(&p1, SIGNAL(readyReadStandardOutput()),this, SLOT(p1ReadFromStdout())); + connect(&p1, SIGNAL(error(QProcess::ProcessError)),this, SLOT(p1Error(QProcess::ProcessError))); + connect(&p1, SIGNAL(readyReadStandardError()),this, SLOT(p1ReadFromStderr())); - connect(&proc_jt9, SIGNAL(readyReadStandardError()), - this, SLOT(readFromStderr())); +// connect(&p3, SIGNAL(readyReadStandardOutput()),this, SLOT(p3ReadFromStdout())); + connect(&p3, SIGNAL(error(QProcess::ProcessError)),this, SLOT(p3Error(QProcess::ProcessError))); + connect(&p3, SIGNAL(readyReadStandardError()),this, SLOT(p3ReadFromStderr())); // Hook up working frequencies. ui->bandComboBox->setModel (m_config.frequencies ()); @@ -321,7 +327,7 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme ui->readFreq->setFont(font); connect(&m_guiTimer, &QTimer::timeout, this, &MainWindow::guiUpdate); - m_guiTimer.start(100); //Don't change the 100 ms! + m_guiTimer.start(100); //### Don't change the 100 ms! ### ptt0Timer = new QTimer(this); ptt0Timer->setSingleShot(true); @@ -338,10 +344,18 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme tuneButtonTimer->setSingleShot(true); connect(tuneButtonTimer, &QTimer::timeout, this, &MainWindow::on_stopTxButton_clicked); + tuneATU_Timer= new QTimer(this); + tuneATU_Timer->setSingleShot(true); + connect(tuneATU_Timer, &QTimer::timeout, this, &MainWindow::stopTuneATU); + killFileTimer = new QTimer(this); killFileTimer->setSingleShot(true); connect(killFileTimer, &QTimer::timeout, this, &MainWindow::killFile); + uploadTimer = new QTimer(this); + uploadTimer->setSingleShot(true); + connect(uploadTimer, SIGNAL(timeout()), this, SLOT(uploadSpots())); + m_auto=false; m_waterfallAvg = 1; m_txFirst=false; @@ -350,6 +364,15 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme m_killAll=false; m_widebandDecode=false; m_ntx=1; + + m_nrx=1; + m_tx=0; + m_txNext=false; + m_uploading=false; + m_grid6=false; + m_nseq=0; + m_ntr=0; + m_loopall=false; m_startAnother=false; m_saveDecoded=false; @@ -382,17 +405,47 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme m_MinW=0; m_nSubMode=0; m_tol=500; - m_DTtol=0.2; + m_DTtol=0.5; m_wideGraph->setTol(m_tol); m_bShMsgs=false; m_bDopplerTracking0=false; + m_uploading=false; + m_hopTest=false; + m_bTxTime=false; + m_band00=-1; + m_rxDone=false; + + m_fWSPR["160"]=1.8366; //WSPR frequencies + m_fWSPR["80"]=3.5926; + m_fWSPR["60"]=5.2872; + m_fWSPR["40"]=7.0386; + m_fWSPR["30"]=10.1387; + m_fWSPR["20"]=14.0956; + m_fWSPR["17"]=18.1046; + m_fWSPR["15"]=21.0946; + m_fWSPR["12"]=24.9246; + m_fWSPR["10"]=28.1246; signalMeter = new SignalMeter(ui->meterFrame); signalMeter->resize(50, 160); + for(int i=0; i<28; i++) { //Initialize dBm values + float dbm=(10.0*i)/3.0 - 30.0; + int ndbm=0; + if(dbm<0) ndbm=int(dbm-0.5); + if(dbm>=0) ndbm=int(dbm+0.5); + QString t; + t.sprintf("%d dBm",ndbm); + ui->TxPowerComboBox->addItem(t); + } + ui->labAz->setStyleSheet("border: 0px;"); ui->labDist->setStyleSheet("border: 0px;"); + auto t = "UTC dB DT Freq Message"; + ui->decodedTextLabel->setText(t); + ui->decodedTextLabel2->setText(t); + readSettings(); //Restore user's setup params // start the audio thread m_audioThread->start (m_audioThreadPriority); @@ -434,7 +487,7 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme "-s", QApplication::applicationName () // shared memory key, // includes rig-name #ifdef NDEBUG - , "-w", "2" //FFTW patience - release + , "-w", "1" //FFTW patience - release #else , "-w", "1" //FFTW patience - debug builds for speed #endif @@ -461,8 +514,8 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme genStdMsgs(m_rpt); m_ntx=6; ui->txrb6->setChecked(true); - if(m_mode!="JT9" and m_mode!="JT9W-1" and m_mode!="JT65" and - m_mode!="JT9+JT65" and m_mode!="JT4") m_mode="JT9"; + if(m_mode!="JT9" and m_mode!="JT9W-1" and m_mode!="JT65" and m_mode!="JT9+JT65" and + m_mode!="JT4" and m_mode!="WSPR-2" and m_mode!="WSPR-15") m_mode="JT9"; on_actionWide_Waterfall_triggered(); //### connect(m_wideGraph.data (), SIGNAL(setFreq3(int,int)),this, @@ -473,6 +526,8 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme if(m_mode=="JT9W-1") on_actionJT9W_1_triggered(); if(m_mode=="JT65") on_actionJT65_triggered(); if(m_mode=="JT9+JT65") on_actionJT9_JT65_triggered(); + if(m_mode=="WSPR-2") on_actionWSPR_2_triggered(); + if(m_mode=="WSPR-15") on_actionWSPR_15_triggered(); m_wideGraph->setLockTxFreq(m_lockTxFreq); m_wideGraph->setMode(m_mode); @@ -488,13 +543,8 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme Q_EMIT startAudioInputStream (m_config.audio_input_device (), m_framesAudioInputBuffered, &m_detector, m_downSampleFactor, m_config.audio_input_channel ()); Q_EMIT initializeAudioOutputStream (m_config.audio_output_device (), AudioDevice::Mono == m_config.audio_output_channel () ? 1 : 2, m_msAudioOutputBuffered); - Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); - auto t = "UTC dB DT Freq Message"; - ui->decodedTextLabel->setText(t); - ui->decodedTextLabel2->setText(t); - enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook ui->label_9->setStyleSheet("QLabel{background-color: #aabec8}"); @@ -516,8 +566,29 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme bool b=m_config.enable_VHF_features() and (m_mode=="JT4" or m_mode=="JT65"); VHF_controls_visible(b); - m_hsymStop=173; - if(m_config.decode_at_52s()) m_hsymStop=181; + m_ntx=1; + ui->txrb1->setChecked(true); + + if(m_mode.mid(0,4)=="WSPR" and m_pctx>0) { + QPalette* palette = new QPalette(); + palette->setColor(QPalette::Base,Qt::yellow); + ui->sbTxPercent->setPalette(*palette); + delete palette; + } + if(m_mode=="WSPR-2") { + m_hsymStop=396; + } else if(m_mode=="WSPR-15") { + m_hsymStop=3090; + } else { + m_hsymStop=173; + if(m_config.decode_at_52s()) m_hsymStop=181; + } + m_modulator.setPeriod(60); + m_dialFreqRxWSPR=0; + wsprNet = new WSPRNet(this); + connect( wsprNet, SIGNAL(uploadStatus(QString)), this, SLOT(uploadResponse(QString))); + +//### Remove this stuff! #if !WSJT_ENABLE_EXPERIMENTAL_FEATURES ui->actionJT9W_1->setEnabled (false); #endif @@ -557,10 +628,12 @@ void MainWindow::writeSettings() m_settings->setValue("NDepth",m_ndepth); m_settings->setValue("RxFreq",ui->RxFreqSpinBox->value()); m_settings->setValue("TxFreq",ui->TxFreqSpinBox->value()); + m_settings->setValue("WSPRfreq",ui->WSPRfreqSpinBox->value()); m_settings->setValue("minW",ui->sbMinW->value()); m_settings->setValue("SubMode",ui->sbSubmode->value()); m_settings->setValue("DTtol",m_DTtol); m_settings->setValue("Ftol",ui->sbTol->value()); + m_settings->setValue("MinSync",m_minSync); m_settings->setValue("EME",m_bEME); m_settings->setValue ("DialFreq", QVariant::fromValue(m_lastMonitoredFrequency)); m_settings->setValue("InGain",m_inGain); @@ -570,7 +643,16 @@ void MainWindow::writeSettings() m_settings->setValue("OutBufSize",outBufSize); m_settings->setValue("LockTxFreq",m_lockTxFreq); m_settings->setValue("Plus2kHz",m_plus2kHz); - + m_settings->setValue("PctTx",m_pctx); + m_settings->setValue("dBm",m_dBm); + m_settings->setValue("UploadSpots",m_uploadSpots); + m_settings->setValue("BandHopping",m_bandHopping); + m_settings->setValue("SunriseBands",ui->sunriseBands->text()); + m_settings->setValue("DayBands",ui->dayBands->text()); + m_settings->setValue("SunsetBands",ui->sunsetBands->text()); + m_settings->setValue("NightBands",ui->nightBands->text()); + m_settings->setValue("TuneBands",ui->tuneBands->text()); + m_settings->setValue("GrayLineDuration",ui->graylineDuration->text()); m_settings->endGroup(); } @@ -595,31 +677,32 @@ void MainWindow::readSettings() if (displayAstro) on_actionAstronomical_data_triggered (); if (displayMsgAvg) on_actionMessage_averaging_triggered(); m_settings->beginGroup("Common"); - morse_(const_cast (m_config.my_callsign ().toLatin1().constData()) - , const_cast (icw) - , &m_ncw - , m_config.my_callsign ().length()); + morse_(const_cast (m_config.my_callsign ().toLatin1().constData()), + const_cast (icw), &m_ncw, m_config.my_callsign ().length()); m_mode=m_settings->value("Mode","JT9").toString(); m_modeTx=m_settings->value("ModeTx","JT9").toString(); if(m_modeTx.mid(0,3)=="JT9") ui->pbTxMode->setText("Tx JT9 @"); if(m_modeTx=="JT65") ui->pbTxMode->setText("Tx JT65 #"); ui->actionNone->setChecked(m_settings->value("SaveNone",true).toBool()); - ui->actionSave_decoded->setChecked(m_settings->value( - "SaveDecoded",false).toBool()); + ui->actionSave_decoded->setChecked(m_settings->value("SaveDecoded",false).toBool()); ui->actionSave_all->setChecked(m_settings->value("SaveAll",false).toBool()); ui->RxFreqSpinBox->setValue(0); // ensure a change is signaled ui->RxFreqSpinBox->setValue(m_settings->value("RxFreq",1500).toInt()); m_nSubMode=m_settings->value("SubMode",0).toInt(); ui->sbSubmode->setValue(m_nSubMode); ui->sbMinW->setMaximum(m_nSubMode); - m_DTtol=m_settings->value("DTtol",0.2).toFloat(); + m_DTtol=m_settings->value("DTtol",0.5).toFloat(); ui->sbDT->setValue(m_DTtol); ui->sbTol->setValue(m_settings->value("Ftol",4).toInt()); + ui->syncSpinBox->setValue(m_settings->value("MinSync",0).toInt()); m_bEME=m_settings->value("EME",false).toBool(); ui->cbEME->setChecked(m_bEME); m_MinW=m_settings->value("minW",0).toInt(); ui->sbMinW->setValue(m_MinW); - m_lastMonitoredFrequency = m_settings->value ("DialFreq", QVariant::fromValue (default_frequency)).value (); + m_lastMonitoredFrequency = m_settings->value ("DialFreq", + QVariant::fromValue (default_frequency)).value (); + ui->WSPRfreqSpinBox->setValue(0); // ensure a change is signaled + ui->WSPRfreqSpinBox->setValue(m_settings->value("WSPRfreq",1500).toInt()); ui->TxFreqSpinBox->setValue(0); // ensure a change is signaled ui->TxFreqSpinBox->setValue(m_settings->value("TxFreq",1500).toInt()); Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); @@ -628,7 +711,17 @@ void MainWindow::readSettings() m_ndepth=m_settings->value("NDepth",3).toInt(); m_inGain=m_settings->value("InGain",0).toInt(); ui->inGain->setValue(m_inGain); - + m_pctx=m_settings->value("PctTx",20).toInt(); + m_rxavg=1.0; + if(m_pctx>0) m_rxavg=100.0/m_pctx - 1.0; //Average # of Rx's per Tx + ui->sbTxPercent->setValue(m_pctx); + m_dBm=m_settings->value("dBm",37).toInt(); + ui->TxPowerComboBox->setCurrentIndex(int(0.3*(m_dBm + 30.0)+0.2)); + m_uploadSpots=m_settings->value("UploadSpots",false).toBool(); + ui->cbUploadWSPR_Spots->setChecked(m_uploadSpots); + if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet("QCheckBox{background-color: yellow}"); + m_bandHopping=m_settings->value("BandHopping",false).toBool(); + ui->cbBandHop->setChecked(m_bandHopping); // setup initial value of tx attenuator ui->outAttenuation->setValue (m_settings->value ("OutAttenuation", 0).toInt ()); on_outAttenuation_valueChanged (ui->outAttenuation->value ()); @@ -641,6 +734,18 @@ void MainWindow::readSettings() ui->cbTxLock->setChecked(m_lockTxFreq); m_plus2kHz=m_settings->value("Plus2kHz",false).toBool(); ui->cbPlus2kHz->setChecked(m_plus2kHz); + ui->sunriseBands->setText(m_settings->value("SunriseBands","").toString()); + on_sunriseBands_editingFinished(); + ui->dayBands->setText(m_settings->value("DayBands","").toString()); + on_dayBands_editingFinished(); + ui->sunsetBands->setText(m_settings->value("SunsetBands","").toString()); + on_sunsetBands_editingFinished(); + ui->nightBands->setText(m_settings->value("NightBands","").toString()); + on_nightBands_editingFinished(); + ui->tuneBands->setText(m_settings->value("TuneBands","").toString()); + on_tuneBands_editingFinished(); + ui->graylineDuration->setText(m_settings->value("GraylineDuration","").toString()); + on_graylineDuration_editingFinished(); m_settings->endGroup(); // use these initialisation settings to tune the audio o/p buffer @@ -692,7 +797,9 @@ void MainWindow::dataSink(qint64 frames) int k (frames); jt9com_.nfa=m_wideGraph->nStartFreq(); jt9com_.nfb=m_wideGraph->Fmax(); - symspec_(&k,&trmin,&m_nsps,&m_inGain,&px,s,&df3,&ihsym,&npts8); + int nsmo=m_wideGraph->smoothYellow()-1; + symspec_(&k,&trmin,&m_nsps,&m_inGain,&nsmo,&px,s,&df3,&ihsym,&npts8); + if(m_mode=="WSPR-2") wspr_downsample_(jt9com_.d2,&k); //### if(ihsym <=0) return; QString t; m_pctZap=nzap*100.0/m_nsps; @@ -702,17 +809,30 @@ void MainWindow::dataSink(qint64 frames) m_wideGraph->dataSink2(s,df3,ihsym,m_diskData); } + if(m_mode=="WSPR-2") { + m_hsymStop=396; + } else if(m_mode=="WSPR-15") { + m_hsymStop=3090; + } else { + m_hsymStop=173; + if(m_config.decode_at_52s()) m_hsymStop=181; + } + + if(ihsym==3*m_hsymStop/4) { + m_dialFreqRxWSPR=m_dialFreq; + } + if(ihsym == m_hsymStop) { + if( m_dialFreqRxWSPR==0) m_dialFreqRxWSPR=m_dialFreq; m_dataAvailable=true; jt9com_.npts8=(ihsym*m_nsps)/16; jt9com_.newdat=1; jt9com_.nagain=0; - if(!m_config.decode_at_52s()) m_hsymStop=173; - if(m_config.decode_at_52s()) m_hsymStop=181; jt9com_.nzhsym=m_hsymStop; QDateTime t = QDateTime::currentDateTimeUtc(); m_dateTime=t.toString("yyyy-MMM-dd hh:mm"); - decode(); //Start decoder + if(m_mode.mid(0,4)!="WSPR") decode(); //Start decoder + if(!m_diskData) { //Always save; may delete later int ihr=t.time().toString("hh").toInt(); int imin=t.time().toString("mm").toInt(); @@ -723,7 +843,43 @@ void MainWindow::dataSink(qint64 frames) "_" + t2 + ".wav"); *future2 = QtConcurrent::run(savewav, m_fname, m_TRperiod); watcher2->setFuture(*future2); + + if(m_mode.mid(0,4)=="WSPR") { + m_c2name=m_config.save_directory ().absoluteFilePath (t.date().toString("yyMMdd") + + "_" + t2 + ".c2"); + int len1=m_c2name.length(); + char c2name[80]; + strcpy(c2name,m_c2name.toLatin1()); + int nsec=120; + int nbfo=1500; + double f0m1500=m_dialFreq/1000000.0 + nbfo - 1500; + savec2_(c2name,&nsec,&f0m1500,len1); + } } + + if(m_mode.mid(0,4)=="WSPR") { + QString t2,cmnd; + double f0m1500=m_dialFreqRxWSPR/1000000.0; // + 0.000001*(m_BFO - 1500); + t2.sprintf(" -f %.6f ",f0m1500); + + if(m_diskData) { +// cmnd='"' + m_appDir + '"' + "/wsprd " + m_path; + cmnd='"' + m_appDir + '"' + "/wsprd -a \"" + + QDir::toNativeSeparators(m_dataDir.absolutePath()) + "\" " + m_path; +// if(m_TRseconds==900) cmnd='"' + m_appDir + '"' + "/wsprd -m 15" + t2 + +// m_path + '"'; + } else { + cmnd='"' + m_appDir + '"' + "/wsprd -a \"" + + QDir::toNativeSeparators(m_dataDir.absolutePath()) + "\" " + + t2 + '"' + m_fname + '"'; + } + QString t3=cmnd; + int i1=cmnd.indexOf("/wsprd "); + cmnd=t3.mid(0,i1+7) + t3.mid(i1+7); + ui->DecodeButton->setChecked (true); + p1.start(QDir::toNativeSeparators(cmnd)); + } + m_rxDone=true; } } @@ -810,14 +966,8 @@ void MainWindow::on_monitorButton_clicked (bool checked) } } - Q_EMIT m_config.sync_transceiver (true, checked); // gets - // Configuration - // in/out of - // strict - // split and - // mode - // checking - +//Get Configuration in/out of strict split and mode checking + Q_EMIT m_config.sync_transceiver (true, checked); } else { @@ -828,17 +978,11 @@ void MainWindow::on_monitorButton_clicked (bool checked) void MainWindow::monitor (bool state) { ui->monitorButton->setChecked (state); - if (state) - { - if (!m_monitoring) - { - Q_EMIT resumeAudioInputStream (); - } - } - else - { - Q_EMIT suspendAudioInputStream (); - } + if (state) { + if (!m_monitoring) Q_EMIT resumeAudioInputStream (); + } else { + Q_EMIT suspendAudioInputStream (); + } m_monitoring = state; } @@ -850,11 +994,27 @@ void MainWindow::on_actionAbout_triggered() //Display "About" void MainWindow::on_autoButton_clicked (bool checked) { m_auto = checked; - m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, ui->autoButton->isChecked (), m_transmitting); +// qDebug() << "autoButton_clicked" << m_auto << m_tuneup; + + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, + QString::number (ui->rptSpinBox->value ()), + m_modeTx, ui->autoButton->isChecked (), + m_transmitting); + if(m_mode.mid(0,4)=="WSPR") { + QPalette* palette = new QPalette(); + if(m_auto or m_pctx==0) { + palette->setColor(QPalette::Base,Qt::white); + } else { + palette->setColor(QPalette::Base,Qt::yellow); + } + ui->sbTxPercent->setPalette(*palette); + delete palette; + } } void MainWindow::auto_tx_mode (bool state) { +// qDebug() << "auto_tx_mode" << state << m_tuneup; ui->autoButton->setChecked (state); on_autoButton_clicked (state); } @@ -984,27 +1144,24 @@ void MainWindow::qsy (Frequency f) if (m_dialFreq != f) { m_dialFreq = f; - m_repeatMsg=0; m_secBandChanged=QDateTime::currentMSecsSinceEpoch()/1000; - - QFile f2 {m_dataDir.absoluteFilePath ("ALL.TXT")}; - if (f2.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) - { + if(m_dialFreq/1000000 < 30 and m_mode.mid(0,4)!="WSPR") { +// Write freq changes to ALL.TXT only below 30 MHz. + QFile f2 {m_dataDir.absoluteFilePath ("ALL.TXT")}; + if (f2.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { QTextStream out(&f2); out << QDateTime::currentDateTimeUtc().toString("yyyy-MMM-dd hh:mm") << " " << (m_dialFreq / 1.e6) << " MHz " << m_mode << endl; f2.close(); - } - else - { + } else { msgBox("Cannot open \"" + f2.fileName () + "\" for append:" + f2.errorString ()); } - if (m_config.spot_to_psk_reporter ()) - { - pskSetLocal (); - } + } + if (m_config.spot_to_psk_reporter ()) { + pskSetLocal (); + } displayDialFrequency (); statusChanged(); m_wideGraph->setDialFreq(m_dialFreq / 1.e6); @@ -1016,11 +1173,14 @@ void MainWindow::displayDialFrequency () { // lookup band auto bands_model = m_config.bands (); - ui->bandComboBox->setCurrentText (bands_model->data (bands_model->find (m_dialFreq)).toString ()); + QString t {bands_model->data(bands_model->find(m_dialFreq)).toString()}; + ui->bandComboBox->setCurrentText (t); + m_wideGraph->setRxBand(t); // search working frequencies for one we are within 10kHz of auto frequencies = m_config.frequencies (); bool valid {false}; + quint64 min_offset=99999999; for (int row = 0; row < frequencies->rowCount (); ++row) { // we need to do specific checks for above and below here to @@ -1028,11 +1188,13 @@ void MainWindow::displayDialFrequency () // potentially use the full 64-bit unsigned range. auto working_frequency = frequencies->data (frequencies->index (row, 0)).value (); auto offset = m_dialFreq > working_frequency ? m_dialFreq - working_frequency : working_frequency - m_dialFreq; - if ((offset < 10000u) or (m_config.enable_VHF_features() and offset < 1000000u)) { - m_freqNominal=working_frequency; - valid = true; + if(offsetlabDialFreq->setProperty ("oob", !valid); // the following sequence is necessary to update the style @@ -1044,7 +1206,9 @@ void MainWindow::displayDialFrequency () void MainWindow::statusChanged() { - m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, ui->autoButton->isChecked (), m_transmitting); + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, + QString::number (ui->rptSpinBox->value ()), + m_modeTx, ui->autoButton->isChecked (), m_transmitting); QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")}; if(f.open(QFile::WriteOnly | QIODevice::Text)) { @@ -1094,6 +1258,9 @@ void MainWindow::createStatusBar() //createStatusBar auto_tx_label->setMinimumSize(QSize(150,18)); auto_tx_label->setFrameStyle(QFrame::Panel | QFrame::Sunken); statusBar()->addWidget(auto_tx_label); + + progressBar = new QProgressBar; + statusBar()->addWidget(progressBar); } void MainWindow::closeEvent(QCloseEvent * e) @@ -1380,6 +1547,7 @@ void MainWindow::decode() //decode() { if(!m_dataAvailable) return; ui->DecodeButton->setChecked (true); + if(jt9com_.newdat==1 && (!m_diskData)) { qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; int imin=ms/60000; @@ -1415,9 +1583,14 @@ void MainWindow::decode() //decode() jt9com_.nsubmode=m_nSubMode; jt9com_.minw=m_MinW; jt9com_.nclearave=m_nclearave; + if(m_nclearave!=0) { + QFile f(m_config.temp_dir ().absoluteFilePath ("avemsg.txt")); + f.remove(); + } jt9com_.dttol=m_DTtol; jt9com_.emedelay=0.0; if(m_bEME) jt9com_.emedelay=2.5; + jt9com_.minSync=m_minSync; strncpy(jt9com_.datetime, m_dateTime.toLatin1(), 20); strncpy(jt9com_.mycall, (m_config.my_callsign()+" ").toLatin1(),12); @@ -1463,7 +1636,7 @@ void MainWindow::readFromStdout() //readFromStdout { while(proc_jt9.canReadLine()) { QByteArray t=proc_jt9.readLine(); - bool baveJT4msg=(t.length()>48); + bool baveJT4msg=(t.length()>49); if(m_mode=="JT4") t=t.mid(0,39) + t.mid(42,t.length()-42); if(t.indexOf("") >= 0) { m_bdecoded = (t.mid(23,1).toInt()==1); @@ -1495,17 +1668,15 @@ void MainWindow::readFromStdout() //readFromStdout msgBox("Cannot open \"" + f.fileName () + "\" for append:" + f.errorString ()); } - if (m_config.insert_blank () && m_blankLine) - { - QString band; - if (QDateTime::currentMSecsSinceEpoch() / 1000 - m_secBandChanged > 50) - { - auto const& bands_model = m_config.bands (); - band = ' ' + bands_model->data (bands_model->find (m_dialFreq + ui->TxFreqSpinBox->value ())).toString (); - } - ui->decodedTextBrowser->insertLineSpacer (band.rightJustified (40, '-')); - m_blankLine = false; - } + if (m_config.insert_blank () && m_blankLine) { + QString band; + if (QDateTime::currentMSecsSinceEpoch() / 1000 - m_secBandChanged > 50) { + auto const& bands_model = m_config.bands (); + band = ' ' + bands_model->data (bands_model->find (m_dialFreq + ui->TxFreqSpinBox->value ())).toString (); + } + ui->decodedTextBrowser->insertLineSpacer (band.rightJustified (40, '-')); + m_blankLine = false; + } DecodedText decodedtext; decodedtext = t.replace("\n",""); //t.replace("\n","").mid(0,t.length()-4); @@ -1595,12 +1766,16 @@ void MainWindow::on_EraseButton_clicked() //Erase { qint64 ms=QDateTime::currentMSecsSinceEpoch(); ui->decodedTextBrowser2->clear(); - m_QSOText.clear(); - if((ms-m_msErase)<500) { + if(m_mode.mid(0,4)=="WSPR") { ui->decodedTextBrowser->clear(); - m_messageClient->clear_decodes (); - QFile f(m_config.temp_dir ().absoluteFilePath ("decoded.txt")); - if(f.exists()) f.remove(); + } else { + m_QSOText.clear(); + if((ms-m_msErase)<500) { + ui->decodedTextBrowser->clear(); + m_messageClient->clear_decodes (); + QFile f(m_config.temp_dir ().absoluteFilePath ("decoded.txt")); + if(f.exists()) f.remove(); + } } m_msErase=ms; } @@ -1650,11 +1825,14 @@ void MainWindow::guiUpdate() static double onAirFreq0=0.0; QString rt; - double tx1=0.0; - double tx2=1.0 + 85.0*m_nsps/12000.0 + icw[0]*2560.0/48000.0; - if(m_modeTx=="JT65") tx2=1.0 + 126*4096/11025.0 + icw[0]*2560.0/48000.0; + double txDuration=1.0 + 85.0*m_nsps/12000.0; // JT9 + if(m_modeTx=="JT65") txDuration=1.0 + 126*4096/11025.0; // JT65 + if(m_mode=="WSPR-2") txDuration=2.0 + 162*8192/12000.0; // WSPR +//### if(m_mode=="WSPR-15") tx2=... - if(!m_txFirst) { + double tx1=0.0; + double tx2=txDuration + + icw[0]*2560.0/48000.0; //Full length including CW ID + if(!m_txFirst and m_mode.mid(0,4)!="WSPR") { tx1 += m_TRperiod; tx2 += m_TRperiod; } @@ -1662,54 +1840,124 @@ void MainWindow::guiUpdate() int nsec=ms/1000; double tsec=0.001*ms; double t2p=fmod(tsec,2*m_TRperiod); - bool bTxTime = ((t2p >= tx1) and (t2p < tx2)) or m_tune; + m_nseq = nsec % m_TRperiod; - if(m_transmitting or m_auto or m_tune) { - QFile f(m_appDir + "/txboth"); - if(f.exists() and fmod(tsec,m_TRperiod) < (1.0 + 85.0*m_nsps/12000.0)) { - bTxTime=true; + if(m_mode.mid(0,4)=="WSPR") { + if(m_nseq==0 and m_ntr==0) { + m_tuneup=false; + if(m_pctx==0) m_nrx=1; //Always receive if pctx=0 + if((m_auto and (m_pctx>0) and (m_txNext or ((m_nrx<=0) and + (m_ntr!=-1)))) or ((m_auto and (m_pctx==100)))) { +// This will be a WSPR Tx sequence. Compute # of Rx's that should follow. + float x=(float)rand()/RAND_MAX; + if(m_pctx<50) { + m_nrx=int(m_rxavg + 3.0*(x-0.5) + 0.5); + } else { + m_nrx=0; + if(xpbTxNext->setChecked(false); + m_bTxTime=true; //Start a WSPR Tx sequence + } else { +// This will be a WSPR Rx sequence. + m_ntr=1; //This says we will have received + m_bTxTime=false; //Start a WSPR Rx sequence + } } - Frequency onAirFreq = m_dialFreq + ui->TxFreqSpinBox->value (); - if (onAirFreq > 10139900 && onAirFreq < 10140320) { - bTxTime=false; + } else { + m_bTxTime = (t2p >= tx1) and (t2p < tx2); // For all modes other than WSPR + } + if(m_tune) m_bTxTime=true; //"Tune" takes precedence + + if(m_transmitting or m_auto or m_tune) { + +// Check for "txboth" (testing purposes only) + QFile f(m_appDir + "/txboth"); + if(f.exists() and fmod(tsec,m_TRperiod) < (1.0 + 85.0*m_nsps/12000.0)) { + m_bTxTime=true; + } + +// Don't transmit another mode in the 30 m WSPR sub-band + Frequency onAirFreq = m_dialFreq + ui->TxFreqSpinBox->value(); + if ((onAirFreq > 10139900 and onAirFreq < 10140320) and m_mode.mid(0,4)!="WSPR") { + m_bTxTime=false; if (m_tune) stop_tuning (); if (m_auto) auto_tx_mode (false); - if(onAirFreq!=onAirFreq0) { onAirFreq0=onAirFreq; QString t="Please choose another Tx frequency.\n"; - t+="WSJT-X will not knowingly transmit\n"; - t+="in the WSPR sub-band on 30 m."; + t+="WSJT-X will not knowingly transmit another\n"; + t+="mode in the WSPR sub-band on 30 m."; msgBox(t); } } float fTR=float((nsec%m_TRperiod))/m_TRperiod; - if(g_iptt==0 and ((bTxTime and fTR<0.4) or m_tune )) { +// if(g_iptt==0 and ((m_bTxTime and fTR<0.4) or m_tune )) { + if(g_iptt==0 and ((m_bTxTime and fTR<99) or m_tune )) { //### allow late starts ### icw[0]=m_ncw; g_iptt = 1; - setXIT (ui->TxFreqSpinBox->value ()); // ensure correct offset - Q_EMIT m_config.transceiver_ptt (true); - ptt1Timer->start(200); //Sequencer delay + setXIT (ui->TxFreqSpinBox->value ()); //Ensure correct offset + Q_EMIT m_config.transceiver_ptt (true); //Assert the PTT + ptt1Timer->start(200); //Sequencer delay + } + if(!m_bTxTime and !m_tune) m_btxok=false; //Time to stop transmitting + } + + if(m_mode.mid(0,4)=="WSPR" and + ((m_ntr==1 and m_rxDone) or (m_ntr==-1 and m_nseq>tx2))) { + if(m_monitoring) { + m_nrx=m_nrx-1; //Decrement the Rx-sequence count + m_rxDone=false; + } + if(m_transmitting) { + m_bTxTime=false; //Time to stop a WSPR transmission + m_btxok=false; + } + if(m_bandHopping and m_ntr==1) { +// qDebug() << "Call bandHopping after Rx" << m_nseq << m_ntr << m_nrx << m_rxDone; + bandHopping(); + m_ntr=0; //This WSPR Rx sequence is complete } - if(!bTxTime and !m_tune) m_btxok=false; } // Calculate Tx tones when needed if((g_iptt==1 && iptt0==0) || m_restart) { QByteArray ba; - if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit(); - if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit(); - if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit(); - if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit(); - if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit(); - if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit(); - if(m_ntx == 7) ba=ui->genMsg->text().toLocal8Bit(); - if(m_ntx == 8) ba=ui->freeTextMsg->currentText().toLocal8Bit(); + + if(m_mode.mid(0,4)=="WSPR") { + QString sdBm,msg0,msg1,msg2; + sdBm.sprintf(" %d",m_dBm); + m_tx=1-m_tx; + int i2=m_config.my_callsign().indexOf("/"); + if(i2>0 or m_grid6) { + if(i2<0) { // "Type 2" WSPR message + msg1=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm; + } else { + msg1=m_config.my_callsign() + sdBm; + } + msg0="<" + m_config.my_callsign() + "> " + m_config.my_grid()+ sdBm; + if(m_tx==0) msg2=msg0; + if(m_tx==1) msg2=msg1; + } else { + msg2=m_config.my_callsign() + " " + m_config.my_grid().mid(0,4) + sdBm; // Normal WSPR message + } + ba=msg2.toLatin1(); + } else { + if(m_ntx == 1) ba=ui->tx1->text().toLocal8Bit(); + if(m_ntx == 2) ba=ui->tx2->text().toLocal8Bit(); + if(m_ntx == 3) ba=ui->tx3->text().toLocal8Bit(); + if(m_ntx == 4) ba=ui->tx4->text().toLocal8Bit(); + if(m_ntx == 5) ba=ui->tx5->currentText().toLocal8Bit(); + if(m_ntx == 6) ba=ui->tx6->text().toLocal8Bit(); + if(m_ntx == 7) ba=ui->genMsg->text().toLocal8Bit(); + if(m_ntx == 8) ba=ui->freeTextMsg->currentText().toLocal8Bit(); + } ba2msg(ba,message); - // ba2msg(ba,msgsent); int len1=22; int ichk=0; if (m_lastMessageSent != m_currentMessage @@ -1722,28 +1970,16 @@ void MainWindow::guiUpdate() if(m_tune) { itone[0]=0; } else { - if(m_modeTx=="JT4") gen4_(message - , &ichk - , msgsent - , const_cast (itone) - , &m_currentMessageType - , len1 - , len1); - if(m_modeTx=="JT9") gen9_(message - , &ichk - , msgsent - , const_cast (itone) - , &m_currentMessageType - , len1 - , len1); - if(m_modeTx=="JT65") gen65_(message - , &ichk - , msgsent - , const_cast (itone) - , &m_currentMessageType - , len1 - , len1); + if(m_modeTx=="JT4") gen4_(message, &ichk , msgsent, const_cast (itone), + &m_currentMessageType, len1, len1); + if(m_modeTx=="JT9") gen9_(message, &ichk, msgsent, const_cast (itone), + &m_currentMessageType, len1, len1); + if(m_modeTx=="JT65") gen65_(message, &ichk, msgsent, const_cast (itone), + &m_currentMessageType, len1, len1); + if(m_mode.mid(0,4)=="WSPR") genwspr_(message, msgsent, const_cast (itone), + len1, len1); } + msgsent[22]=0; m_currentMessage = QString::fromLatin1(msgsent); if (m_tune) @@ -1829,9 +2065,7 @@ void MainWindow::guiUpdate() } } m_restart=false; - } - else - { + } else { if (!m_auto && m_sentFirst73) { m_sentFirst73 = false; @@ -1852,38 +2086,29 @@ void MainWindow::guiUpdate() if (g_iptt == 1 && iptt0 == 0) { QString t=QString::fromLatin1(msgsent); - if(t==m_msgSent0) - { - m_repeatMsg++; - } - else - { - m_repeatMsg=0; - m_msgSent0=t; + if(t==m_msgSent0) { + m_repeatMsg++; + } else { + m_repeatMsg=0; + m_msgSent0=t; + } + if(!m_tune) { + QFile f {m_dataDir.absoluteFilePath ("ALL.TXT")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << QDateTime::currentDateTimeUtc().toString("hhmm") + << " Transmitting " << (m_dialFreq / 1.e6) << " MHz " << m_modeTx + << ": " << m_currentMessage << endl; + f.close(); + } else { + msgBox("Cannot open \"" + f.fileName () + "\" for append:" + f.errorString ()); } + } - if(!m_tune) - { - QFile f {m_dataDir.absoluteFilePath ("ALL.TXT")}; - if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) - { - QTextStream out(&f); - out << QDateTime::currentDateTimeUtc().toString("hhmm") - << " Transmitting " << (m_dialFreq / 1.e6) << " MHz " << m_modeTx - << ": " << m_currentMessage << endl; - f.close(); - } - else - { - msgBox("Cannot open \"" + f.fileName () + "\" for append:" + f.errorString ()); - } - } - - if (m_config.TX_messages () && !m_tune) - { - ui->decodedTextBrowser2->displayTransmittedText(t,m_modeTx, + if (m_config.TX_messages () && !m_tune) { + ui->decodedTextBrowser2->displayTransmittedText(t,m_modeTx, ui->TxFreqSpinBox->value(),m_config.color_TxMsg()); - } + } m_transmitting = true; transmitDisplay (true); @@ -1905,11 +2130,11 @@ void MainWindow::guiUpdate() (m_DopplerMethod==0 and m_DopplerMethod0>0)) { //Doppler tracking has just been turned off. Reset dial frequency to "nominal + kHz" if(m_transmitting) { - m_dialFreqTx=m_freqNominal + 1000*m_astroWidget->m_kHz; + m_dialFreqTx=m_freqNominal + 1000*m_astroWidget->m_kHz + m_astroWidget->m_Hz; ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (m_dialFreqTx)); Q_EMIT m_config.transceiver_tx_frequency (m_dialFreqTx); } else { - f=m_freqNominal + 1000*m_astroWidget->m_kHz; + f=m_freqNominal + 1000*m_astroWidget->m_kHz + m_astroWidget->m_Hz; Q_EMIT m_config.transceiver_frequency(f); } } @@ -1918,36 +2143,39 @@ void MainWindow::guiUpdate() } if(nsec != m_sec0) { //Once per second + int ipct=0; + if(m_monitoring or m_transmitting) ipct=int(100*m_nseq/txDuration); + progressBar->setValue(ipct); QDateTime t = QDateTime::currentDateTimeUtc(); if(m_astroWidget) { - m_freqMoon=m_dialFreq + 1000*m_astroWidget->m_kHz; + m_freqMoon=m_dialFreq + 1000*m_astroWidget->m_kHz + m_astroWidget->m_Hz; int ndop,ndop00; - m_astroWidget->astroUpdate(t, m_config.my_grid (), m_hisGrid,m_freqMoon, &ndop, &ndop00); - if(m_freqNominal>144000000) { -//Apply Doppler corrections only for 144 MHz and above - if(m_astroWidget->m_bDopplerTracking and (m_DopplerMethod==1)) { -// All Doppler correction will be done here; DX station stays at nominal dial frequency. - int ndopr=m_astroWidget->m_stepHz*qRound(double(ndop)/double(m_astroWidget->m_stepHz)); - if(m_transmitting) { - m_dialFreqTx=m_freqNominal + 1000*m_astroWidget->m_kHz - ndopr; - ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (m_dialFreqTx)); - Q_EMIT m_config.transceiver_tx_frequency (m_dialFreqTx); - } else { - f=m_freqNominal + 1000*m_astroWidget->m_kHz + ndopr; - Q_EMIT m_config.transceiver_frequency(f); - } - } + m_astroWidget->astroUpdate(t, m_config.my_grid (), m_hisGrid,m_freqMoon, + &ndop, &ndop00, m_transmitting); + +//Apply Doppler corrections only for 50 MHz and above + if(m_freqNominal>=50000000) { + + if(m_astroWidget->m_bDopplerTracking) { + + int ndopr=0; // No Doppler Correction + if(m_DopplerMethod==1) { + // All Doppler correction done here; DX station stays at nominal dial frequency. + ndopr=m_astroWidget->m_stepHz*qRound(double(ndop)/double(m_astroWidget->m_stepHz)); + } + if(m_DopplerMethod==2) { + // Doppler correction to constant frequency on Moon + ndopr=m_astroWidget->m_stepHz*qRound(double(ndop00/2.0)/double(m_astroWidget->m_stepHz)); + } - if(m_astroWidget->m_bDopplerTracking and (m_DopplerMethod==2)) { -// Doppler correction to constant frequency on the Moon - int ndopr=m_astroWidget->m_stepHz*qRound(double(ndop00/2.0)/double(m_astroWidget->m_stepHz)); if(m_transmitting) { - m_dialFreqTx=m_freqNominal + 1000*m_astroWidget->m_kHz - ndopr; + m_dialFreqTx=m_freqNominal + 1000*m_astroWidget->m_kHz + m_astroWidget->m_Hz - ndopr; ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (m_dialFreqTx)); Q_EMIT m_config.transceiver_tx_frequency (m_dialFreqTx); } else { - f=m_freqNominal + 1000*m_astroWidget->m_kHz + ndopr; - Q_EMIT m_config.transceiver_frequency(f); + m_dialFreq=m_freqNominal + 1000*m_astroWidget->m_kHz + m_astroWidget->m_Hz + ndopr; + ui->labDialFreq->setText (Radio::pretty_frequency_MHz_string (m_dialFreq)); + Q_EMIT m_config.transceiver_frequency(m_dialFreq); } } } @@ -1972,7 +2200,10 @@ void MainWindow::guiUpdate() } } else if(m_monitoring) { tx_status_label->setStyleSheet("QLabel{background-color: #00ff00}"); - tx_status_label->setText("Receiving "); + QString t="Receiving "; + if(m_auto and (m_mode.mid(0,4)=="WSPR")) t += QString::number(m_nrx); + tx_status_label->setText(t); + transmitDisplay(false); } else if (!m_diskData) { tx_status_label->setStyleSheet(""); tx_status_label->setText(""); @@ -2006,6 +2237,23 @@ void MainWindow::startTx2() if(snr>0.0 or snr < -50.0) snr=99.0; transmit (snr); signalMeter->setValue(0); + if(m_mode.mid(0,4)=="WSPR" and !m_tune) { + auto const& bands_model = m_config.bands (); + t = " Transmiting " + m_mode + " ----------------------- " + + bands_model->data(bands_model->find(m_dialFreq)).toString (); + ui->decodedTextBrowser->append(t.rightJustified (71, '-')); + + QFile f {m_dataDir.absoluteFilePath ("ALL_WSPR.TXT")}; + if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) { + QTextStream out(&f); + out << QDateTime::currentDateTimeUtc().toString("yyMMdd hhmm") + << " Transmitting " << (m_dialFreq / 1.e6) << " MHz: " + << m_currentMessage << " " + m_mode << endl; + f.close(); + } else { + msgBox("Cannot open \"" + f.fileName () + "\" for append:" + f.errorString ()); + } + } } } @@ -2019,21 +2267,25 @@ void MainWindow::stopTx() tx_status_label->setText(""); ptt0Timer->start(200); //Sequencer delay monitor (true); - m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx, ui->autoButton->isChecked (), m_transmitting); + m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, + QString::number (ui->rptSpinBox->value ()), + m_modeTx, ui->autoButton->isChecked (), m_transmitting); } void MainWindow::stopTx2() { - QString rt; - //Lower PTT - Q_EMIT m_config.transceiver_ptt (false); - - if (m_config.watchdog () && m_repeatMsg>=m_watchdogLimit-1) - { - on_stopTxButton_clicked(); - msgBox("Runaway Tx watchdog"); - m_repeatMsg=0; - } + Q_EMIT m_config.transceiver_ptt (false); //Lower PTT + if (m_mode.mid(0,4)!="WSPR" and m_config.watchdog() and + m_repeatMsg>=m_watchdogLimit-1) { + on_stopTxButton_clicked(); + msgBox("Runaway Tx watchdog"); + m_repeatMsg=0; + } + if(m_mode.mid(0,4)=="WSPR" and m_ntr==-1 and m_bandHopping and !m_tuneup) { +// qDebug () << "Call bandHopping after Tx" << m_tuneup; + bandHopping(); + m_ntr=0; + } } void MainWindow::ba2msg(QByteArray ba, char message[]) //ba2msg() @@ -2148,19 +2400,9 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl decodedtext = decodedtext.string ().left (eom_pos + 1); // remove DXCC entity and worked B4 status. TODO need a better way to do this } - /* - // if(decodedtext.indexOf("Tx")==6) return; //Ignore Tx line - // int i4=t.mid(i1).length(); - // if(i4>55) i4=55; - // QString t3=t.mid(i1,i4); auto t3 = decodedtext.string (); auto t4 = t3.replace (" CQ DX ", " CQ_DX ").split (" ", QString::SkipEmptyParts); if(t4.size () < 6) return; //Skip the rest if no decoded text -*/ - auto t3 = decodedtext.string (); - auto t4 = t3.replace (" CQ DX ", " CQ_DX ").split (" ", QString::SkipEmptyParts); - if(t4.size () <5) return; //Skip the rest if no decoded text - QString hiscall; QString hisgrid; @@ -2188,8 +2430,8 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl ui->pbTxMode->setText("Tx JT65 #"); m_wideGraph->setModeTx(m_modeTx); } - } else if ((decodedtext.isJT9 () && m_modeTx != "JT9") || - (decodedtext.isJT65 () && m_modeTx != "JT65")) { + } else if ((decodedtext.isJT9 () and m_modeTx != "JT9" and m_mode != "JT4") or + (decodedtext.isJT65 () and m_modeTx != "JT65" and m_mode != "JT4")) { // if we are not allowing mode change then don't process decode return; } @@ -2198,17 +2440,14 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl QString firstcall = decodedtext.call(); // Don't change Tx freq if a station is calling me, unless m_lockTxFreq // is true or CTRL is held down - if ((firstcall!=m_config.my_callsign () && firstcall != m_baseCall) || m_lockTxFreq or ctrl) - { - if (ui->TxFreqSpinBox->isEnabled ()) - { - ui->TxFreqSpinBox->setValue(frequency); - } - else - { - return; - } + if ((firstcall!=m_config.my_callsign () and firstcall != m_baseCall) or + m_lockTxFreq or ctrl) { + if (ui->TxFreqSpinBox->isEnabled ()) { + ui->TxFreqSpinBox->setValue(frequency); + } else if(m_mode!="JT4") { + return; } + } int i9=m_QSOText.indexOf(decodedtext.string()); if (i9<0 and !decodedtext.isTX()) @@ -2679,8 +2918,8 @@ void MainWindow::on_dxGridEntry_textChanged(const QString &t) //dxGrid changed qint64 nsec = QDateTime::currentMSecsSinceEpoch() % 86400; double utch=nsec/3600.0; int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter; - - azdist_(const_cast (m_config.my_grid ().toLatin1().constData()),const_cast (m_hisGrid.toLatin1().constData()),&utch, + azdist_(const_cast (m_config.my_grid ().toLatin1().constData()), + const_cast (m_hisGrid.toLatin1().constData()),&utch, &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); QString t; t.sprintf("Az: %d",nAz); @@ -2757,6 +2996,8 @@ void MainWindow::on_actionJT9_1_triggered() if(m_modeTx!="JT9") on_pbTxMode_clicked(); statusChanged(); m_TRperiod=60; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); m_nsps=6912; m_hsymStop=173; if(m_config.decode_at_52s()) m_hsymStop=181; @@ -2771,6 +3012,7 @@ void MainWindow::on_actionJT9_1_triggered() m_wideGraph->setModeTx(m_modeTx); ui->pbTxMode->setEnabled(false); VHF_controls_visible(false); + WSPR_config(false); ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); } @@ -2781,6 +3023,8 @@ void MainWindow::on_actionJT9W_1_triggered() if(m_modeTx!="JT9") on_pbTxMode_clicked(); statusChanged(); m_TRperiod=60; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); m_nsps=6912; m_hsymStop=173; if(m_config.decode_at_52s()) m_hsymStop=181; @@ -2795,6 +3039,7 @@ void MainWindow::on_actionJT9W_1_triggered() m_wideGraph->setModeTx(m_modeTx); ui->pbTxMode->setEnabled(false); VHF_controls_visible(false); + WSPR_config(false); ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); } @@ -2810,6 +3055,8 @@ void MainWindow::on_actionJT65_triggered() if(m_modeTx!="JT65") on_pbTxMode_clicked(); statusChanged(); m_TRperiod=60; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); m_nsps=6912; //For symspec only m_hsymStop=173; if(m_config.decode_at_52s()) m_hsymStop=181; @@ -2826,6 +3073,7 @@ void MainWindow::on_actionJT65_triggered() ui->pbTxMode->setEnabled(false); bool bVHF=m_config.enable_VHF_features(); VHF_controls_visible(bVHF); + WSPR_config(false); ui->sbSubmode->setMaximum(2); if(bVHF) { ui->sbSubmode->setValue(m_nSubMode); @@ -2845,6 +3093,8 @@ void MainWindow::on_actionJT9_JT65_triggered() m_nSubMode=0; //Dual-mode always means JT9 and JT65A statusChanged(); m_TRperiod=60; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); m_nsps=6912; m_hsymStop=173; if(m_config.decode_at_52s()) m_hsymStop=181; @@ -2859,6 +3109,7 @@ void MainWindow::on_actionJT9_JT65_triggered() m_wideGraph->setModeTx(m_modeTx); ui->pbTxMode->setEnabled(true); VHF_controls_visible(false); + WSPR_config(false); ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); } @@ -2869,6 +3120,8 @@ void MainWindow::on_actionJT4_triggered() m_modeTx="JT4"; statusChanged(); m_TRperiod=60; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); m_nsps=6912; //For symspec only m_hsymStop=181; // if(m_config.decode_at_52s()) m_hsymStop=181; @@ -2885,6 +3138,7 @@ void MainWindow::on_actionJT4_triggered() ui->pbTxMode->setEnabled(false); bool bVHF=m_config.enable_VHF_features(); VHF_controls_visible(bVHF); + WSPR_config(false); ui->sbSubmode->setMaximum(6); ui->label_6->setText("Single-Period Decodes"); ui->label_7->setText("Average Decodes"); @@ -2898,6 +3152,70 @@ void MainWindow::on_actionJT4_triggered() if(m_MinW > m_nSubMode) ui->sbMinW->setValue(m_nSubMode); } +void MainWindow::on_actionWSPR_2_triggered() +{ + m_mode="WSPR-2"; + m_modeTx="WSPR-2"; //### not needed ?? ### + statusChanged(); + m_TRperiod=120; + m_modulator.setPeriod(m_TRperiod); + m_detector.setPeriod(m_TRperiod); + m_nsps=6912; //For symspec only + m_hsymStop=396; + m_toneSpacing=12000.0/8192.0; + mode_label->setStyleSheet("QLabel{background-color: #ff00ff}"); + mode_label->setText(m_mode); + ui->actionWSPR_2->setChecked(true); + VHF_features_enabled(false); + ui->ClrAvgButton->setVisible(false); + m_wideGraph->setPeriod(m_TRperiod,m_nsps); + m_wideGraph->setMode(m_mode); + m_wideGraph->setModeTx(m_modeTx); + VHF_controls_visible(false); + WSPR_config(true); +} + +void MainWindow::on_actionWSPR_15_triggered() +{ + msgBox("WSPR-15 is not yet available"); +} + + +void MainWindow::WSPR_config(bool b) +{ + ui->decodedTextBrowser2->setVisible(!b); + ui->decodedTextLabel2->setVisible(!b); + ui->label_6->setVisible(!b); + ui->label_7->setVisible(!b); + ui->pbTxMode->setVisible(!b); + ui->TxFreqSpinBox->setVisible(!b); + ui->RxFreqSpinBox->setVisible(!b); + ui->cbTxLock->setVisible(!b); + ui->txFirstCheckBox->setVisible(!b); + ui->pbR2T->setVisible(!b); + ui->pbT2R->setVisible(!b); + ui->rptSpinBox->setVisible(!b); + ui->label_8->setVisible(!b); + ui->labAz->setVisible(!b); + ui->labDist->setVisible(!b); + ui->logQSOButton->setVisible(!b); + ui->label_3->setVisible(!b); + ui->label_4->setVisible(!b); + ui->dxCallEntry->setVisible(!b); + ui->dxGridEntry->setVisible(!b); + ui->lookupButton->setVisible(!b); + ui->addButton->setVisible(!b); + ui->DecodeButton->setEnabled(!b); + if(b) { + ui->decodedTextLabel->setText( + "UTC dB DT Freq Drift Call Grid dBm Dist"); + auto_tx_label->setText(""); + } else { + ui->decodedTextLabel->setText("UTC dB DT Freq Message"); + auto_tx_label->setText (m_config.quick_call () ? "Tx-Enable Armed" : "Tx-Enable Disarmed"); + } +} + void MainWindow::on_TxFreqSpinBox_valueChanged(int n) { m_wideGraph->setTxFreq(n); @@ -2998,49 +3316,34 @@ void MainWindow::on_bandComboBox_activated (int index) { auto frequencies = m_config.frequencies (); auto frequency = frequencies->data (frequencies->index (index, 0)); - // Lookup band auto bands = m_config.bands (); auto band_index = bands->find (frequency); - if (band_index.isValid ()) - { - ui->bandComboBox->lineEdit ()->setStyleSheet ({}); - ui->bandComboBox->setCurrentText (band_index.data ().toString ()); - } - else - { - ui->bandComboBox->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); - ui->bandComboBox->setCurrentText (bands->data (QModelIndex {}).toString ()); - } - + if (band_index.isValid ()) { + ui->bandComboBox->lineEdit ()->setStyleSheet ({}); + ui->bandComboBox->setCurrentText (band_index.data ().toString ()); + } else { + ui->bandComboBox->lineEdit ()->setStyleSheet ("QLineEdit {color: yellow; background-color : red;}"); + ui->bandComboBox->setCurrentText (bands->data (QModelIndex {}).toString ()); + } auto f = frequency.value (); - if (m_plus2kHz) - { - f += 2000; - } - + if (m_plus2kHz) f += 2000; m_bandEdited = true; band_changed (f); + m_wideGraph->setRxBand(band_index.data().toString()); +// qDebug() << "bandComboBox_activated" << index << 0.000001*f; } void MainWindow::band_changed (Frequency f) { - if (m_bandEdited) - { - m_bandEdited = false; - - // Upload any queued spots before changing band - psk_Reporter->sendReport(); - - if (!m_transmitting) - { - monitor (true); - } - - Q_EMIT m_config.transceiver_frequency (f); - qsy (f); - setXIT (ui->TxFreqSpinBox->value ()); - } + if (m_bandEdited) { + m_bandEdited = false; + psk_Reporter->sendReport(); // Upload any queued spots before changing band + if (!m_transmitting) monitor (true); + Q_EMIT m_config.transceiver_frequency (f); + qsy (f); + setXIT (ui->TxFreqSpinBox->value ()); + } } void MainWindow::enable_DXCC_entity (bool on) @@ -3167,17 +3470,14 @@ void MainWindow::on_rptSpinBox_valueChanged(int n) void MainWindow::on_tuneButton_clicked (bool checked) { - if (m_tune) - { - tuneButtonTimer->start(250); - } - else - { - m_sentFirst73=false; - m_repeatMsg=0; - itone[0]=0; - on_monitorButton_clicked (true); - } + if (m_tune) { + tuneButtonTimer->start(250); + } else { + m_sentFirst73=false; + m_repeatMsg=0; + itone[0]=0; + on_monitorButton_clicked (true); + } m_tune = checked; Q_EMIT tune (checked); } @@ -3188,10 +3488,17 @@ void MainWindow::stop_tuning () on_tuneButton_clicked (false); } +void MainWindow::stopTuneATU() +{ + on_tuneButton_clicked(false); + m_tune=false; + m_bTxTime=false; +} + void MainWindow::on_stopTxButton_clicked() //Stop Tx { if (m_tune) stop_tuning (); - if (m_auto) auto_tx_mode (false); + if (m_auto and !m_tuneup) auto_tx_mode (false); m_btxok=false; m_repeatMsg=0; } @@ -3208,10 +3515,7 @@ void MainWindow::rigOpen () void MainWindow::on_pbR2T_clicked() { - if (ui->TxFreqSpinBox->isEnabled ()) - { - ui->TxFreqSpinBox->setValue(ui->RxFreqSpinBox->value ()); - } + if (ui->TxFreqSpinBox->isEnabled ()) ui->TxFreqSpinBox->setValue(ui->RxFreqSpinBox->value ()); } void MainWindow::on_pbT2R_clicked() @@ -3249,12 +3553,12 @@ void MainWindow::on_pbTxMode_clicked() void MainWindow::setXIT(int n) { m_XIT = 0; - if (m_config.split_mode ()) + if (m_config.split_mode () and (m_mode != "JT4")) //Don't use XIT in JT4 mode { m_XIT=(n/500)*500 - 1500; } - if (m_monitoring || m_transmitting) + if (m_monitoring or m_transmitting) { if (m_config.transceiver_online ()) { @@ -3264,7 +3568,6 @@ void MainWindow::setXIT(int n) } } } - Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); } @@ -3292,26 +3595,17 @@ void MainWindow::on_cbPlus2kHz_toggled(bool checked) { m_plus2kHz = checked; - if (m_config.transceiver_online (false)) // only update state if not - // starting up - { - // Upload any queued spots before changing band - psk_Reporter->sendReport(); - + if (m_config.transceiver_online (false)) { // update state only if not starting up + psk_Reporter->sendReport(); // Upload any queued spots before changing band auto f = m_dialFreq; - - if (m_plus2kHz) - { - f += 2000; - } - else - { - f -= 2000; - } - + if (m_plus2kHz) { + f += 2000; + } else { + f -= 2000; + } m_bandEdited = true; band_changed (f); - } + } } void MainWindow::handle_transceiver_update (Transceiver::TransceiverState s) @@ -3378,12 +3672,14 @@ void MainWindow::transmit (double snr) if(m_nSubMode==2) toneSpacing=4*11025.0/4096.0; Q_EMIT sendMessage (NUM_JT65_SYMBOLS, 4096.0*12000.0/11025.0, ui->TxFreqSpinBox->value () - m_XIT, - m_toneSpacing, &m_soundOutput, m_config.audio_output_channel (), + toneSpacing, &m_soundOutput, m_config.audio_output_channel (), true, snr); } - if (m_modeTx == "JT9") Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, - ui->TxFreqSpinBox->value () - m_XIT, m_toneSpacing, - &m_soundOutput, m_config.audio_output_channel (), true, snr); + if (m_modeTx == "JT9") { + Q_EMIT sendMessage (NUM_JT9_SYMBOLS, m_nsps, + ui->TxFreqSpinBox->value () - m_XIT, m_toneSpacing, + &m_soundOutput, m_config.audio_output_channel (), true, snr); + } if (m_modeTx == "JT4") { if(m_nSubMode==0) toneSpacing=4.375; if(m_nSubMode==1) toneSpacing=2*4.375; @@ -3397,6 +3693,13 @@ void MainWindow::transmit (double snr) toneSpacing, &m_soundOutput, m_config.audio_output_channel (), true, snr); } + if (m_mode=="WSPR-2") { //### Similar code needed for WSPR-15 ### + + Q_EMIT sendMessage (NUM_WSPR_SYMBOLS, 8192.0, + ui->TxFreqSpinBox->value()-2, m_toneSpacing, + &m_soundOutput, m_config.audio_output_channel(), + true, snr); + } } void MainWindow::on_outAttenuation_valueChanged (int a) @@ -3512,14 +3815,12 @@ void MainWindow::pskSetLocal () , 1 , Qt::MatchExactly); QString antenna_description; - if (!matches.isEmpty ()) - { - antenna_description = stations->index (matches.first ().row (), 2).data ().toString (); - } - psk_Reporter->setLocalStation( - m_config.my_callsign () - , m_config.my_grid () - , antenna_description, QString {"WSJT-X v" + version() + " " + m_revision}.simplified ()); + if (!matches.isEmpty ()) { + antenna_description = stations->index (matches.first ().row (), 2).data ().toString (); + } + psk_Reporter->setLocalStation(m_config.my_callsign (), m_config.my_grid (), + antenna_description, QString {"WSJT-X v" + version() + " " + + m_revision}.simplified ()); } void MainWindow::transmitDisplay (bool transmitting) @@ -3532,16 +3833,26 @@ void MainWindow::transmitDisplay (bool transmitting) m_btxok=true; } - auto QSY_allowed = !transmitting || m_config.tx_QSY_allowed () || !m_config.split_mode (); + auto QSY_allowed = !transmitting or m_config.tx_QSY_allowed () or + !m_config.split_mode (); if (ui->cbTxLock->isChecked ()) { ui->RxFreqSpinBox->setEnabled (QSY_allowed); ui->pbT2R->setEnabled (QSY_allowed); } - ui->TxFreqSpinBox->setEnabled (QSY_allowed); - ui->pbR2T->setEnabled (QSY_allowed); - ui->cbTxLock->setEnabled (QSY_allowed); - // only allow +2kHz when not transmitting or if TX QSYs are allowed + if(m_mode=="JT4" and (m_dialFreq/1000000 >= 432)) { +// if(m_mode=="JT4") { + ui->TxFreqSpinBox->setValue(1000); + ui->TxFreqSpinBox->setEnabled (false); + ui->cbTxLock->setChecked(false); + ui->cbTxLock->setEnabled(false); + } else if(m_mode!="WSPR") { + ui->TxFreqSpinBox->setEnabled (QSY_allowed); + ui->pbR2T->setEnabled (QSY_allowed); + ui->cbTxLock->setEnabled (QSY_allowed); + } + + // Allow +2kHz only when not transmitting or if TX QSYs are allowed ui->cbPlus2kHz->setEnabled (!transmitting || m_config.tx_QSY_allowed ()); // the following are always disallowed in transmit @@ -3584,6 +3895,7 @@ void::MainWindow::VHF_controls_visible(bool b) ui->sbDT->setVisible(b); ui->labTol->setVisible(b); ui->sbTol->setVisible(b); + ui->syncSpinBox->setVisible(b); } void::MainWindow::VHF_features_enabled(bool b) @@ -3744,3 +4056,370 @@ void MainWindow::networkError (QString const& e) m_messageClient->set_server (m_config.udp_server_name ()); } } + +void MainWindow::on_syncSpinBox_valueChanged(int n) +{ + m_minSync=n; +} + +void MainWindow::p1ReadFromStderr() //p1readFromStderr +{ + QByteArray t=p1.readAllStandardError(); + msgBox(t); +} + +void MainWindow::p1Error (QProcess::ProcessError e) +{ + if(!m_killAll) { + msgBox("Error starting or running\n" + m_appDir + "/wsprd"); + qDebug() << e; // silence compiler warning + exit(1); + } +} + +void MainWindow::p1ReadFromStdout() //p1readFromStdout +{ + QString t1; + while(p1.canReadLine()) { + QString t(p1.readLine()); + if(t.indexOf("") >= 0) { + ui->DecodeButton->setChecked (false); + if(m_uploadSpots) { + float x=rand()/((double)RAND_MAX + 1.0); + int msdelay=20000*x; + uploadTimer->start(msdelay); //Upload delay + } else { + QFile f(QDir::toNativeSeparators(m_dataDir.absolutePath()) + "/wspr_spots.txt"); + if(f.exists()) f.remove(); + } + if(!m_saveAll and !m_diskData) { + QFile savedWav(m_fname); + savedWav.remove(); + } +/* + if(m_saveAll and !m_diskData) { + int i1=m_fname.indexOf(".wav"); + QString sc2=m_fname.mid(0,i1) + ".c2"; + QFile savedC2(sc2); + savedC2.remove(); + } +*/ + m_RxLog=0; + m_startAnother=m_loopall; + m_blankLine=true; + return; + } else { + + int n=t.length(); + t=t.mid(0,n-2) + " "; + t.remove(QRegExp("\\s+$")); + QStringList rxFields = t.split(QRegExp("\\s+")); + QString rxLine; + QString grid=""; + if ( rxFields.count() == 8 ) { + rxLine = QString("%1 %2 %3 %4 %5 %6 %7 %8") + .arg(rxFields.at(0), 4) + .arg(rxFields.at(1), 4) + .arg(rxFields.at(2), 5) + .arg(rxFields.at(3), 11) + .arg(rxFields.at(4), 4) + .arg(rxFields.at(5), -12) + .arg(rxFields.at(6), -6) + .arg(rxFields.at(7), 3); + grid = rxFields.at(6); + } else if ( rxFields.count() == 7 ) { // Type 2 message + rxLine = QString("%1 %2 %3 %4 %5 %6 %7 %8") + .arg(rxFields.at(0), 4) + .arg(rxFields.at(1), 4) + .arg(rxFields.at(2), 5) + .arg(rxFields.at(3), 11) + .arg(rxFields.at(4), 4) + .arg(rxFields.at(5), -12) + .arg("", -6) + .arg(rxFields.at(6), 3); + } else { + rxLine = t; + } + if(grid!="") { + double utch=0.0; + int nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter; + azdist_(const_cast (m_config.my_grid ().toLatin1().constData()), + const_cast (grid.toLatin1().constData()),&utch, + &nAz,&nEl,&nDmiles,&nDkm,&nHotAz,&nHotABetter,6,6); + QString t1; + if(m_config.miles()) { + t1.sprintf("%7d",nDmiles); + } else { + t1.sprintf("%7d",nDkm); + } + rxLine += t1; + } + + if (m_config.insert_blank () && m_blankLine) { + QString band; + auto const& bands_model = m_config.bands (); + Frequency f=1000000.0*rxFields.at(3).toDouble()+0.5; + band = ' ' + bands_model->data (bands_model->find (f)).toString (); + ui->decodedTextBrowser->append(band.rightJustified (71, '-')); + m_blankLine = false; + } + +// ui->decodedTextBrowser->append(t); + ui->decodedTextBrowser->append(rxLine); + } + } +} + +void MainWindow::uploadSpots() +{ + if(m_diskData) return; + if(m_uploading) { + qDebug() << "Previous upload has not completed, spots were lost"; + return; + } + QString rfreq = QString("%1").arg(0.000001*(m_dialFreqRxWSPR + 1500), 0, 'f', 6); + QString tfreq = QString("%1").arg(0.000001*(m_dialFreqRxWSPR + + ui->TxFreqSpinBox->value()), 0, 'f', 6); + wsprNet->upload(m_config.my_callsign(), m_config.my_grid(), rfreq, tfreq, + m_mode, QString::number(ui->autoButton->isChecked() ? m_pctx : 0), + QString::number(m_dBm), version(), + QDir::toNativeSeparators(m_dataDir.absolutePath()) + "/wspr_spots.txt"); + m_uploading = true; +} + +void MainWindow::uploadResponse(QString response) +{ + if (response == "done") { + m_uploading=false; + } else if (response == "Upload Failed") { + m_uploading=false; + } +// qDebug() << "uploadResponse" << response; +} + + +void MainWindow::p3ReadFromStdout() //p3readFromStdout +{ + QByteArray t=p3.readAllStandardOutput(); + if(t.length()>0) { + msgBox("user_hardware stdout:\n\n"+t+"\n"+m_cmnd); + } +} + +void MainWindow::p3ReadFromStderr() //p3readFromStderr +{ + QByteArray t=p3.readAllStandardError(); + if(t.length()>0) { + msgBox("user_hardware stderr:\n\n"+t+"\n"+m_cmnd); + } +} + +void MainWindow::p3Error(QProcess::ProcessError e) //p3rror +{ + msgBox("Error attempting to run user_hardware.\n\n"+m_cmnd); + qDebug() << e; // silence compiler warning +} + + +void MainWindow::on_TxPowerComboBox_currentIndexChanged(const QString &arg1) +{ + int i1=arg1.indexOf(" "); + m_dBm=arg1.mid(0,i1).toInt(); +} + +void MainWindow::on_sbTxPercent_valueChanged(int n) +{ + m_pctx=n; + m_rxavg=1.0; + if(m_pctx>0) { + m_rxavg=100.0/m_pctx - 1.0; //Average # of Rx's per Tx + ui->pbTxNext->setEnabled(true); + } else { + m_txNext=false; + ui->pbTxNext->setEnabled(false); + ui->pbTxNext->setChecked(false); + } +} + +void MainWindow::on_cbUploadWSPR_Spots_toggled(bool b) +{ + m_uploadSpots=b; + if(m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet(""); + if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet( + "QCheckBox{background-color: yellow}"); +} + +void MainWindow::on_WSPRfreqSpinBox_valueChanged(int n) +{ + ui->TxFreqSpinBox->setValue(n); +} + +void MainWindow::on_pbTxNext_clicked(bool b) +{ + m_txNext=b; +} + +void MainWindow::on_cbBandHop_toggled(bool b) +{ + m_bandHopping=b; +} + +void MainWindow::bandHopping() +{ + QString bandName[]={"160","80","60","40","30","20","17","15","12","10"}; + QDateTime t = QDateTime::currentDateTimeUtc(); + QString date = t.date().toString("yyyy MMM dd").trimmed(); + QString utc = t.time().toString().trimmed(); + int nyear=t.date().year(); + int month=t.date().month(); + int nday=t.date().day(); + int nhr=t.time().hour(); + int nmin=t.time().minute(); + float sec=t.time().second() + 0.001*t.time().msec(); + float uth=nhr + nmin/60.0 + sec/3600.0; + int isun; + int iband0,iband; + int ntxnext; + int i,j; + + static int icall=0; + if(m_hopTest) uth+= 2.0*icall/60.0; + icall++; + +// Find grayline status, isun: 0=Sunrise, 1=Day, 2=Sunset, 3=Night + hopping_(&nyear, &month, &nday, &uth, + const_cast (m_config.my_grid ().toLatin1().constData()), + &m_grayDuration, &m_pctx, &isun, &iband0, &ntxnext, 6); + +/* + if(m_auto and ntxnext==1) { + m_nrx=0; + } else { + m_nrx=1; + } +*/ + + QString bname; + QStringList s; + if(isun==0) s=m_sunriseBands; + if(isun==1) s=m_dayBands; + if(isun==2) s=m_sunsetBands; + if(isun==3) s=m_nightBands; + + Frequency f0; + iband=-1; + for(i=0; idata (frequencies->index (i, 0)); + auto f = frequency.value(); + if(f==0) break; + if(f==f0) { + on_bandComboBox_activated(i); //Set new band +// qDebug() << nhr << nmin << int(sec) << "Band selected" << i << 0.000001*f0 << 0.000001*f; + break; + } + } + + m_cmnd=""; + QFile f1 {m_appDir + "/user_hardware.bat"}; + if(f1.exists()) { + m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.bat ") + bname; + } + QFile f2 {m_appDir + "/user_hardware.cmd"}; + if(f2.exists()) { + m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.cmd ") + bname; + } + QFile f3 {m_appDir + "/user_hardware.exe"}; + if(f3.exists()) { + m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.exe ") + bname; + } + QFile f4 {m_appDir + "/user_hardware"}; + if(f4.exists()) { + m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware ") + bname; + } + if(m_cmnd!="") p3.start(m_cmnd); // Execute user's hardware controller + +// Displat grayline status + QString dailySequence[4]={"Sunrise grayline","Day","Sunset grayline","Night"}; + auto_tx_label->setText(dailySequence[isun]); + +// Produce a short tuneup signal + s=m_tuneBands; + m_tuneup=false; + for(int i=0; istart(2500); + } +} + +void MainWindow::on_pushButton_clicked() +{ + m_hopTest=true; +// for(int i=0; i<720; i++) { + bandHopping(); +// } + m_hopTest=false; +} + +void MainWindow::on_sunriseBands_editingFinished() +{ + m_sunriseBands=ui->sunriseBands->text().split(" ", QString::SkipEmptyParts); +} + +void MainWindow::on_dayBands_editingFinished() +{ + m_dayBands=ui->dayBands->text().split(" ", QString::SkipEmptyParts); +} + +void MainWindow::on_sunsetBands_editingFinished() +{ + m_sunsetBands=ui->sunsetBands->text().split(" ", QString::SkipEmptyParts); +} + +void MainWindow::on_nightBands_editingFinished() +{ + m_nightBands=ui->nightBands->text().split(" ", QString::SkipEmptyParts); +} + +void MainWindow::on_tuneBands_editingFinished() +{ + m_tuneBands=ui->tuneBands->text().split(" ", QString::SkipEmptyParts); +} + +void MainWindow::on_graylineDuration_editingFinished() +{ + m_grayDuration=ui->graylineDuration->text().toInt(); +} diff --git a/mainwindow.h b/mainwindow.h index f7c75a9e0..2358415ae 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -30,10 +30,12 @@ #include "Detector.hpp" #include "Modulator.hpp" #include "decodedtext.h" +#include "wsprnet.h" #define NUM_JT4_SYMBOLS 206 #define NUM_JT65_SYMBOLS 126 #define NUM_JT9_SYMBOLS 85 +#define NUM_WSPR_SYMBOLS 162 #define NUM_CW_SYMBOLS 250 #define TX_SAMPLE_RATE 48000 @@ -83,6 +85,9 @@ public slots: void readFromStdout(); void readFromStderr(); void jt9_error(QProcess::ProcessError); + void p1ReadFromStdout(); + void p1ReadFromStderr(); + void p1Error(QProcess::ProcessError); void setXIT(int n); void setFreq4(int rxFreq, int txFreq); void clrAvg(); @@ -188,6 +193,7 @@ private slots: void band_changed (Frequency); void monitor (bool); void stop_tuning (); + void stopTuneATU(); void auto_tx_mode (bool); void on_actionMessage_averaging_triggered(); void on_sbTol_valueChanged(int i); @@ -203,6 +209,28 @@ private slots: void on_cbTx6_toggled(bool b); void networkError (QString const&); void on_ClrAvgButton_clicked(); + void on_actionWSPR_2_triggered(); + void on_actionWSPR_15_triggered(); + void on_syncSpinBox_valueChanged(int n); + void on_TxPowerComboBox_currentIndexChanged(const QString &arg1); + void on_sbTxPercent_valueChanged(int n); + void on_cbUploadWSPR_Spots_toggled(bool b); + void WSPR_config(bool b); + void uploadSpots(); + void uploadResponse(QString response); + void p3ReadFromStdout(); + void p3ReadFromStderr(); + void p3Error(QProcess::ProcessError e); + void on_WSPRfreqSpinBox_valueChanged(int n); + void on_pbTxNext_clicked(bool b); + void on_cbBandHop_toggled(bool b); + void on_sunriseBands_editingFinished(); + void on_pushButton_clicked(); + void on_dayBands_editingFinished(); + void on_sunsetBands_editingFinished(); + void on_nightBands_editingFinished(); + void on_tuneBands_editingFinished(); + void on_graylineDuration_editingFinished(); private: void enable_DXCC_entity (bool on); @@ -249,6 +277,7 @@ private: QScopedPointer m_msgAvgWidget; Frequency m_dialFreq; + Frequency m_dialFreqRxWSPR; Detector m_detector; SoundInput m_soundInput; @@ -263,6 +292,7 @@ private: qint64 m_dialFreqTx; float m_DTtol; + float m_rxavg; qint32 m_waterfallAvg; qint32 m_ntx; @@ -274,6 +304,8 @@ private: qint32 m_RxLog; qint32 m_nutc0; qint32 m_nrx; + qint32 m_ntr; + qint32 m_tx; qint32 m_hsym; qint32 m_TRperiod; qint32 m_nsps; @@ -291,6 +323,12 @@ private: qint32 m_nclearave; qint32 m_DopplerMethod; qint32 m_DopplerMethod0; + qint32 m_minSync; + qint32 m_dBm; + qint32 m_pctx; + qint32 m_nseq; + qint32 m_grayDuration; + qint32 m_band00; bool m_btxok; //True if OK to transmit bool m_diskData; @@ -338,6 +376,15 @@ private: bool m_bShMsgs; bool m_bDopplerTracking; bool m_bDopplerTracking0; + bool m_uploadSpots; + bool m_uploading; + bool m_txNext; + bool m_grid6; + bool m_bandHopping; + bool m_hopTest; + bool m_tuneup; + bool m_bTxTime; + bool m_rxDone; float m_pctZap; @@ -347,6 +394,8 @@ private: QLabel * last_tx_label; QLabel * auto_tx_label; + QProgressBar* progressBar; + QMessageBox msgBox0; QFuture* future1; @@ -357,6 +406,10 @@ private: QFutureWatcher* watcher3; QProcess proc_jt9; + QProcess p1; + QProcess p3; + + WSPRNet *wsprNet; QTimer m_guiTimer; QTimer* ptt1Timer; //StartTx delay @@ -364,6 +417,8 @@ private: QTimer* logQSOTimer; QTimer* killFileTimer; QTimer* tuneButtonTimer; + QTimer* uploadTimer; + QTimer* tuneATU_Timer; QString m_path; QString m_pbdecoding_style1; @@ -389,9 +444,17 @@ private: QString m_msgSent0; QString m_fileToSave; QString m_band; + QString m_c2name; QStringList m_prefix; QStringList m_suffix; + QStringList m_sunriseBands; + QStringList m_dayBands; + QStringList m_sunsetBands; + QStringList m_nightBands; + QStringList m_tuneBands; + + QMap m_fWSPR; QHash m_pfx; QHash m_sfx; @@ -444,6 +507,7 @@ private: void replyToCQ (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text); void replayDecodes (); void postDecode (bool is_new, QString const& message); + void bandHopping(); }; extern void getfile(QString fname, int ntrperiod); @@ -456,7 +520,7 @@ extern int ptt(int nport, int ntx, int* iptt, int* nopen); extern "C" { //----------------------------------------------------- C and Fortran routines - void symspec_(int* k, int* ntrperiod, int* nsps, int* ingain, + void symspec_(int* k, int* ntrperiod, int* nsps, int* ingain, int* minw, float* px, float s[], float* df3, int* nhsym, int* npts8); void gen4_(char* msg, int* ichk, char* msgsent, int itone[], @@ -468,6 +532,8 @@ extern "C" { void gen65_(char* msg, int* ichk, char* msgsent, int itone[], int* itext, int len1, int len2); + void genwspr_(char* msg, char* msgsent, int itone[], int len1, int len2); + bool stdmsg_(const char* msg, int len); void azdist_(char* MyGrid, char* HisGrid, double* utch, int* nAz, int* nEl, @@ -481,6 +547,12 @@ extern "C" { int fftwf_import_wisdom_from_filename(const char *); int fftwf_export_wisdom_to_filename(const char *); + void wspr_downsample_(short int d2[], int* k); + void savec2_(char* fname, int* m_TRseconds, double* m_dialFreq, int len1); + + void hopping_(int* nyear, int* month, int* nday, float* uth, char* MyGrid, + int* nduration, int* npctx, int* isun, int* iband, + int* ntxnext, int len); } #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 84445e435..8c62ea4ff 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -11,14 +11,14 @@ - + 0 0 - 825 + 0 460 @@ -38,6 +38,9 @@ + + QLayout::SetDefaultConstraint + @@ -556,58 +559,7 @@ - - - - - - - 0 - 0 - - - - - 60 - 0 - - - - Tolerance for offset from selected Rx frequency. - - - F tol 500 - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 18 - 16777215 - - - - 7 - - - 5 - - - - - - + @@ -656,7 +608,7 @@ - + @@ -678,7 +630,7 @@ - + <html><head/><body><p>Tx frequency tracks Rx frequency</p></body></html> @@ -688,7 +640,7 @@ - + 5 @@ -742,23 +694,7 @@ - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 18 - - - - - + Tolerance for expected time offset. @@ -783,140 +719,7 @@ - - - - - - Set minimum width expected for Doppler-spread tones - - - MinW A - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 18 - 16777215 - - - - 0 - - - 6 - - - - - - - - - - 40 - 16777215 - - - - Check to add 2.5 s to expected propagation delay. - - - EME - - - - - - - - 0 - 0 - - - - - 48 - 16777215 - - - - Set Tx frequency to Rx Frequency - - - Tx<Rx - - - - - - - 0 - 0 - - - - - 100 - 20 - - - - Audio Rx frequency - - - Hz - - - Rx - - - 200 - - - 5000 - - - 1500 - - - - - - - - 60 - 23 - - - - - 105 - 16777215 - - - - Check to Tx in even minutes, uncheck for odd minutes - - - Tx even - - - - @@ -939,6 +742,9 @@ Audio Tx frequency + + Qt::AlignCenter + Hz @@ -956,7 +762,7 @@ - + false @@ -969,7 +775,7 @@ - + @@ -1008,7 +814,7 @@ - + Qt::Horizontal @@ -1024,6 +830,235 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::AlignCenter + + + Sync + + + 10 + + + 1 + + + + + + + + + Set minimum width expected for Doppler-spread tones + + + MinW A + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 18 + 16777215 + + + + 0 + + + 6 + + + + + + + + + + 16777215 + 16777215 + + + + Check to add 2.5 s to expected propagation delay. + + + EME + + + + + + + + 0 + 0 + + + + + 48 + 16777215 + + + + Set Tx frequency to Rx Frequency + + + Tx<Rx + + + + + + + + 0 + 0 + + + + + 100 + 20 + + + + Audio Rx frequency + + + Qt::AlignCenter + + + Hz + + + Rx + + + 200 + + + 5000 + + + 1500 + + + + + + + + 60 + 23 + + + + + 105 + 16777215 + + + + Check to Tx in even minutes, uncheck for odd minutes + + + Tx even + + + + + + + + + + 0 + 0 + + + + + 60 + 0 + + + + Tolerance for offset from selected Rx frequency. + + + F tol 500 + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 18 + 16777215 + + + + 7 + + + 5 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -1381,7 +1416,7 @@ - + 0 0 @@ -1392,6 +1427,12 @@ 200 + + + 0 + 0 + + QTabWidget::West @@ -1399,7 +1440,7 @@ QTabWidget::Triangular - 0 + 1 @@ -2197,6 +2238,361 @@ list. The list can be maintained in Settings (F2). + + + 3 + + + + + 30 + 22 + 200 + 171 + + + + + + + + 16777215 + 20 + + + + + 10 + + + + WSPR Mode + + + Qt::AlignCenter + + + + + + + Qt::AlignCenter + + + Hz + + + Tx + + + 1400 + + + 1600 + + + 1500 + + + + + + + Upload spots + + + + + + + Qt::AlignCenter + + + % + + + Tx Pct + + + 100 + + + 20 + + + + + + + Qt::Horizontal + + + + 5 + 17 + + + + + + + + true + + + Band hopping + + + + + + + QPushButton:checked { + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + Tx Next + + + true + + + + + + + + + + + + 4 + + + + + 100 + 190 + 75 + 23 + + + + Test + + + + + + 30 + 30 + 211 + 152 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Sunrise: + + + + + + + + 160 + 16777215 + + + + 160 80 40 30 20 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Day: + + + + + + + + 160 + 16777215 + + + + 30 20 17 15 12 10 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Sunset: + + + + + + + + 160 + 16777215 + + + + 160 80 40 30 20 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Night: + + + + + + + + 160 + 16777215 + + + + 160 80 40 30 20 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Tune: + + + + + + + + 160 + 16777215 + + + + 80 40 30 20 17 15 12 10 + + + + + + + + 55 + 0 + + + + + 60 + 16777215 + + + + Gray time: + + + + + + + + 160 + 16777215 + + + + 60 + + + + + + @@ -2508,6 +2904,22 @@ QLabel[oob="true"] { + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 1 + + + + @@ -2588,6 +3000,8 @@ QLabel[oob="true"] { + + @@ -2973,6 +3387,25 @@ QLabel[oob="true"] { Include correlation + + + true + + + WSPR-2 + + + + + true + + + false + + + WSPR-15 + + @@ -2998,7 +3431,6 @@ QLabel[oob="true"] { txFirstCheckBox TxFreqSpinBox rptSpinBox - tabWidget genStdMsgsPushButton tx1 tx2 diff --git a/plotter.cpp b/plotter.cpp index 95c5254e4..662937433 100644 --- a/plotter.cpp +++ b/plotter.cpp @@ -37,6 +37,7 @@ CPlotter::CPlotter(QWidget *parent) : //CPlotter Constructor m_Percent2DScreen = 30; //percent of screen used for 2D display m_txFreq=0; m_fftBinWidth=1500.0/2048.0; + m_bScaleOK=false; } CPlotter::~CPlotter() { } // Destructor @@ -92,8 +93,10 @@ void CPlotter::draw(float swide[], bool bScroll) //dr int j,j0,y2; float y; - double gain = pow(10.0,0.02*m_plotGain); + double fac = sqrt(m_binsPerPixel*m_waterfallAvg/15.0); + double gain = fac*pow(10.0,0.02*m_plotGain); double gain2d = pow(10.0,0.02*(m_plot2dGain)); + qDebug() << m_binsPerPixel << m_waterfallAvg << m_plotGain << gain; //move current data down one line (must do this before attaching a QPainter object) if(bScroll) m_WaterfallPixmap.scroll(0,1,0,0,m_w,m_h1); @@ -159,7 +162,7 @@ void CPlotter::draw(float swide[], bool bScroll) //dr } } - if(m_bLinearAvg) { //Linear Avg + if(m_bLinearAvg) { //Linear Avg (yellow) float sum=0.0; int j=j0+m_binsPerPixel*i; for(int k=0; k1.0e29) m_line=0; m_line++; if(m_line == 13) { - UTCstr(); painter1.setPen(Qt::white); - painter1.drawText(5,10,m_sutc); + QString t=QDateTime::currentDateTimeUtc().toString("hh:mm") + " " + m_rxBand; + painter1.drawText(5,10,t); } if(m_mode=="JT4") { @@ -189,7 +192,6 @@ void CPlotter::draw(float swide[], bool bScroll) //dr painter2D.setPen(pen3); Font.setWeight(QFont::Bold); painter2D.setFont(Font); -// qDebug() << "B" << m_rxFreq; int x1=XfromFreq(m_rxFreq); y=0.2*m_h2; painter2D.drawText(x1-4,y,"T"); @@ -201,22 +203,7 @@ void CPlotter::draw(float swide[], bool bScroll) //dr painter2D.drawText(x1-4,y,"73"); } update(); //trigger a new paintEvent -} - -void CPlotter::UTCstr() //UTCstr -{ - int ihr,imin; - if(jt9com_.ndiskdat != 0) { - ihr=jt9com_.nutc/100; - imin=jt9com_.nutc % 100; - } else { - qint64 ms = QDateTime::currentMSecsSinceEpoch() % 86400000; - imin=ms/60000; - ihr=imin/60; - imin=imin % 60; - imin=imin - (imin % (m_TRperiod/60)); - } - sprintf(m_sutc,"%2.2d:%2.2d",ihr,imin); + m_bScaleOK=true; } void CPlotter::DrawOverlay() //DrawOverlay() @@ -225,7 +212,6 @@ void CPlotter::DrawOverlay() //DrawOverlay() if(m_WaterfallPixmap.isNull()) return; int w = m_WaterfallPixmap.width(); int x,y,x1,x2; -// int nHzDiv[11]={0,50,100,200,200,200,500,500,500,500,500}; float pixperdiv; double df = m_binsPerPixel*m_fftBinWidth; @@ -233,7 +219,7 @@ void CPlotter::DrawOverlay() //DrawOverlay() { QPainter painter(&m_OverlayPixmap); painter.initFrom(this); - QLinearGradient gradient(0, 0, 0 ,m_h2); //fill background with gradient + QLinearGradient gradient(0, 0, 0 ,m_h2); //fill background with gradient gradient.setColorAt(1, Qt::black); gradient.setColorAt(0, Qt::darkBlue); painter.setBrush(gradient); @@ -293,13 +279,13 @@ void CPlotter::DrawOverlay() //DrawOverlay() //draw tick marks on upper scale pixperdiv = m_freqPerDiv/df; - for( int i=0; i const& cl) {m_ColorTbl = cl;} void setFlatten(bool b); void setTol(int n); + void setRxBand(QString band); signals: void freezeDecode1(int n); @@ -88,7 +92,6 @@ protected: private: void MakeFrequencyStrs(); - void UTCstr(); int XfromFreq(float f); float FreqfromX(int x); @@ -106,6 +109,7 @@ private: qint32 m_plot2dGain; qint32 m_plot2dZero; qint32 m_binsPerPixel; + qint32 m_waterfallAvg; qint32 m_w; qint32 m_Flatten; qint32 m_nSubMode; @@ -120,6 +124,7 @@ private: QString m_HDivText[483]; QString m_mode; QString m_modeTx; + QString m_rxBand; bool m_Running; bool m_paintEventBusy; diff --git a/widegraph.cpp b/widegraph.cpp index 23a0a892a..0f1dfd87d 100644 --- a/widegraph.cpp +++ b/widegraph.cpp @@ -53,8 +53,11 @@ WideGraph::WideGraph(QSettings * settings, QWidget *parent) : ui->widePlot->setFlatten(m_bFlatten); ui->widePlot->setBreadth(m_settings->value("PlotWidth",1000).toInt()); ui->bppSpinBox->setValue(n); + m_nsmo=m_settings->value("SmoothYellow",1).toInt(); + ui->smoSpinBox->setValue(m_nsmo); m_waterfallAvg = m_settings->value("WaterfallAvg",5).toInt(); ui->waterfallAvgSpinBox->setValue(m_waterfallAvg); + ui->widePlot->setWaterfallAvg(m_waterfallAvg); ui->widePlot->setCurrent(m_settings->value("Current",false).toBool()); ui->widePlot->setCumulative(m_settings->value("Cumulative",true).toBool()); ui->widePlot->setLinearAvg(m_settings->value("LinearAvg",false).toBool()); @@ -111,6 +114,7 @@ void WideGraph::saveSettings() //saveS m_settings->setValue ("Plot2dZero", ui->widePlot->plot2dZero()); m_settings->setValue ("PlotWidth", ui->widePlot->plotWidth ()); m_settings->setValue ("BinsPerPixel", ui->bppSpinBox->value ()); + m_settings->setValue ("SmoothYellow", ui->smoSpinBox->value ()); m_settings->setValue ("WaterfallAvg", ui->waterfallAvgSpinBox->value ()); m_settings->setValue ("Current", ui->widePlot->current()); m_settings->setValue ("Cumulative", ui->widePlot->cumulative()); @@ -176,6 +180,7 @@ void WideGraph::on_bppSpinBox_valueChanged(int n) //b void WideGraph::on_waterfallAvgSpinBox_valueChanged(int n) //Navg { m_waterfallAvg = n; + ui->widePlot->setWaterfallAvg(n); } void WideGraph::keyPressEvent(QKeyEvent *e) //F11, F12 @@ -282,9 +287,15 @@ void WideGraph::on_spec2dComboBox_currentIndexChanged(const QString &arg1) ui->widePlot->setCurrent(false); ui->widePlot->setCumulative(false); ui->widePlot->setLinearAvg(false); + ui->smoSpinBox->setEnabled(false); + ui->labSmooth->setEnabled(false); if(arg1=="Current") ui->widePlot->setCurrent(true); if(arg1=="Cumulative") ui->widePlot->setCumulative(true); - if(arg1=="Linear Avg") ui->widePlot->setLinearAvg(true); + if(arg1=="Linear Avg") { + ui->widePlot->setLinearAvg(true); + ui->smoSpinBox->setEnabled(true); + ui->labSmooth->setEnabled(true); + } } void WideGraph::on_fSplitSpinBox_valueChanged(int n) //fSplit @@ -309,6 +320,12 @@ void WideGraph::setDialFreq(double d) //setDialFreq ui->widePlot->setDialFreq(d); } +void WideGraph::setRxBand(QString band) +{ + ui->widePlot->setRxBand(band); +} + + void WideGraph::on_fStartSpinBox_valueChanged(int n) //fStart { ui->widePlot->setStartFreq(n); @@ -384,12 +401,13 @@ void WideGraph::on_zeroSlider_valueChanged(int value) //Zero void WideGraph::on_gain2dSlider_valueChanged(int value) //Gain2 { ui->widePlot->setPlot2dGain(value); -// ui->widePlot->draw(swide); + if(ui->widePlot->m_bScaleOK) ui->widePlot->draw(swide,false); } void WideGraph::on_zero2dSlider_valueChanged(int value) //Zero2 { ui->widePlot->setPlot2dZero(value); +// ui->widePlot->draw(swide,false); } void WideGraph::setTol(int n) //setTol @@ -398,3 +416,13 @@ void WideGraph::setTol(int n) //setTol ui->widePlot->DrawOverlay(); ui->widePlot->update(); } + +void WideGraph::on_smoSpinBox_valueChanged(int n) +{ + m_nsmo=n; +} + +int WideGraph::smoothYellow() +{ + return m_nsmo; +} diff --git a/widegraph.h b/widegraph.h index b0f9ea533..3b8f99ac3 100644 --- a/widegraph.h +++ b/widegraph.h @@ -40,6 +40,8 @@ public: void setLockTxFreq(bool b); bool flatten(); void setTol(int n); + int smoothYellow(); + void setRxBand(QString band); signals: void freezeDecode2(int n); @@ -69,6 +71,7 @@ private slots: void on_zeroSlider_valueChanged(int value); void on_gain2dSlider_valueChanged(int value); void on_zero2dSlider_valueChanged(int value); + void on_smoSpinBox_valueChanged(int n); private: void readPalette(); @@ -88,6 +91,7 @@ private: qint32 m_fMin; qint32 m_fMax; qint32 m_nSubMode; + qint32 m_nsmo; bool m_lockTxFreq; bool m_bFlatten; diff --git a/widegraph.ui b/widegraph.ui index 323b3a372..315482204 100644 --- a/widegraph.ui +++ b/widegraph.ui @@ -95,59 +95,6 @@ - - - - - 0 - 0 - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Number of FFTs averaged (controls waterfall scrolling rate) - - - N Avg - - - 1 - - - 20 - - - - - - - - 0 - 0 - - - - - 80 - 0 - - - - Select waterfall palette - - - @@ -158,7 +105,7 @@ - 120 + 100 0 @@ -191,6 +138,72 @@ + + + + Flatten + + + + + + + + 0 + 0 + + + + + 80 + 0 + + + + + 125 + 16777215 + + + + Select waterfall palette + + + + + + + + 80 + 0 + + + + + 150 + 16777215 + + + + Spectrum gain + + + -50 + + + 50 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + @@ -201,7 +214,7 @@ - 120 + 100 0 @@ -234,169 +247,30 @@ - - - - - 0 - 0 - + + + + false - 100 + 65 0 - - - 100 - 16777215 - - - - Frequency (Hz) at left edge of waterfall - - - Hz - - - Start - - - 5000 - - - 100 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - Flatten - - - - - - - - - Palette - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Adjust... - - - - - - - - - Qt::Vertical - - - - - - - Qt::Vertical - - - - - - - - 150 - 16777215 - - - - Spectrum gain - - - -50 - - - 50 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove - - - - - - - - 150 - 16777215 - - - - Waterfall zero - - - -50 - - - 50 - - - 0 - - - Qt::Horizontal - - - QSlider::TicksAbove + Smoothing + + + 80 + 0 + + 150 @@ -423,8 +297,139 @@ + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 100 + 16777215 + + + + Frequency (Hz) at left edge of waterfall + + + Hz + + + Start + + + 5000 + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 100 + 16777215 + + + + Number of FFTs averaged (controls waterfall scrolling rate) + + + N Avg + + + 1 + + + 20 + + + + + + + Qt::Vertical + + + + + + + Qt::Vertical + + + + + + + + + Palette + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 65 + 16777215 + + + + Adjust... + + + + + + + + 80 + 0 + + 150 @@ -448,6 +453,88 @@ + + + + + 80 + 0 + + + + + 150 + 16777215 + + + + Waterfall zero + + + -50 + + + 50 + + + 0 + + + Qt::Horizontal + + + QSlider::TicksAbove + + + + + + + + + false + + + + 65 + 0 + + + + + 50 + 16777215 + + + + Smoothing of Linear Average spectrum + + + Qt::AlignCenter + + + 1 + + + 7 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + diff --git a/wsjtx.pro b/wsjtx.pro index 912a3465a..6cb7a73ae 100644 --- a/wsjtx.pro +++ b/wsjtx.pro @@ -83,14 +83,15 @@ SOURCES += \ soundin.cpp \ meterwidget.cpp \ signalmeter.cpp \ - WFPalette.cpp \ + WFPalette.cpp \ plotter.cpp \ widegraph.cpp \ about.cpp \ mainwindow.cpp \ main.cpp \ - decodedtext.cpp \ - messageaveraging.cpp + decodedtext.cpp \ + wsprnet.cpp \ + messageaveraging.cpp HEADERS += qt_helpers.hpp \ pimpl_h.hpp pimpl_impl.hpp \ @@ -102,7 +103,7 @@ HEADERS += qt_helpers.hpp \ FrequencyLineEdit.hpp AudioDevice.hpp Detector.hpp Modulator.hpp psk_reporter.h \ Transceiver.hpp TransceiverBase.hpp TransceiverFactory.hpp PollingTransceiver.hpp \ EmulateSplitTransceiver.hpp DXLabSuiteCommanderTransceiver.hpp HamlibTransceiver.hpp \ - Configuration.hpp \ + Configuration.hpp wsprnet.h \ signalmeter.h \ meterwidget.h \ logbook/logbook.h \ diff --git a/wsprnet.cpp b/wsprnet.cpp new file mode 100644 index 000000000..b6004684f --- /dev/null +++ b/wsprnet.cpp @@ -0,0 +1,192 @@ +// Interface to WSPRnet website +// +// by Edson Pereira - PY2SDR + +#include "wsprnet.h" + +WSPRNet::WSPRNet(QObject *parent) : + QObject(parent) +{ + wsprNetUrl = "http://wsprnet.org/post?"; + //wsprNetUrl = "http://127.0.0.1/post.php?"; + networkManager = new QNetworkAccessManager(this); + connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReply(QNetworkReply*))); + + uploadTimer = new QTimer(this); + connect( uploadTimer, SIGNAL(timeout()), this, SLOT(work())); +} + +void WSPRNet::upload(QString call, QString grid, QString rfreq, QString tfreq, + QString mode, QString tpct, QString dbm, QString version, + QString fileName) +{ + m_call = call; + m_grid = grid; + m_rfreq = rfreq; + m_tfreq = tfreq; + m_mode = mode; + m_tpct = tpct; + m_dbm = dbm; + m_vers = version; + m_file = fileName; + + // Open the wsprd.out file + QFile wsprdOutFile(fileName); + if (!wsprdOutFile.open(QIODevice::ReadOnly | QIODevice::Text) || + wsprdOutFile.size() == 0) { + urlQueue.enqueue( wsprNetUrl + urlEncodeNoSpot()); + m_uploadType = 1; + uploadTimer->start(200); + return; + } + + // Read the contents + while (!wsprdOutFile.atEnd()) { + QHash query; + if ( decodeLine(wsprdOutFile.readLine(), query) ) { + // Prevent reporting data ouside of the current frequency band + float f = fabs(m_rfreq.toFloat() - query["tqrg"].toFloat()); + if (f < 0.0002) { + urlQueue.enqueue( wsprNetUrl + urlEncodeSpot(query)); + m_uploadType = 2; + } + } + } + m_urlQueueSize = urlQueue.size(); + uploadTimer->start(200); +} + +void WSPRNet::networkReply(QNetworkReply *reply) +{ + QString serverResponse = reply->readAll(); + if( m_uploadType == 2) { + if (!serverResponse.contains(QRegExp("spot\\(s\\) added"))) { + emit uploadStatus("Upload Failed"); + urlQueue.clear(); + uploadTimer->stop(); + } + } + + if (urlQueue.isEmpty()) { + emit uploadStatus("done"); + QFile::remove(m_file); + uploadTimer->stop(); + } +} + +bool WSPRNet::decodeLine(QString line, QHash &query) +{ + // 130223 2256 7 -21 -0.3 14.097090 DU1MGA PK04 37 0 40 0 + // Date Time Sync dBm DT Freq Msg + // 1 2 3 4 5 6 -------7------ 8 9 10 + QRegExp rx("^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+\\.\\d+)\\s+(\\d+\\.\\d+)\\s+(.*)\\s+([+-]?\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+)"); + if (rx.indexIn(line) != -1) { + int msgType = 0; + QString msg = rx.cap(7); + msg.remove(QRegExp("\\s+$")); + msg.remove(QRegExp("^\\s+")); + QString call, grid, dbm; + QRegExp msgRx; + + // Check for Message Type 1 + msgRx.setPattern("^([A-Z0-9]{3,6})\\s+([A-Z]{2}\\d{2})\\s+(\\d+)"); + if (msgRx.indexIn(msg) != -1) { + msgType = 1; + call = msgRx.cap(1); + grid = msgRx.cap(2); + dbm = msgRx.cap(3); + } + + // Check for Message Type 2 + msgRx.setPattern("^([A-Z0-9/]+)\\s+(\\d+)"); + if (msgRx.indexIn(msg) != -1) { + msgType = 2; + call = msgRx.cap(1); + grid = ""; + dbm = msgRx.cap(2); + } + + // Check for Message Type 3 + msgRx.setPattern("^<([A-Z0-9/]+)>\\s+([A-Z]{2}\\d{2}[A-Z]{2})\\s+(\\d+)"); + if (msgRx.indexIn(msg) != -1) { + msgType = 3; + call = msgRx.cap(1); + grid = msgRx.cap(2); + dbm = msgRx.cap(3); + } + + // Unknown message format + if (!msgType) { + return false; + } + + query["function"] = "wspr"; + query["date"] = rx.cap(1); + query["time"] = rx.cap(2); + query["sig"] = rx.cap(4); + query["dt"] = rx.cap(5); + query["drift"] = rx.cap(8); + query["tqrg"] = rx.cap(6); + query["tcall"] = call; + query["tgrid"] = grid; + query["dbm"] = dbm; + } else { + return false; + } + return true; +} + +QString WSPRNet::urlEncodeNoSpot() +{ + QString queryString; + queryString += "function=wsprstat&"; + queryString += "rcall=" + m_call + "&"; + queryString += "rgrid=" + m_grid + "&"; + queryString += "rqrg=" + m_rfreq + "&"; + queryString += "tpct=" + m_tpct + "&"; + queryString += "tqrg=" + m_tfreq + "&"; + queryString += "dbm=" + m_dbm + "&"; + queryString += "version=" + m_vers; + if(m_mode=="WSPR-2") queryString += "&mode=2"; + if(m_mode=="WSPR-15") queryString += "&mode=15"; + return queryString;; +} + +QString WSPRNet::urlEncodeSpot(QHash query) +{ + QString queryString; + queryString += "function=" + query["function"] + "&"; + queryString += "rcall=" + m_call + "&"; + queryString += "rgrid=" + m_grid + "&"; + queryString += "rqrg=" + m_rfreq + "&"; + queryString += "date=" + query["date"] + "&"; + queryString += "time=" + query["time"] + "&"; + queryString += "sig=" + query["sig"] + "&"; + queryString += "dt=" + query["dt"] + "&"; + queryString += "drift=" + query["drift"] + "&"; + queryString += "tqrg=" + query["tqrg"] + "&"; + queryString += "tcall=" + query["tcall"] + "&"; + queryString += "tgrid=" + query["tgrid"] + "&"; + queryString += "dbm=" + query["dbm"] + "&"; + queryString += "version=" + m_vers; + if(m_mode=="WSPR-2") queryString += "&mode=2"; + if(m_mode=="WSPR-15") queryString += "&mode=15"; + return queryString; +} + +void WSPRNet::work() +{ + if (!urlQueue.isEmpty()) { + QUrl url(urlQueue.dequeue()); + QNetworkRequest request(url); + networkManager->get(request); + QString status = "Uploading Spot " + QString::number(m_urlQueueSize - urlQueue.size()) + + "/"+ QString::number(m_urlQueueSize); + emit uploadStatus(status); + } else { + uploadTimer->stop(); + } +} + + + diff --git a/wsprnet.h b/wsprnet.h new file mode 100644 index 000000000..1c9379d73 --- /dev/null +++ b/wsprnet.h @@ -0,0 +1,37 @@ +#ifndef WSPRNET_H +#define WSPRNET_H + +#include +#include + +class WSPRNet : public QObject +{ +Q_OBJECT +public: + explicit WSPRNet(QObject *parent = 0); + void upload(QString call, QString grid, QString rfreq, QString tfreq, + QString mode, QString tpct, QString dbm, QString version, + QString fileName); + static bool decodeLine(QString line, QHash &query); + +signals: + void uploadStatus(QString); + +public slots: + void networkReply(QNetworkReply *); + void work(); + +private: + QNetworkAccessManager *networkManager; + QString wsprNetUrl; + QString m_call, m_grid, m_rfreq, m_tfreq, m_mode, m_tpct, m_dbm, m_vers, m_file; + QQueue urlQueue; + QTimer *uploadTimer; + int m_urlQueueSize; + int m_uploadType; + + QString urlEncodeNoSpot(); + QString urlEncodeSpot(QHash spot); +}; + +#endif // WSPRNET_H